Skip to content

Commit

Permalink
Add log tracking
Browse files Browse the repository at this point in the history
  • Loading branch information
dab246 committed Jun 20, 2024
1 parent 98c848d commit 0483ff0
Show file tree
Hide file tree
Showing 40 changed files with 1,006 additions and 48 deletions.
21 changes: 21 additions & 0 deletions core/lib/domain/exceptions/file_exception.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import 'package:equatable/equatable.dart';

abstract class FileException with EquatableMixin implements Exception {
final String message;

FileException(this.message);

@override
String toString() => message;

@override
List<Object> get props => [message];
}

class NotFoundFileInFolderException extends FileException {
NotFoundFileInFolderException() : super('No files found in the folder');
}

class UserCancelShareFileException extends FileException {
UserCancelShareFileException() : super('User cancel share file');
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ class CupertinoLoadingWidget extends StatelessWidget {
final double? size;
final EdgeInsetsGeometry? padding;
final bool isCenter;
final Color? progressColor;

const CupertinoLoadingWidget({
super.key,
this.size,
this.padding,
this.isCenter = true,
this.progressColor,
});

@override
Expand All @@ -21,8 +23,8 @@ class CupertinoLoadingWidget extends StatelessWidget {
child: SizedBox(
width: size ?? CupertinoLoadingWidgetStyles.size,
height: size ?? CupertinoLoadingWidgetStyles.size,
child: const CupertinoActivityIndicator(
color: CupertinoLoadingWidgetStyles.progressColor
child: CupertinoActivityIndicator(
color: progressColor ?? CupertinoLoadingWidgetStyles.progressColor
)
)
)
Expand All @@ -31,8 +33,8 @@ class CupertinoLoadingWidget extends StatelessWidget {
child: SizedBox(
width: size ?? CupertinoLoadingWidgetStyles.size,
height: size ?? CupertinoLoadingWidgetStyles.size,
child: const CupertinoActivityIndicator(
color: CupertinoLoadingWidgetStyles.progressColor
child: CupertinoActivityIndicator(
color: progressColor ?? CupertinoLoadingWidgetStyles.progressColor
)
),
);
Expand Down
12 changes: 9 additions & 3 deletions core/lib/utils/app_logger.dart
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import 'dart:async';

import 'package:core/utils/log_tracking.dart';
import 'package:core/utils/platform_info.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

final logHistory = _Dispatcher('');

void log(String? value, {Level level = Level.info}) {
if (!kDebugMode) return;
Future<void> log(String? value, {Level level = Level.info}) async {
if (!kDebugMode && !LogTracking().enableTraceLog) return;

String logsStr = value ?? '';
logHistory.value = '$logsStr\n${logHistory.value}';
Expand Down Expand Up @@ -41,11 +42,16 @@ void log(String? value, {Level level = Level.info}) {
break;
}
}

// ignore: avoid_print
print('[TwakeMail] $logsStr');

if (LogTracking().enableTraceLog) {
await LogTracking().addLog(message: logsStr);
}
}

void logError(String? value) => log(value, level: Level.error);
Future<void> logError(String? value) => log(value, level: Level.error);

// Take from: https://flutter.dev/docs/testing/errors
void initLogger(VoidCallback runApp) {
Expand Down
54 changes: 54 additions & 0 deletions core/lib/utils/file_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'dart:convert';
import 'dart:io';

import 'package:core/domain/exceptions/download_file_exception.dart';
import 'package:core/domain/exceptions/file_exception.dart';
import 'package:core/utils/app_logger.dart';
import 'package:core/utils/platform_info.dart';
import 'package:flutter/services.dart';
Expand Down Expand Up @@ -68,6 +69,18 @@ class FileUtils {
}
}

Future<void> deleteFileByFolderName({required String nameFile, String? folderPath}) async {
final internalStorageDirPath = await _getInternalStorageDirPath(
nameFile: nameFile,
folderPath: folderPath);

final file = File(internalStorageDirPath);

if (await file.exists()) {
await file.delete();
}
}

Future<String> getContentFromFile({
required String nameFile,
String? folderPath,
Expand Down Expand Up @@ -121,4 +134,45 @@ class FileUtils {
final base64Data = base64Encode(Uint8List.view(buffer));
return base64Data;
}

static Future<String> getExternalDocumentPath({String? folderPath}) async {
Directory directory = Directory('');
if (Platform.isAndroid) {
if (folderPath?.isNotEmpty == true) {
directory = Directory('/storage/emulated/0/Download/$folderPath');
} else {
directory = Directory('/storage/emulated/0/Download');
}
} else {
directory = await getApplicationDocumentsDirectory();
if (folderPath?.isNotEmpty == true) {
directory = Directory('${directory.absolute.path}/$folderPath');
}
}

final exPath = directory.path;
log('FileUtils::getExternalDocumentPath:Saved Path: $exPath');
await Directory(exPath).create(recursive: true);
return exPath;
}

static Future<String> copyInternalFilesToDownloadExternal(List<String> listFilePaths) async {
final externalPath = await getExternalDocumentPath();

List<String> externalListPaths = [];
for (var filePath in listFilePaths) {
final file = File(filePath);
final fileName = filePath.substring(filePath.lastIndexOf('/') + 1);
log('FileUtils::copyInternalFilesToDownloadExternal:filePath: $filePath | fileName: $fileName');
final externalFile = File('$externalPath/$fileName');
await externalFile.writeAsBytes(file.readAsBytesSync());
externalListPaths.add(externalFile.path);
}

if (externalListPaths.isNotEmpty) {
return externalPath;
} else {
throw NotFoundFileInFolderException();
}
}
}
3 changes: 1 addition & 2 deletions core/lib/utils/html/html_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ class HtmlUtils {

static const unregisterDropListener = (
script: '''
console.log("unregisterDropListener");
const editor = document.querySelector(".note-editable");
const newEditor = editor.cloneNode(true);
editor.parentNode.replaceChild(newEditor, editor);''',
Expand Down Expand Up @@ -76,12 +75,12 @@ class HtmlUtils {
required String base64Data,
required String mimeType
}) {
log('HtmlUtils::convertBase64ToImageResourceData:');
mimeType = validateHtmlImageResourceMimeType(mimeType);
if (!base64Data.endsWith('==')) {
base64Data.append('==');
}
final imageResource = 'data:$mimeType;base64,$base64Data';
log('HtmlUtils::convertBase64ToImageResourceData:imageResource: $imageResource');
return imageResource;
}

Expand Down
200 changes: 200 additions & 0 deletions core/lib/utils/log_tracking.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
import 'dart:async';
import 'dart:collection';
import 'dart:io';

import 'package:core/domain/exceptions/download_file_exception.dart';
import 'package:core/utils/app_logger.dart';
import 'package:core/utils/file_utils.dart';
import 'package:core/utils/platform_info.dart';
import 'package:dartz/dartz.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.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;

bool enableTraceLog = PlatformInfo.isMobile;

final Queue<String> _messagesQueue = Queue();
bool _isScheduled = false;

Future<void> addLog({required String message}) async {
_messagesQueue.add(message);

if (!_isScheduled) {
_isScheduled = true;
await _executeTraceLog();
}
}

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);
}

Future<TraceLog> getTraceLog() async {
final folderPath = await _getInternalStorageDirPath(folderPath: logFolder);
log('LogTracking::getTraceLog:folderPath = $folderPath');
final directory = Directory(folderPath);
if (directory.existsSync()) {
final directoryInfo = await getDirInfo(directory);
log('LogTracking::getTraceLog:directorySize = ${directoryInfo.value1}');
log('LogTracking::getTraceLog:CountFile = ${directoryInfo.value2.length}');
return TraceLog(
path: folderPath,
size: directoryInfo.value1,
listFilePaths: directoryInfo.value2);
} else {
throw Exception('Trace folder not exist');
}
}

Future<Tuple2<int, List<String>>> getDirInfo(Directory dir) async {
var files = await dir.list(recursive: true).toList();
var dirSize = files.fold(0, (int sum, file) => sum + file.statSync().size);
var listPath = files.map((file) => file.path).toList();
return Tuple2(dirSize, listPath);
}

Future<String> exportTraceLog(TraceLog traceLog) async {
if (PlatformInfo.isIOS) {
final savePath = FileUtils.getExternalDocumentPath(folderPath: logFolder);
log('LogTracking::exportTraceLog:savePath = $savePath');
return savePath;
} else {
final savePath = await compute(
FileUtils.copyInternalFilesToDownloadExternal,
traceLog.listFilePaths);
log('LogTracking::exportTraceLog:savePath = $savePath');
return savePath;
}
}
}

class TraceLog with EquatableMixin {
final String path;
final int size;
final List<String> listFilePaths;

TraceLog({
required this.path,
required this.size,
required this.listFilePaths
});

@override
List<Object?> get props => [path, size, listFilePaths];
}
Loading

0 comments on commit 0483ff0

Please sign in to comment.