Skip to main content

logd 0.10.1 copy "logd: ^0.10.1" to clipboard
logd: ^0.10.1 copied to clipboard

Modular hierarchical logging for Dart and Flutter. Features modular pipeline processing, O(1) configuration resolution, and low‑overhead performance.

logd   pub version pub points BSD-3-Clause

logd Hero

A structured, hierarchical logging engine for Dart and Flutter.

Most Dart logging libraries handle the simple case well. logd handles the rest: multiple isolates, structured context propagation, background I/O offloading, and a full output pipeline — from styled ANSI terminals to a live browser dashboard — without ever breaking the simple case.


Quick Start #

dependencies:
  logd: ^latest_version
import 'package:logd/logd.dart';

void main() {
  final logger = Logger.get('app');
  logger.info('Server started');
  logger.warning('Disk space low', context: {'freeGb': 1.2});
  logger.error('Connection failed', error: e, stackTrace: st);
}

That's it for most use cases. Everything below is opt-in.


Engineered for Real-World Systems #

Most Dart logging libraries treat logging as simple string formatting in a terminal box. logd treats logging as application infrastructure.

Why developers choose logd #

  • Never drop a frame on disk or network I/O
    Logging to disk or remote servers shouldn't hitch your 120Hz Flutter UI or stall your event loop. Add .async() to any handler to offload formatting, serialization, and I/O to a background worker isolate in ~15µs.

  • Trace requests across async boundaries — zero boilerplate
    Stop threading requestId, userId, or tenantId through dozens of function signatures. LogContext.run() binds ambient context once, automatically propagating it through every nested await, microtask, and concurrent branch.

  • Cut LLM prompt token costs by 30–50%
    Traditional JSON logs bloat AI context windows with repetitive keys. With native TOON (Tab-Oriented Object Notation from package:logd_toon), log telemetry is emitted with a compact schema header and tab-delimited rows — purpose-built for LLM agent workflows and DuckDB ingestion.

  • Full-lifecycle HTTP inspection & credential redaction
    Capture API requests, monotonic response times, executable cURL commands, and failure diagnostics with automatic SOC2/GDPR credential redaction and ambient trace ID forwarding using package:logd_dio.

  • One log call, any destination simultaneously
    Route the same log event to a styled ANSI console, an indexed WAL-mode SQLite database (package:logd_sqlite), a self-contained HTML report (package:logd_html), or stream it in real time to a live browser dashboard (package:logd_network).

  • Surgical noise control with O(1) hierarchy
    Target and quiet chatty subsystems (e.g., Logger.configurePattern('app.network.*', logLevel: LogLevel.warning)) with dot-separated inheritance, dynamic glob matching, and zero-allocation fast paths.

  • Zero silent drops with fail-safe fallback
    If a disk fills up or a network sink throws, logd's internal fallback redirects critical logs before they can be lost — without ever crashing your host application.


Pre-Wired Handlers #

Ready-to-use handlers cover common destinations out of the box (with specialized formats, storage, networking, HTTP interception, & telemetry provided by satellite packages like logd_dio, logd_toon, logd_html, logd_markdown, logd_sqlite, and logd_network). No pipeline wiring required:

// Styled terminal output (dark or light theme)
ConsoleHandler(theme: const DarkTheme())

// Structured JSON to file (pretty or compact)
JsonFileHandler('logs/api.json', pretty: true)

// Self-contained HTML report (from package:logd_html)
HtmlFileHandler('logs/session.html')

// Token-efficient format for LLM / AI agent consumption (from package:logd_toon)
ToonFileHandler('logs/telemetry.toon')

// Plain text to file
PlainFileHandler('logs/app.log')

// GitHub-Flavored Markdown for CI summaries (from package:logd_markdown)
MarkdownFileHandler('logs/ci.md')

// In-memory ring buffer for tests and debug panels
MemoryHandler(capacity: 200)

// Indexed SQLite database storage (from package:logd_sqlite)
SqliteHandler('logs/app.db')

// Live browser dashboard via HTTP + WebSocket server (from package:logd_network)
HttpDashboardHandler(port: 8080)

Add .async() to any output-bound handler to offload its pipeline to a background isolate:

Logger.configure('app', handlers: [
  ConsoleHandler.async(),
  JsonFileHandler.async('logs/production.json'),
  HtmlFileHandler.async('logs/report.html'),
  SqliteHandler.async('logs/app.db'),
  HttpDashboardHandler.async(port: 8080),
]);

Tip

MemoryHandler intentionally stays synchronous to provide direct, non-blocking heap access to .entries on the caller thread. For web observability, HttpDashboardHandler.async() is available via package:logd_network.


Core Concepts #

Log Levels #

Level Description
trace Verbose diagnostic noise
debug Developer debugging
info Informational milestones
warning Potential issues
error Failures

Hierarchical Logger Tree #

Loggers are named with dot-separated paths. Configuration propagates from parents to children unless overridden — the same model as Python's logging or Java's log4j:

// Set the baseline for the whole app
Logger.configure('app', logLevel: LogLevel.warning);

// Override a noisy subsystem
Logger.configure('app.network', logLevel: LogLevel.debug);

// Deep loggers inherit from the nearest configured ancestor
final httpLogger = Logger.get('app.network.http'); // → DEBUG
final uiLogger   = Logger.get('app.ui.button');    // → WARNING

Bulk Configuration #

Configure multiple loggers atomically — validated and cache-invalidated in a single pass:

Logger.configureMultiple({
  'global':      const LoggerConfig(logLevel: LogLevel.warning),
  'app.network': const LoggerConfig(logLevel: LogLevel.debug),
  'app.ui':      const LoggerConfig(enabled: false),
});
  • Atomic Validation: the entire map is validated before any updates are written.
  • Single-Pass Invalidation: cache is invalidated in one optimized pass for all changed loggers and descendants.

Pattern-Based Configuration #

Match loggers dynamically with glob patterns:

Logger.configurePattern('*.database', logLevel: LogLevel.debug);
Logger.configurePattern('vendor.*',   enabled: false);
Logger.configurePattern('app.services.*', logLevel: LogLevel.info);

Ambient Structured Context (MDC) #

Bind context once. It flows automatically through every await, sync call, and microtask — no threading required:

Future<void> handleCheckout(String requestId, int userId) async {
  await LogContext.run({'requestId': requestId, 'userId': userId}, () async {
    logger.info('Validating cart');       // → {requestId: req-9812, userId: 42}
    await chargePaymentGateway();         // downstream async calls inherit context
    logger.info('Order completed');       // → {requestId: req-9812, userId: 42}
  });
}

Future<void> chargePaymentGateway() async {
  // No context parameter. No logger parameter. Zero boilerplate.
  Logger.get('app.payment').info('Connecting to Stripe');
  // Output includes: {requestId: req-9812, userId: 42}
}
  • Scope nesting: inner LogContext.run scopes inherit outer keys; conflicting keys override without mutation.
  • Call-site overrides: logger.info('msg', context: {'key': 'val'}) overrides specific keys for one entry.
  • Concurrent isolation: parallel tasks (Future.wait) maintain separate contexts with zero race conditions.
  • Zero-cost fast path: when no context is present, dispatch performs 0 heap allocations.

Output Formats #

Format Handler / Formatter Best for
Styled ANSI ConsoleHandler Development terminal
Structured JSON JsonFileHandler / JsonFormatter Log aggregators (Loki, ELK)
HTML report HtmlFileHandler / HtmlEncoder (from logd_html) Shareable session logs
Markdown MarkdownFileHandler (from logd_markdown) CI/CD job summaries
TOON ToonFileHandler / ToonFormatter (from logd_toon) LLM / AI agent consumption
Plain text PlainFileHandler / PlainFormatter Simple file logs
SQLite logd_sqlite Queryable persistent storage

TOON: Logs Optimized for LLMs #

Token-Oriented Object Notation emits the column schema once, then tab-delimited rows — 30–50% fewer tokens than JSON for the same data (provided by package:logd_toon):

import 'package:logd/logd.dart';
import 'package:logd_toon/logd_toon.dart';

Logger.configure('ai.agent', handlers: [
  ToonFileHandler('logs/agent.toon'),
]);

The strict dialect adds parser headers for direct ingestion into DuckDB, Loki, or awk:

Handler(
  formatter: const ToonFormatter(
    dialect: ToonDialect.strict,
    sortKeys: true,  // deterministic key ordering
    maxDepth: 3,     // bounded map recursion
  ),
  sink: FileSink('logs/pipeline.toon'),
)

When slicing a TOON file for an LLM sliding-window prompt:

final schema = ToonEncoder.extractPreamble(bytes) ?? '';
final window = sliceLines(bytes, last: 50);
final prompt = '$schema\n${window.join('\n')}';

Advanced Features #

Custom Pipeline Handlers #

When pre-wired handlers aren't enough, compose your own:

final handler = Handler(
  formatter: const JsonFormatter(
    metadata: {LogMetadata.timestamp, LogMetadata.logger},
  ),
  decorators: const [StyleDecorator(DarkTheme())],
  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: [handler]);

Note

Modern formatters automatically include level, message, error, and stackTrace. The metadata parameter controls optional context like timestamps or logger names.

Multiple Outputs #

Attach multiple handlers to route the same logs to different destinations:

Logger.configure('global', handlers: [
  Handler(
    formatter: const StructuredFormatter(),
    decorators: const [
      BoxDecorator(borderStyle: BorderStyle.rounded),
      StyleDecorator(DarkTheme()),
    ],
    sink: const ConsoleSink(lineLength: 80),
  ),
  Handler(
    formatter: const PlainFormatter(),
    sink: FileSink('logs/app.log'),
  ),
]);

Or fan out a single formatter to multiple sinks:

Handler(
  formatter: const PlainFormatter(),
  sink: MultiSink(sinks: [
    ConsoleSink(),
    FileSink('logs/app.log'),
  ]),
)

Atomic Multi-Line Logs #

Prevent line 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(); // flushed as a single atomic LogEntry

Filtering #

// Level filter — errors only
Handler(filters: [LevelFilter(LogLevel.error)], ...)

// Regex filter — exclude sensitive fields
Handler(filters: [RegexFilter(r'password|secret|token', exclude: true)], ...)

// Context filter — match on MDC key presence or value
Handler(filters: [ContextFilter(key: 'tenantId', value: 'acme')], ...)

Timestamp & Timezone #

final timestamp = Timestamp(
  formatter: 'yyyy-MM-dd HH:mm:ss.SSS Z',
  timezone: Timezone.named('America/New_York'),
);

Logger.configure('app', timestamp: timestamp);

For high-frequency or daily aggregate logs, bypass sub-second calculations entirely with date-only formatting:

Logger.configure('app.audit', timestamp: Timestamp.dateOnly('yyyy-MM-dd'));

File Rotation #

// Size-based
FileSink('logs/app.log', fileRotation: SizeRotation(
  maxSize: '10 MB', backupCount: 5, compress: true,
))

// Time-based
FileSink('logs/app.log', fileRotation: TimeRotation(
  interval: Duration(hours: 1),
  timestamp: Timestamp(formatter: 'yyyy-MM-dd_HH'),
  backupCount: 24,
))

HTTP Network Interception (logd_dio) #

Monitor, sanitize, and debug Dio HTTP traffic with monotonic timing, cURL output, and automatic token redaction using package:logd_dio:

import 'package:dio/dio.dart';
import 'package:logd_dio/logd_dio.dart';

final dio = Dio();
dio.interceptors.add(
  LogdDioInterceptor(
    const LogdDioOptions(
      logCurl: true,              // Executable cURL generation
      maxBodyLength: 512,         // Safe body truncation to protect memory
      attachAmbientTraceId: true, // Propagate ambient LogContext requestId
    ),
  ),
);

Network Logging (logd_network) #

Ship logs to remote servers with built-in resilience using package:logd_network:

import 'package:logd/logd.dart';
import 'package:logd_network/logd_network.dart';

// HTTP batching with retry and drop policy
final httpSink = HttpSink(
  url: 'https://logs.api.com',
  batchSize: 50,
  flushInterval: const Duration(seconds: 10),
  dropPolicy: DropPolicy.discardOldest,
);

// Real-time WebSocket streaming
final socketSink = SocketSink(
  url: 'wss://monitor.example.com/logs',
);

Logger.configure('app', handlers: [
  Handler(formatter: const JsonFormatter(), sink: httpSink),
]);

SQLite Persistence (logd_sqlite) #

Store structured logs locally with WAL-mode concurrency and rich query engine using package:logd_sqlite:

import 'package:logd/logd.dart';
import 'package:logd_sqlite/logd_sqlite.dart';

final sqliteHandler = SqliteHandler(
  dbPath: 'app_logs.db',
  maxEntries: 10000,
  maxAge: const Duration(days: 7),
  batchSize: 50,
  flushInterval: const Duration(seconds: 2),
  walMode: true,
);

Logger.configure('app', handlers: [sqliteHandler]);

// Query stored logs & level breakdown
final errors = sqliteHandler.queryLogs(
  minLevel: LogLevel.warning,
  search: 'Payment',
  limit: 50,
);
final counts = sqliteHandler.fetchLevelCounts();

Performance #

Hot-Path Dispatch Bypass #

Capturing caller origin (file, line, method) requires a StackTrace.current VM call on every log entry. When you don't need it, turn it off:

Logger.configure('app.analytics', includeOrigin: false);

Result: ~5x dispatch throughput gain (~2.38µs vs ~11.76µs per entry). Explicit stackTrace: parameters and stackMethodCount overrides are still respected.

Execution Engines #

Engine Allocation Platform When to use
StandardEngine GC heap All Default — always correct
ArenaEngine LIFO pool VM / Native Complex logs, many decorators
NativeEngine C heap via FFI VM / Native Narrow-width wrapping, 1.5x speedup

Inheritance Freezing #

Eliminate hierarchy tree-walks on hot paths by baking resolved configuration into descendant loggers:

// Bake current resolved config into all descendants (O(1) lookup after)
Logger.get('app').freezeInheritance();

// Re-snapshot after ancestry changes, preserving explicit overrides
Logger.get('app').freezeInheritance(force: true);

// Restore dynamic resolution for specific fields only
Logger.get('app').unfreezeInheritance(
  fields: {'logLevel'},
  includeSelf: false, // descendants only
);

Inheritance Control & Diagnostics #

// Visualize the live logger hierarchy with effective values
Logger.printHierarchy();
final tree = Logger.formatHierarchy();

// Full JSON export — effective values, frozen state, ghost-node detection
final snapshot = Logger.exportHierarchy();

// Reset a namespace subtree (useful in tests)
Logger.reset('app.ui'); // resets 'app.ui' and its descendants
Logger.reset();         // full registry reset

// Hierarchy safety limit (warns at >10 levels by default)
Logger.maxHierarchyDepth = 12;

Isolate Configuration Transport #

Export and import the full logger registry across isolate boundaries:

// Primary isolate
final config = Logger.exportConfig();

// Worker isolate
Logger.importConfig(config);

Custom formatters and sinks are mapped via LoggerSerializationRegistry.register.


Observability & Metrics #

// Cache efficiency, handler failures, buffer allocations and leak warnings
print('Cache hits:    ${LoggerMetrics.cacheHits}');
print('Buffer leaks:  ${LoggerMetrics.bufferLeaks}');
print('Handler fails: ${LoggerMetrics.handlerFailures}');

final metricsJson = LoggerMetrics.toJson();

Graceful Fallback & Degradation #

If all configured handlers throw, logd automatically falls back to console output — critical logs are never silently lost. Customize or disable this behavior:

Logger.fallbackHandler = (entry, error, stackTrace) {
  stderr.writeln('ALERT: all handlers failed for: ${entry.message}');
};

Integration Recipes #

1. Flutter Mobile (Zero UI Jank & Crash Resilience) #

Offload disk I/O to a background worker isolate so 120Hz animations never drop frames, while catching uncaught framework and platform errors:

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:logd/logd.dart';
import 'package:path_provider/path_provider.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  final dir = await getApplicationSupportDirectory();

  Logger.configure('app',
    logLevel: kReleaseMode ? LogLevel.info : LogLevel.debug,
    handlers: [
      if (kDebugMode) ConsoleHandler(theme: const DarkTheme()),
      // Background worker isolate — disk writes never hitch the UI thread:
      JsonFileHandler.async('${dir.path}/app_logs.json'),
    ],
  );

  // Catch framework widget errors and unhandled async microtasks:
  FlutterError.onError = (details) => Logger.get('app.crash').error(
    details.exceptionAsString(),
    error: details.exception,
    stackTrace: details.stack,
  );
  PlatformDispatcher.instance.onError = (error, stack) {
    Logger.get('app.crash').error('Unhandled error', error: error, stackTrace: stack);
    return true; // Prevent app crash
  };

  runApp(const MyApp());
}

2. HTTP Network Inspection (Dio Interceptor) #

Capture API requests, monotonic response times, executable cURL commands, and failure diagnostics using the official package:logd_dio:

import 'package:dio/dio.dart';
import 'package:logd_dio/logd_dio.dart';

final dio = Dio();
dio.interceptors.add(
  LogdDioInterceptor(
    const LogdDioOptions(
      logCurl: true,              // Generates copy-pasteable cURL commands
      maxBodyLength: 512,         // Truncates large payloads to prevent OOM
      attachAmbientTraceId: true, // Propagates LogContext requestId downstream
    ),
  ),
);

// Automatic SOC2/GDPR token redaction & monotonic timing
await dio.get('https://api.example.com/users');

Tip

Explore package:logd_dio and run its interactive gallery (dart run example/showcase_gallery.dart) for specialized recipes on SOC2 redaction, distributed tracing, and cancellation resilience.

3. Backend & Microservices (Ambient Tracing with Shelf) #

Bind a requestId once at the gateway; every downstream async function and database call inherits it automatically without passing parameters:

import 'package:logd/logd.dart';
import 'package:shelf/shelf.dart';

Middleware requestTracingMiddleware() {
  return (Handler innerHandler) => (Request request) async {
    final requestId = request.headers['x-request-id'] ?? 'req-${DateTime.now().microsecondsSinceEpoch}';

    // LogContext.run binds context to all downstream async calls & microtasks
    return LogContext.run({'requestId': requestId, 'path': request.url.path}, () async {
      Logger.get('api.http').info('${request.method} ${request.url.path}');

      final response = await innerHandler(request);

      // Echo requestId back in response headers for client-server correlation
      return response.change(headers: {'x-request-id': requestId});
    });
  };
}

// Deep in your services or database queries — zero boilerplate:
Future<void> queryDatabase() async {
  // Automatically tagged with {requestId: ..., path: ...}!
  Logger.get('api.db').info('SELECT * FROM users');
}

4. Microservice Subsystem Routing (Per-Domain Files & Filters) #

Route different application namespaces to separate dedicated files and log levels:

Logger.configure('api', handlers: [
  Handler(formatter: const JsonFormatter(), sink: FileSink('logs/api.log')),
]);

Logger.configure('database', handlers: [
  Handler(formatter: const JsonFormatter(), sink: FileSink('logs/db.log')),
]);

Logger.configure('auth', handlers: [
  Handler(
    formatter: const JsonFormatter(),
    sink: FileSink('logs/security.log'),
    filters: [LevelFilter(LogLevel.warning)], // Warnings and errors only
  ),
]);

5. AI Agents & LLM Telemetry (TOON) #

Save 30–50% prompt tokens vs JSON by logging agent actions in Tab-Oriented Object Notation (package:logd_toon):

import 'package:logd/logd.dart';
import 'package:logd_toon/logd_toon.dart';

void main() {
  Logger.configure('agent', handlers: [
    ToonFileHandler('logs/agent_session.toon'),
  ]);

  final logger = Logger.get('agent');
  logger.info('Tool executed', context: {
    'tool': 'web_search',
    'query': 'Dart 3.6',
    'durationMs': 142,
  });

  // Emits a compact column schema once, followed by tab-delimited rows.
  // Ideal for sliding-window LLM prompts and DuckDB log analysis!
}

Tip

Looking for offline SQLite diagnostics, live WebSocket dashboards, or CI Markdown reports? Check out the full Production Cookbook.


Testing #

import 'package:logd/testing.dart';

test('logs warning on failure', () async {
  final logger = TestLogger.get('app');
  final capture = CaptureSink();
  logger.configure(handlers: [
    Handler(formatter: const PlainFormatter(), sink: capture),
  ]);

  performAction(logger);

  expect(capture, hasLog(
    message: contains('action failed'),
    level: LogLevel.warning,
  ));
});

Satellite Packages #

Package Purpose
logd_dio Production-grade Dio HTTP logging interceptor with cURL generation & redaction
logd_toon Tab-Oriented Object Notation (TOON) formatter, encoder & file handler (30–50% LLM token reduction)
logd_html Self-contained HTML report encoder, stylesheets & file handler
logd_markdown GitHub-Flavored Markdown formatter & file handler for CI summaries
logd_sqlite WAL-mode SQLite persistence, auto-pruning, rich query engine
logd_network HTTP batching, WebSocket streaming, embedded viewer dashboard
logd_linters Custom lint rules for arena lifecycle and formatter purity

Documentation #

Document Description
API Reference Complete catalog of public classes, methods, and configurations
Production Cookbook Copy-paste recipes for Flutter, Dio, Shelf, SQLite & AI
Logger Philosophy 13 design principles with rationale
Handler Architecture Pipeline internals
Execution Engines Guide Standard, Arena, Native
TOON Specification Format spec + DuckDB ingestion
Dio Interceptor Guide Full-featured HTTP interceptor, cURL generation & redaction
Isolates Guide Cross-isolate configuration
Migration Guide Upgrading from legacy components
Architecture Decisions ADR-001 through ADR-008
Roadmap Planned features

Contributing #

License #

BSD 3-Clause. See LICENSE.

8
likes
160
points
611
downloads

Documentation

Documentation
API reference

Publisher

unverified uploader

Weekly Downloads

Modular hierarchical logging for Dart and Flutter. Features modular pipeline processing, O(1) configuration resolution, and low‑overhead performance.

Repository (GitHub)
View/report issues
Contributing

Topics

#logging #hierarchical #performance #structured-logging

License

BSD-3-Clause (license)

Dependencies

characters, ffi, matcher, meta, source_maps, source_span, timezone

More

Packages that depend on logd