Skip to content
Merged
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
22 changes: 14 additions & 8 deletions lib/src/outputs/advanced_file_output.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
Expand All @@ -115,7 +118,7 @@ class AdvancedFileOutput extends LogOutput {
final Comparator<File> _fileSorter;
final Duration _fileUpdateDuration;

final File _file;
late final File _file;
IOSink? _sink;
Timer? _bufferFlushTimer;
Timer? _targetFileUpdater;
Expand All @@ -142,14 +145,18 @@ class AdvancedFileOutput extends LogOutput {
return a.lastModifiedSync().compareTo(b.lastModifiedSync());
}

@protected
File createFile(String path) {
return File(path);
}

@override
Future<void> 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(
Expand Down Expand Up @@ -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<File>()
// Filter out the latest file
Expand Down
7 changes: 7 additions & 0 deletions lib/src/outputs/advanced_file_output_stub.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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.");
Expand Down
4 changes: 4 additions & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
124 changes: 67 additions & 57 deletions test/outputs/advanced_file_output_test.dart
Original file line number Diff line number Diff line change
@@ -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,
);
Expand All @@ -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(
Expand All @@ -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();
Expand All @@ -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(
Expand All @@ -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();
Expand All @@ -100,17 +125,16 @@ void main() {
output.output(event2);
await output.destroy();

final files = dir.listSync();

final files = output.file.parent.listSync();
expect(
files,
(hasLength(3)),
);
});

test('Rolling files with rotated files deletion', () async {
var output = AdvancedFileOutput(
path: dir.path,
var output = MemoryAdvancedFileOutput(
path: dirName,
maxFileSizeKB: 1,
maxRotatedFilesCount: 1,
);
Expand All @@ -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<File>()
.firstWhere((file) => file.path != latestFile.path);
expect(await latestFile.readAsString(), contains("3"));
Expand All @@ -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,
Expand All @@ -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();
Expand All @@ -203,31 +218,27 @@ 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<File>()
.firstWhere((file) => file.path != latestFile.path);
expect(await latestFile.readAsString(), contains("3"));
expect(await rotatedFile.readAsString(), contains("1"));
});

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"]);
Expand All @@ -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(
Expand Down
16 changes: 8 additions & 8 deletions test/outputs/file_output_test.dart
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down