Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 43 additions & 13 deletions lib/core/services/logger_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ LoggyPrinter _defaultConsolePrinter() {
}
}

Future<void> initLogger([String? path]) async {
void initLogger([String? path]) {
LoggyPrinter logPrinter;

if (path != null) {
Expand All @@ -31,7 +31,7 @@ Future<void> initLogger([String? path]) async {
maxBackupFiles: 1,
);
} catch (e) {
print("Log rotation check failed: $e");
debugPrint("Log rotation check failed: $e");
}
logPrinter = MultiLogPrinter([
_defaultConsolePrinter(),
Expand Down Expand Up @@ -123,14 +123,29 @@ class FileLogPrinter extends LoggyPrinter {
}
}

// Utility class for log file rotation management
/// Utility class for log file rotation management.
///
/// Handles log file size checking, rotation, compression, and cleanup of old backups.
/// Performs synchronous I/O operations, so it should be called before the logger
/// starts writing to files to avoid race conditions.
class LogRotation {
/// Checks if the log file exceeds the size limit and rotates it if needed.
///
/// Creates the log file if it doesn't exist. If the file size exceeds
/// [maxFileSizeInBytes], rotates the log by renaming it with a timestamp,
/// compressing it to a zip archive, and creating a new empty log file.
/// Also cleans up old backup files beyond [maxBackupFiles] limit.
///
/// Parameters:
/// - [path]: Path to the log file to check
/// - [maxFileSizeInBytes]: Maximum file size before rotation
/// - [maxBackupFiles]: Maximum number of backup zip files to keep (default: 2)
static void checkAndRotateIfNeeded({
required String path,
required int maxFileSizeInBytes,
int maxBackupFiles = 2,
}) {
print("Checking log file size for rotation: $path");
debugPrint("Checking log file size for rotation: $path");
final file = File(path);

if (!file.existsSync()) {
Expand All @@ -146,11 +161,14 @@ class LogRotation {

static void _rotateLog(File currentFile, int maxBackupFiles) {
try {
print("Rotating log file: ${currentFile.path}");
debugPrint("Rotating log file: ${currentFile.path}");
final timestamp = DateFormat('yyyyMMdd_HHmmss').format(DateTime.now());
final directory = currentFile.parent;
final fileName = currentFile.path.split(Platform.pathSeparator).last;
final nameWithoutExt = fileName.replaceAll('.log', '');
const logSuffix = '.log';
final nameWithoutExt = fileName.endsWith(logSuffix)
? fileName.substring(0, fileName.length - logSuffix.length)
: fileName;

// Create backup file path
final backupPath =
Expand All @@ -160,22 +178,30 @@ class LogRotation {
// Compress the backup file to zip
final zipPath =
'${directory.path}${Platform.pathSeparator}${nameWithoutExt}_$timestamp.zip';
_compressFile(backupPath, zipPath);
// Delete the original log file after compression
File(backupPath).deleteSync();
final compressionSucceeded = _compressFile(backupPath, zipPath);

// Only delete the backup file if compression succeeded and zip file exists
if (compressionSucceeded && File(zipPath).existsSync()) {
final zipFile = File(zipPath);
if (zipFile.lengthSync() > 0) {
File(backupPath).deleteSync();
} else {
debugPrint("Warning: Zip file is empty, keeping backup file");
}
}

// Create new log file
File(currentFile.path).createSync();
// Clean up old backups (now looking for .zip files)
_cleanupOldBackups(
directory, nameWithoutExt, currentFile.path, maxBackupFiles);
_cleanupOldBackups(directory, nameWithoutExt, maxBackupFiles);

debugPrint("Log rotated and compressed: $zipPath");
} catch (e, st) {
debugPrint("Failed to rotate log: $e\n$st");
}
}

static void _compressFile(String sourcePath, String zipPath) {
static bool _compressFile(String sourcePath, String zipPath) {
try {
final sourceFile = File(sourcePath);
final bytes = sourceFile.readAsBytesSync();
Expand All @@ -191,16 +217,18 @@ class LogRotation {
// Write zip file
if (zipData != null) {
File(zipPath).writeAsBytesSync(zipData);
return true;
}
return false;
} catch (e, st) {
debugPrint("Failed to compress log file: $e\n$st");
return false;
}
}

static void _cleanupOldBackups(
Directory directory,
String nameWithoutExt,
String currentPath,
int maxBackupFiles,
) {
try {
Expand All @@ -211,10 +239,12 @@ class LogRotation {
name.endsWith('.zip'); // Look for .zip files
}).toList();

// Sort by modification time in ascending order (oldest first).
backupFiles
.sort((a, b) => a.lastModifiedSync().compareTo(b.lastModifiedSync()));

if (backupFiles.length > maxBackupFiles) {
// Delete the oldest files beyond the maxBackupFiles limit, keeping the newest ones.
final filesToDelete =
backupFiles.take(backupFiles.length - maxBackupFiles);
for (var file in filesToDelete) {
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ dependencies:
win32: ^5.15.0
fixnum: ^1.1.1
package_info_plus: ^9.0.0
archive: ^4.0.7

dev_dependencies:
flutter_test:
Expand All @@ -136,7 +137,6 @@ dev_dependencies:

riverpod_lint: ^3.0.3
ffigen: ^20.0.0
archive: ^4.0.7

# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
Expand Down