Custom sink
Any log destination can be integrated by implementing the LogSink interface.
Interface
abstract class LogSink {
Future<void> write(LogModel event);
}
Example: file sink
import 'dart:convert';
import 'dart:io';
import 'package:structured_logger/structured_logger.dart';
class FileSink extends LogSink {
FileSink(this.file);
final File file;
Future<void> write(LogModel event) async {
final line = json.encode(event.toMap());
await file.writeAsString('$line\n', mode: FileMode.append);
}
}
Usage:
final logger = StructureLogger();
logger.addSink(FileSink(File('/tmp/app.log')));
await logger.log(
'Sync completed in {ms}ms',
level: LogLevel.info,
data: {'ms': 340},
);
Example: level filter sink
class LevelFilterSink extends LogSink {
LevelFilterSink(this.delegate, {required this.minLevel});
final LogSink delegate;
final LogLevel minLevel;
static const _order = {
LogLevel.verbose: 0,
LogLevel.debug: 1,
LogLevel.info: 2,
LogLevel.warning: 3,
LogLevel.error: 4,
};
Future<void> write(LogModel event) async {
final eventLevel = LogLevel.values.firstWhere(
(l) => l.sValue == event.level,
orElse: () => LogLevel.debug,
);
if (_order[eventLevel]! >= _order[minLevel]!) {
await delegate.write(event);
}
}
}
Best practices
- Keep
write()fast — heavy work (HTTP, disk) can block thelog()caller. Consider a background queue for high volume. - Do not throw unhandled exceptions — sink failures should not crash the app. Catch errors internally.
- Use
LogModel.toMap()— consistent CLEF serialization. - Implement dispose when needed — close connections, streams, or clients when done (like
SinkSeq.close()).
Register and remove
final sink = FileSink(logFile);
logger.addSink(sink);
// ... later
logger.removeSink(sink);