structured_log 0.2.0-dev.0+1
structured_log: ^0.2.0-dev.0+1 copied to clipboard
Structured logging for Dart, inspired by Python's structlog. Log JSON with context binding.
example/main.dart
import 'dart:io';
import 'package:structured_log/structured_log.dart';
Future<void> main() async {
// Basic usage
final log = getLogger();
log.info('user_login', context: {'user_id': 42, 'ip': '127.0.0.1'});
// With bound context
final boundLog = getLogger().bind({'request_id': 'abc-123'});
boundLog.info('processing_request');
boundLog.warning('slow_query', context: {'duration_ms': 1500});
// Chaining bind
final userLog = boundLog.bind({'user_id': 42});
userLog.info('user_action', context: {'action': 'purchase'});
userLog.error('payment_failed', context: {'error': 'timeout'});
// File output
StructlogConfiguration.configure(
output: fileOutput('logs/app.log'),
);
final fileLog = getLogger();
fileLog.info('logged to file');
fileLog.error('error in file');
print('Logs written to logs/app.log');
print(File('logs/app.log').readAsStringSync());
// Rotating file output
StructlogConfiguration.configure(
output: rotatingFileOutput('logs/rotating.log', maxSizeBytes: 1024),
);
final rotatingLog = getLogger();
for (var i = 0; i < 100; i++) {
rotatingLog.info('iteration', context: {'i': i});
}
print('Rotating logs:');
print('logs/rotating.log exists: ${File('logs/rotating.log').existsSync()}');
print(
'logs/rotating.log.0 exists: ${File('logs/rotating.log.0').existsSync()}');
// Typed correlation fields
StructlogConfiguration.reset();
final correlatedLog = getLogger().withCorrelation(
sessionId: 's-14',
requestId: 'r-42',
connectionGeneration: 8,
);
correlatedLog.info('processing_request');
// Child scope: inherits sessionId/requestId, adds toolCallId
final toolLog = correlatedLog.withCorrelation(toolCallId: 'tc-3');
toolLog.info('tool_invoked');
// Multi-sink routing: console gets everything, a dedicated file only
// gets entries tagged with the 'protocol' category.
StructlogConfiguration.configure(sinks: [
LogSink(name: 'console', output: coloredConsoleOutput),
LogSink(
name: 'protocol',
output: fileOutput('logs/protocol.log'),
categories: {'protocol'},
),
]);
final routedLog = getLogger();
routedLog.info('app_event'); // console only
routedLog.debug('raw_frame',
context: {'category': 'protocol'}); // console + protocol.log
// Toggle the protocol sink off at runtime without rebuilding the logger.
StructlogConfiguration.setSinkEnabled('protocol', enabled: false);
routedLog
.debug('raw_frame_2', context: {'category': 'protocol'}); // console only
// trace() sits below the default minLevel (debug), so a sink needs to
// opt in explicitly to receive it -- no category tagging required.
StructlogConfiguration.configure(sinks: [
LogSink(name: 'console', output: coloredConsoleOutput),
LogSink(
name: 'trace',
output: fileOutput('logs/trace.log'),
minLevel: LogLevel.trace,
),
]);
final traceLog = getLogger();
traceLog.info('handled_request'); // console + trace.log
traceLog.trace('raw_frame', context: {'bytes': 128}); // trace.log only
// Async file output: does not block the calling isolate. Keep a
// reference to the instance so you can await flushed before exit.
final asyncOutput = AsyncFileOutput('logs/async.log');
StructlogConfiguration.configure(output: asyncOutput);
final asyncLog = getLogger();
asyncLog.info('async_event_1');
asyncLog.info('async_event_2');
await asyncOutput.flushed;
print('logs/async.log after flush:');
print(File('logs/async.log').readAsStringSync());
}