TF-3719 Add LogTracking to trace error notFound email in Email/get

Signed-off-by: dab246 <tdvu@linagora.com>
This commit is contained in:
dab246
2025-05-15 15:25:41 +07:00
committed by Dat H. Pham
parent 21f4b4bfa0
commit 3f5cfef2fa
5 changed files with 183 additions and 2 deletions
+1
View File
@@ -54,6 +54,7 @@ export 'utils/mail/domain.dart';
export 'utils/mail/mail_address.dart';
export 'utils/application_manager.dart';
export 'utils/preview_eml_file_utils.dart';
export 'utils/logger/log_tracking.dart';
// Views
export 'presentation/views/text/slogan_builder.dart';
+150
View File
@@ -0,0 +1,150 @@
import 'dart:async';
import 'dart:collection';
import 'dart:io';
import 'package:core/domain/exceptions/download_file_exception.dart';
import 'package:core/utils/platform_info.dart';
import 'package:intl/intl.dart';
import 'package:path_provider/path_provider.dart';
class LogTracking {
static const String logFolder = 'TraceLogs';
static const String logFileNameDatePattern = 'yyyy-MM-dd';
static const String logMessageDatePattern = 'yyyy-MM-dd, HH:mm:ss';
LogTracking._();
factory LogTracking() => _instance ??= LogTracking._();
static LogTracking? _instance;
final Queue<String> _messagesQueue = Queue();
bool _isScheduled = false;
Future<void> addLog({required String message}) async {
try {
_messagesQueue.add(message);
if (!_isScheduled) {
_isScheduled = true;
await _executeTraceLog();
}
} catch (_) {
_messagesQueue.remove(message);
_isScheduled = false;
}
}
Future _executeTraceLog() async {
while (true) {
try {
if (_messagesQueue.isEmpty) {
_isScheduled = false;
return;
}
final message = _messagesQueue.removeFirst();
await saveLog(message: message);
} catch (_) {}
}
}
Future<void> saveLog({required String message}) async {
try {
final currentDate = DateTime.timestamp();
final logFileName = generateLogFileName(currentDate: currentDate);
final messageSanitized = sanitizeMessage(
message: message,
currentDate: currentDate,
);
await saveToFile(
nameFile: logFileName,
folderPath: logFolder,
content: messageSanitized,
);
} catch (_) {}
}
String sanitizeMessage({
required String message,
required DateTime currentDate,
}) {
final dateFormat = getDateFormatAsString(
pattern: logMessageDatePattern,
currentDate: currentDate,
);
return '($dateFormat): $message \n';
}
String getDateFormatAsString({
required String pattern,
required DateTime currentDate,
}) {
final dateFormat = DateFormat(pattern);
return dateFormat.format(currentDate);
}
String generateLogFileName({required DateTime currentDate}) {
final dateFormat = getDateFormatAsString(
pattern: logFileNameDatePattern,
currentDate: currentDate,
);
return '${dateFormat}_log';
}
Future<String> _getInternalStorageDirPath({
String? nameFile,
String? folderPath,
String? extensionFile,
}) async {
if (PlatformInfo.isMobile) {
String fileDirectory =
(await getApplicationDocumentsDirectory()).absolute.path;
if (folderPath != null) {
fileDirectory = '$fileDirectory/$folderPath';
}
Directory directory = Directory(fileDirectory);
if (!await directory.exists()) {
await directory.create(recursive: true);
}
if (nameFile != null) {
fileDirectory = '$fileDirectory/$nameFile';
}
if (extensionFile != null) {
fileDirectory = '$fileDirectory.$extensionFile';
}
return fileDirectory;
} else {
throw DeviceNotSupportedException();
}
}
Future<File> saveToFile({
required String nameFile,
required String content,
String? folderPath,
String? extensionFile,
FileMode fileMode = FileMode.append,
}) async {
final internalStorageDirPath = await _getInternalStorageDirPath(
nameFile: nameFile,
folderPath: folderPath,
extensionFile: extensionFile,
);
final file = File(internalStorageDirPath);
return await file.writeAsString(content, mode: fileMode);
}
}
@@ -9,4 +9,6 @@ import 'package:tmail_ui_user/features/caching/utils/cache_utils.dart';
extension ListEmailIdExtension on List<EmailId> {
List<String> toCacheKeyList(AccountId accountId, UserName userName) =>
map((id) => TupleKey(id.asString, accountId.asString, userName.value).encodeKey).toList();
List<String> get asListString => map((id) => id.asString).toList();
}
@@ -1,6 +1,7 @@
import 'dart:async';
import 'package:core/utils/app_logger.dart';
import 'package:core/utils/logger/log_tracking.dart';
import 'package:jmap_dart_client/http/http_client.dart';
import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/core/filter/filter.dart';
@@ -25,10 +26,12 @@ import 'package:tmail_ui_user/features/thread/data/extensions/list_email_extensi
import 'package:jmap_dart_client/jmap/mail/email/search_snippet/search_snippet.dart';
import 'package:jmap_dart_client/jmap/mail/email/search_snippet/search_snippet_get_method.dart';
import 'package:jmap_dart_client/jmap/mail/email/search_snippet/search_snippet_get_response.dart';
import 'package:tmail_ui_user/features/thread/data/extensions/list_email_id_extension.dart';
import 'package:tmail_ui_user/features/thread/data/model/email_change_response.dart';
import 'package:tmail_ui_user/features/thread/domain/model/email_response.dart';
import 'package:tmail_ui_user/features/thread/domain/model/search_emails_response.dart';
import 'package:tmail_ui_user/main/error/capability_validator.dart';
import 'package:tmail_ui_user/main/utils/app_config.dart';
class ThreadAPI {
@@ -101,6 +104,11 @@ class ThreadAPI {
?.toEmailIds()
.toList();
log('ThreadAPI::getAllEmail:notFoundEmailIds = $notFoundEmailIds');
if (notFoundEmailIds?.isNotEmpty == true && AppConfig.isApiLoggingEnabled) {
await LogTracking().addLog(
message: 'ThreadAPI::getAllEmail:notFoundEmailIds = ${notFoundEmailIds!.asListString.toString()}',
);
}
return EmailsResponse(
emailList: emailList,
notFoundEmailIds: notFoundEmailIds,
@@ -299,7 +307,15 @@ class ThreadAPI {
newStateEmail = emailResponseUpdated?.state;
final notFoundIdsUpdated = emailResponseUpdated?.notFound?.toEmailIds().toList() ?? [];
log('ThreadAPI::getChanges:notFoundIdsUpdated = $notFoundIdsUpdated');
destroyedEmailIds.addAll(notFoundIdsUpdated);
if (notFoundIdsUpdated.isNotEmpty) {
destroyedEmailIds.addAll(notFoundIdsUpdated);
if (AppConfig.isApiLoggingEnabled) {
await LogTracking().addLog(
message: 'ThreadAPI::getChanges:notFoundIdsUpdated = ${notFoundIdsUpdated.asListString.toString()}',
);
}
}
}
if (getEmailCreatedInvocation != null) {
@@ -311,7 +327,15 @@ class ThreadAPI {
newStateEmail = emailResponseCreated?.state;
final notFoundIdsCreated = emailResponseCreated?.notFound?.toEmailIds().toList() ?? [];
log('ThreadAPI::getChanges:notFoundIdsCreated = $notFoundIdsCreated');
destroyedEmailIds.addAll(notFoundIdsCreated);
if (notFoundIdsCreated.isNotEmpty) {
destroyedEmailIds.addAll(notFoundIdsCreated);
if (AppConfig.isApiLoggingEnabled) {
await LogTracking().addLog(
message: 'ThreadAPI::getChanges:notFoundIdsCreated = ${notFoundIdsCreated.asListString.toString()}',
);
}
}
}
log('ThreadAPI::getChanges:newStateChanges = $newStateChanges | newStateEmail = $newStateEmail | hasMoreChanges = $hasMoreChanges');
log('ThreadAPI::getChanges:updatedEmailSize = ${updatedEmail?.length} | createdEmailSize = ${createdEmail?.length}');
+4
View File
@@ -1,5 +1,6 @@
import 'dart:io';
import 'package:core/utils/platform_info.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
@@ -13,6 +14,9 @@ class AppConfig {
static const int warningAttachmentFileSizeInMegabytes = 10;
static const int defaultLimitAutocomplete = 8;
// For testing
static bool isApiLoggingEnabled = PlatformInfo.isAndroid;
static const String envFileName = 'env.file';
static const String appDashboardConfigurationPath = "configurations/app_dashboard.json";
static const String appFCMConfigurationPath = "configurations/env.fcm";