logd
A modular hierarchical logger for Dart and Flutter. Build structured logs, control output destinations, and keep overhead minimal.
Why logd?
- Hierarchical configuration – Loggers are named with dot‑separated paths (
app.network.http). Settings propagate from parents to children unless overridden. - Zero‑boilerplate – Simple
Logger.get('app')gives a fully‑configured logger. - Performance‑first – Lazy resolution, aggressive caching, and optional inheritance freezing keep the cost of a disabled logger essentially zero.
- Flexible output – Choose between console, file, network, HTML, or any custom sink; format logs as text, structured JSON, HTML, Markdown or LLM‑optimized TOON.
- Layout Sovereignty – A centralized engine guarantees structural integrity (e.g., perfect boxes) across all terminal widths.
- Platform‑agnostic styling – Decouple visual intent from representation using the semantic
LogThemesystem.
Getting Started
Add logd to your project:
dependencies:
logd: ^latest_version
Then run:
dart pub get # or flutter pub get
Quick Example
import 'package:logd/logd.dart';
void main() {
final logger = Logger.get('app');
logger.info('Application started');
logger.debug('Debug message');
logger.warning('Low disk space');
logger.error('Connection failed',
error: exception,
stackTrace: stack,
);
}
Typical console output
[app][INFO] 2025-01-23 05:30:12.456
--example/main.dart:12 (main)
----Application started
Tip: Use
Logger.configureto set global log‑levels, handlers, or timestamps.logduses Deep Equality to ensure that re-configuring with identical values results in zero performance overhead.
Core Concepts
Hierarchical Loggers
Loggers inherit configuration from their ancestors.
// Configure the entire app
Logger.configure('app', logLevel: LogLevel.warning);
// Override a subsystem
Logger.configure('app.network', logLevel: LogLevel.debug);
// Create a logger deep in the tree
final uiLogger = Logger.get('app.ui.button'); // inherits WARNING
final httpLogger = Logger.get('app.network.http'); // inherits DEBUG
Log levels
| Level | Description |
|---|---|
trace |
Diagnostic noise |
debug |
Developer debugging |
info |
Informational |
warning |
Potential issue |
error |
Failure |
Advanced Features
Custom Handlers
Create complex pipelines of formatters and sinks:
final jsonHandler = Handler(
formatter: const JsonFormatter(
metadata: {LogMetadata.timestamp, LogMetadata.logger},
),
sink: FileSink(
'logs/app.log',
fileRotation: TimeRotation(
interval: Duration(days: 1),
timestamp: Timestamp(formatter: 'yyyy-MM-dd'),
backupCount: 7,
compress: true,
),
),
filters: [LevelFilter(LogLevel.info)],
);
Logger.configure('app', handlers: [jsonHandler]);
Result: JSON logs written to logs/app.log, rotated daily, keeping 7 compressed backups.
Note
Modern formatters (v0.6.1+) automatically include mandatory data like level, message, error, and stackTrace. The metadata parameter is used only for additional context like timestamps or logger names.
Atomic multi‑line logs
Prevent interleaving in concurrent environments:
final buffer = logger.infoBuffer;
buffer?.writeln('=== User Session ===');
buffer?.writeln('User ID: ${user.id}');
buffer?.writeln('Login time: ${DateTime.now()}');
buffer?.writeln('IP: ${request.ip}');
buffer?.sink(); // writes atomically
Multiple Outputs
You can either use multiple handlers:
final consoleHandler = Handler(
formatter: const StructuredFormatter(),
decorators: const [
BoxDecorator(),
StyleDecorator(),
SuffixDecorator(
label: '[v1.0.2]',
align: ture,
),
],
sink: const ConsoleSink(),
lineLength: 80,
);
final fileHandler = Handler(
formatter: const PlainFormatter(),
sink: FileSink('logs/app.log'),
);
Logger.configure('global', handlers: [consoleHandler, fileHandler]);
Or use a multi-sink in a handler:
final multiSinkHandler = Handler(
formatter: PlainFormatter(),
sink: MultiSink(sinks: [
ConsoleSinK(),
FileSink('logs/app.log'),
],
),
);
Filtering
Control which logs reach which handlers:
// Level-based filtering
final errorHandler = Handler(
formatter: JsonFormatter(),
sink: FileSink('logs/errors.log'),
filters: [LevelFilter(LogLevel.error)], // Errors only
);
// Regex-based filtering (exclude sensitive data)
final publicHandler = Handler(
formatter: PlainFormatter(),
sink: FileSink('logs/public.log'),
filters: [
RegexFilter(r'password|secret|token', exclude: true),
],
);
Timezone & Timestamp
final timestamp = Timestamp(
formatter: 'yyyy-MM-dd HH:mm:ss.SSS Z',
timezone: Timezone.named('America/New_York'),
);
Logger.configure('app', timestamp: timestamp);
File Rotation
| Strategy | Example |
|---|---|
| Size | FileSink('logs/app.log', fileRotation: SizeRotation(maxSize: '10 MB', backupCount: 5, compress: true)) |
| Time | FileSink('logs/app.log', fileRotation: TimeRotation(interval: Duration(hours: 1), timestamp: Timestamp(formatter: 'yyyy-MM-dd_HH'), backupCount: 24)) |
Performance Tuning
Logger.get('app').freezeInheritance(); // snapshot config, eliminate runtime look‑ups
Use Cases
Development Console
Logger.configure('global', handlers: [
const Handler(
formatter: StructuredFormatter(),
decorators: [
HierarchyDepthPrefixDecorator(indent: '│ '),
BoxDecorator(borderStyle: BorderStyle.rounded),
StyleDecorator(theme: LogTheme(colorScheme: LogColorScheme.darkScheme)),
],
sink: ConsoleSink(),
lineLength: 80,
),
]);
Production JSON
Logger.configure('global', handlers: [
Handler(
formatter: JsonFormatter(),
sink: FileSink('logs/production.log'),
),
]);
LLM-Native Logging (TOON)
Optimize logs for consumption by AI agents by using the Token-Oriented Object Notation:
Logger.configure('ai.agent', handlers: [
Handler(
formatter: const ToonFormatter(
arrayName: 'context',
metadata: {LogMetadata.timestamp},
),
sink: FileSink('logs/ai_feed.toon'),
),
]);
Result: A highly token-efficient, flat format that LLMs can parse with minimal overhead. The header is emitted only when the configuration changes.
Network Logging
Ship logs to remote servers with built-in resilience:
const httpSink = HttpSink(
url: 'https://logs.api.com',
batchSize: 50,
flushInterval: Duration(seconds: 10),
dropPolicy: DropPolicy.discardOldest,
);
Logger.configure('app', handlers: [
Handler(formatter: JsonFormatter(), sink: httpSink),
]);
Supported sinks: HttpSink (batching & retries), SocketSink (real-time streaming).
// For real-time streaming to a WebSocket server:
const socketSink = SocketSink(
url: 'wss://monitor.example.com/logs',
);
Microservice Logging
Logger.configure('api', handlers: [
Handler(formatter: JsonFormatter(), sink: FileSink('logs/api.log')),
]);
Logger.configure('database', handlers: [
Handler(formatter: JsonFormatter(), sink: FileSink('logs/db.log')),
]);
Logger.configure('auth', handlers: [
Handler(
formatter: JsonFormatter(),
sink: FileSink('logs/security.log'),
filters: [LevelFilter(LogLevel.warning)],
),
]);
Flutter integration
Capture framework errors and async errors:
void main() {
Logger.attachToFlutterErrors(); // listens to all uncaught Flutter errors
runZonedGuarded(
() => runApp(MyApp()),
(error, stack) {
Logger.get('app.crash').error(
'Uncaught error',
error: error,
stackTrace: stack,
);
},
);
}
Documentation
- Documentation Index - Overview and navigation
- Logger Philosophy - Design principles and rationale
- Logger Architecture - Implementation details
- Handler Guide - Pipeline and sink customization
- Migration Guide - Upgrading from legacy components
- Decorator Composition - Execution priority and flow
- Time Module - Timestamp and timezone handling
- Roadmap - Planned features and vision
Contributing
- Report bugs or suggest features via GitHub Issues.
- Share ideas in Discussions.
All contributions should follow the guidelines in CONTRIBUTING.md and, for docs, docs/CONTRIBUTING_DOCS.md.
License
This project is licensed under the BSD 3-Clause License. See the LICENSE file for details.
Support
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Package page: logd on pub.dev