diff --git a/lib/src/outputs/advanced_file_output.dart b/lib/src/outputs/advanced_file_output.dart index 9737adc..4b8d525 100644 --- a/lib/src/outputs/advanced_file_output.dart +++ b/lib/src/outputs/advanced_file_output.dart @@ -2,6 +2,8 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'package:meta/meta.dart'; + import '../log_level.dart'; import '../log_output.dart'; import '../output_event.dart'; @@ -94,8 +96,9 @@ class AdvancedFileOutput extends LogOutput { ], _maxRotatedFilesCount = maxRotatedFilesCount, _fileSorter = fileSorter ?? _defaultFileSorter, - _fileUpdateDuration = fileUpdateDuration, - _file = maxFileSizeKB > 0 ? File('$path/$latestFileName') : File(path); + _fileUpdateDuration = fileUpdateDuration { + _file = createFile(maxFileSizeKB > 0 ? '$path/$latestFileName' : path); + } /// Logs directory path by default, particular log file path if [_maxFileSizeKB] is 0. final String _path; @@ -115,7 +118,7 @@ class AdvancedFileOutput extends LogOutput { final Comparator _fileSorter; final Duration _fileUpdateDuration; - final File _file; + late final File _file; IOSink? _sink; Timer? _bufferFlushTimer; Timer? _targetFileUpdater; @@ -142,14 +145,18 @@ class AdvancedFileOutput extends LogOutput { return a.lastModifiedSync().compareTo(b.lastModifiedSync()); } + @protected + File createFile(String path) { + return File(path); + } + @override Future init() async { if (_rotatingFilesMode) { - final dir = Directory(_path); // We use sync directory check to avoid losing potential initial boot logs // in early crash scenarios. - if (!dir.existsSync()) { - dir.createSync(recursive: true); + if (!_file.parent.existsSync()) { + _file.parent.createSync(recursive: true); } _targetFileUpdater = Timer.periodic( @@ -232,8 +239,7 @@ class AdvancedFileOutput extends LogOutput { // If maxRotatedFilesCount is not set, keep all files if (_maxRotatedFilesCount == null) return; - final dir = Directory(_path); - final files = dir + final files = _file.parent .listSync() .whereType() // Filter out the latest file diff --git a/lib/src/outputs/advanced_file_output_stub.dart b/lib/src/outputs/advanced_file_output_stub.dart index 1cfd820..dcfd10c 100644 --- a/lib/src/outputs/advanced_file_output_stub.dart +++ b/lib/src/outputs/advanced_file_output_stub.dart @@ -1,6 +1,8 @@ import 'dart:convert'; import 'dart:io'; +import 'package:meta/meta.dart'; + import '../log_level.dart'; import '../log_output.dart'; import '../output_event.dart'; @@ -76,6 +78,11 @@ class AdvancedFileOutput extends LogOutput { throw UnsupportedError("Not supported on this platform."); } + @protected + File createFile(String path) { + throw UnsupportedError("Not supported on this platform."); + } + @override void output(OutputEvent event) { throw UnsupportedError("Not supported on this platform."); diff --git a/pubspec.yaml b/pubspec.yaml index 6cab836..9ddf188 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,9 +10,13 @@ topics: environment: sdk: ">=2.17.0 <4.0.0" +dependencies: + meta: ^1.16.0 + dev_dependencies: test: ^1.16.8 lints: ^2.0.1 + file: ">=6.0.0 <8.0.0" platforms: android: diff --git a/test/outputs/advanced_file_output_test.dart b/test/outputs/advanced_file_output_test.dart index 49b8f6c..33307cd 100644 --- a/test/outputs/advanced_file_output_test.dart +++ b/test/outputs/advanced_file_output_test.dart @@ -1,24 +1,53 @@ import 'dart:io'; +import 'package:file/memory.dart'; import 'package:logger/logger.dart'; import 'package:test/test.dart'; -void main() { - var file = File("${Directory.systemTemp.path}/dart_advanced_logger_test.log"); - var dir = Directory("${Directory.systemTemp.path}/dart_advanced_logger_dir"); - setUp(() async { - await file.create(recursive: true); - await dir.create(recursive: true); +var memory = MemoryFileSystem(); + +class MemoryAdvancedFileOutput extends AdvancedFileOutput { + MemoryAdvancedFileOutput({ + required super.path, + super.maxDelay, + super.maxFileSizeKB, + super.writeImmediately, + super.maxRotatedFilesCount, + super.fileNameFormatter, + super.fileSorter, + super.fileHeader, + super.fileFooter, }); - tearDown(() async { - await file.delete(); - await dir.delete(recursive: true); + late File _file; + + get file => _file; + + @override + File createFile(String path) { + return _file = memory.file(path); + } +} + +void main() { + final fileName = "dart_advanced_logger_test.log"; + final dirName = "dart_advanced_logger_dir"; + + tearDown(() { + var file = memory.file(fileName); + if (file.existsSync()) { + file.deleteSync(); + } + + var directory = memory.directory(dirName); + if (directory.existsSync()) { + directory.deleteSync(recursive: true); + } }); test('Real file read and write with buffer accumulation', () async { - var output = AdvancedFileOutput( - path: file.path, + var output = MemoryAdvancedFileOutput( + path: fileName, maxDelay: const Duration(milliseconds: 500), maxFileSizeKB: 0, ); @@ -32,12 +61,9 @@ void main() { output.output(event1); output.output(event2); - // Wait until buffer is flushed to file - await Future.delayed(const Duration(seconds: 1)); - await output.destroy(); - var content = await file.readAsString(); + var content = await output.file.readAsString(); expect( content, allOf( @@ -50,8 +76,8 @@ void main() { test('Real file read and write with rotating file names and immediate output', () async { - var output = AdvancedFileOutput( - path: dir.path, + var output = MemoryAdvancedFileOutput( + path: dirName, writeImmediately: [Level.info], ); await output.init(); @@ -66,8 +92,7 @@ void main() { await output.destroy(); - final logFile = File('${dir.path}/latest.log'); - var content = await logFile.readAsString(); + var content = await output.file.readAsString(); expect( content, allOf( @@ -79,8 +104,8 @@ void main() { }); test('Rolling files', () async { - var output = AdvancedFileOutput( - path: dir.path, + var output = MemoryAdvancedFileOutput( + path: dirName, maxFileSizeKB: 1, ); await output.init(); @@ -100,8 +125,7 @@ void main() { output.output(event2); await output.destroy(); - final files = dir.listSync(); - + final files = output.file.parent.listSync(); expect( files, (hasLength(3)), @@ -109,8 +133,8 @@ void main() { }); test('Rolling files with rotated files deletion', () async { - var output = AdvancedFileOutput( - path: dir.path, + var output = MemoryAdvancedFileOutput( + path: dirName, maxFileSizeKB: 1, maxRotatedFilesCount: 1, ); @@ -120,34 +144,25 @@ void main() { output.output(event0); await output.destroy(); - // TODO Find out why test is so flaky with durations <1000ms - // Give the OS a chance to flush to the file system (should reduce flakiness) - await Future.delayed(const Duration(milliseconds: 1000)); - // Start again to roll files on init without waiting for timer tick await output.init(); final event1 = OutputEvent(LogEvent(Level.fatal, ""), ["2" * 1500]); output.output(event1); await output.destroy(); - await Future.delayed(const Duration(milliseconds: 1000)); - // And again for another roll await output.init(); final event2 = OutputEvent(LogEvent(Level.fatal, ""), ["3" * 1500]); output.output(event2); await output.destroy(); - await Future.delayed(const Duration(milliseconds: 1000)); - - final files = dir.listSync(); - + final latestFile = output.file; + final files = latestFile.parent.listSync(); // Expect only 2 files: the "latest" that is the current log file // and only one rotated file. The first created file should be deleted. expect(files, hasLength(2)); - final latestFile = File('${dir.path}/latest.log'); - final rotatedFile = dir - .listSync() + + final rotatedFile = files .whereType() .firstWhere((file) => file.path != latestFile.path); expect(await latestFile.readAsString(), contains("3")); @@ -158,8 +173,8 @@ void main() { const fileHeader = "TEST-HEADER"; const fileFooter = "TEST-FOOTER"; - var output = AdvancedFileOutput( - path: dir.path, + var output = MemoryAdvancedFileOutput( + path: dirName, maxFileSizeKB: 1, maxRotatedFilesCount: 1, fileHeader: fileHeader, @@ -172,23 +187,23 @@ void main() { output.output(event0); await output.destroy(); - // Give the OS a chance to flush to the file system (should reduce flakiness) - await Future.delayed(const Duration(milliseconds: 1000)); - - final latestFile = File('${dir.path}/latest.log'); + final latestFile = output.file; expect(await latestFile.readAsString(), startsWith(fileHeader)); expect(await latestFile.readAsString(), endsWith("$fileFooter\n")); }); test('Rolling files with custom file sorter', () async { - var output = AdvancedFileOutput( - path: dir.path, + int fileNameCounter = 0; + var output = MemoryAdvancedFileOutput( + path: dirName, maxFileSizeKB: 1, maxRotatedFilesCount: 1, // Define a custom file sorter that sorts files by their length // (strange behavior for testing purposes) from the longest to // the shortest: the longest file should be deleted first. fileSorter: (a, b) => b.lengthSync().compareTo(a.lengthSync()), + // The default uses date time until milliseconds and sometimes this test is faster and would re-use the same name multiple times. + fileNameFormatter: (timestamp) => "${fileNameCounter++}.log", ); await output.init(); @@ -203,23 +218,19 @@ void main() { output.output(event1); await output.destroy(); - // Give the OS a chance to flush to the file system (should reduce flakiness) - await Future.delayed(const Duration(milliseconds: 50)); - // And again for another roll await output.init(); final event2 = OutputEvent(LogEvent(Level.fatal, ""), ["3" * 1500]); output.output(event2); await output.destroy(); - final files = dir.listSync(); - + final latestFile = output.file; + final files = latestFile.parent.listSync(); // Expect only 2 files: the "latest" that is the current log file // and only one rotated file (the shortest one). expect(files, hasLength(2)); - final latestFile = File('${dir.path}/latest.log'); - final rotatedFile = dir - .listSync() + + final rotatedFile = files .whereType() .firstWhere((file) => file.path != latestFile.path); expect(await latestFile.readAsString(), contains("3")); @@ -227,7 +238,7 @@ void main() { }); test('Flush temporary buffer on destroy', () async { - var output = AdvancedFileOutput(path: dir.path); + var output = MemoryAdvancedFileOutput(path: dirName); await output.init(); final event0 = OutputEvent(LogEvent(Level.info, ""), ["Last event"]); @@ -238,8 +249,7 @@ void main() { await output.destroy(); - final logFile = File('${dir.path}/latest.log'); - var content = await logFile.readAsString(); + var content = await output.file.readAsString(); expect( content, allOf( diff --git a/test/outputs/file_output_test.dart b/test/outputs/file_output_test.dart index 74caf11..02ebb32 100644 --- a/test/outputs/file_output_test.dart +++ b/test/outputs/file_output_test.dart @@ -1,16 +1,16 @@ -import 'dart:io'; - +import 'package:file/memory.dart'; import 'package:logger/logger.dart'; import 'package:test/test.dart'; +var memory = MemoryFileSystem(); + void main() { - var file = File("${Directory.systemTemp.path}/dart_logger_test.log"); - setUp(() async { - await file.create(recursive: true); - }); + final file = memory.file("dart_logger_test.log"); - tearDown(() async { - await file.delete(); + tearDown(() { + if (file.existsSync()) { + file.deleteSync(); + } }); test('Real file read and write', () async {