x_logger

x_logger

pub package license: MIT platforms

Configurable logging for Flutter: named loggers, levels, pretty / JSON / colored output, filtering, and async export of the log history to a file your users can share โ€” all configured once and reused across your app.

09:12:03.114 ๐Ÿ’ก [INFO]  (QuranReader) ayah rendered {surah=2, ayah=255}
09:12:03.330 โš ๏ธ [WARN]  (AudioPlayer) buffering {bufferMs=1200}
09:12:03.440 โ›” [ERROR] (AudioPlayer) playback stopped | error: Bad state

Contents

Features

  • Named instances โ€” one logger per part of your app (QuranReader, AudioPlayer, Database, โ€ฆ).
  • Levels โ€” debug, info, warning, error.
  • Pluggable style (printers) โ€” SimplePrinter, PrettyPrinter (boxed + emoji), or CallbackPrinter for a fully bespoke format.
  • Color โ€” ANSI 256-color output with a per-level scheme; auto-enabled when the terminal supports it, off for files.
  • Customizable header โ€” toggle timestamp / level / name / fields, choose a timestamp format (iso, clock, dateTime, or your own), UTC or local.
  • Filtering โ€” by level, name, or any predicate; combine with allOf/anyOf; apply globally and/or per output.
  • Structured fields โ€” attach {key: value} context to any call.
  • Multiple destinations โ€” ConsoleOutput, async FileOutput, StreamOutput (for in-app log views), MemoryOutput (ring buffer), MultiOutput (fan-out). Each owns its own printer + filter.
  • Export API โ€” get the backing File or read the whole history as a string.

Architecture

A record flows through three independent, swappable stages:

log()  โ†’  LogFilter (global)  โ†’  for each LogOutput:  LogFilter (local) โ†’ LogPrinter โ†’ write
  • LogFilter decides whether to log.
  • LogPrinter decides how it looks (style, color).
  • LogOutput decides where it goes.

Install

dependencies:
  x_logger: ^0.2.0
flutter pub get
import 'package:x_logger/x_logger.dart';

Quick start

final log = XLogger(name: 'QuranReader');

log.debug('Opening surah');
log.info('Ayah rendered', fields: {'surah': 2, 'ayah': 255});
log.warning('Slow font load');
log.error('Render failed', error: e, stackTrace: s);
2026-05-24T09:12:03.114 [INFO] (QuranReader) Ayah rendered {surah=2, ayah=255}

Zero config (no customization)

Don't want to configure anything? XLogger.standard() wires up a colorized console and an on-device file in one line, so export works immediately:

final log = XLogger.standard(name: 'QuranReader');

log.info('ready');
log.error('failed', error: e, stackTrace: s);

// Already exportable โ€” the file lives in the app documents directory.
final file = await log.getLogFile();
final text = await log.exportLogsAsString();

XLogger.standard accepts optional name, fileName, and minLevel; that's all you need to touch.

Set the logger up one time at app startup, then create cheap child() loggers wherever you want to tag a subsystem. Children share the parent's config, filter, and outputs โ€” the same single file, console, and buffers. They cost almost nothing (just a name), so this does not duplicate I/O or memory.

// lib/logging.dart โ€” created once, reused everywhere.
final appLog = XLogger(
  name: 'WACYI',
  outputs: [ConsoleOutput(), FileOutput(fileName: 'app.log')],
);

// Anywhere in the app โ€” these reuse appLog's file & console:
final quranLog = appLog.child('QuranReader');
final audioLog = appLog.child('AudioPlayer');

quranLog.info('ayah rendered');     // (QuranReader) ...
audioLog.warning('buffering');      // (AudioPlayer) ... โ†’ same app.log

The label after child(...) shows up in (parentheses) so you can tell which part of the app wrote each line โ€” handy when you read the exported file.

Create the logger once (a top-level variable, a DI singleton, etc.) and reuse it. The only thing to avoid is constructing a new XLogger/FileOutput repeatedly (e.g. inside build()), which would open redundant file handles.

If you don't care about per-subsystem labels at all, just use one logger everywhere and skip child() entirely.

Customizing the style

LoggerConfig drives the built-in printers:

final log = XLogger(
  name: 'Database',
  config: const LoggerConfig(
    showTimestamp: true,
    showLevel: true,
    showName: false,           // hide "(Database)"
    showFields: true,
    showEmoji: true,           // prepend ๐Ÿ› ๐Ÿ’ก โš ๏ธ โ›”
    useUtc: true,
    colorize: true,
    timeFormatter: TimeFormats.clock,   // 09:12:03.114
  ),
);

With showEmoji, each level is tagged with its glyph:

09:12:03.114 ๐Ÿ› [DEBUG] (Database) cache warmed
09:12:03.221 ๐Ÿ’ก [INFO]  (Database) row inserted
09:12:03.330 โš ๏ธ [WARN]  (Database) slow query
09:12:03.440 โ›” [ERROR] (Database) connection lost

Use the boxed PrettyPrinter on a watched console:

final log = XLogger(
  name: 'AudioPlayer',
  outputs: [ConsoleOutput(printer: const PrettyPrinter())],
);
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
โ”‚ โš ๏ธ WARN  (AudioPlayer)  09:12:03.114
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
โ”‚ Buffering
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

Or take full control of the format with CallbackPrinter:

ConsoleOutput(
  printer: CallbackPrinter(
    (e) => ['#${e.sequence} ${e.level.label.padRight(5)} ${e.message}'],
  ),
);

JSON output (pretty-printed)

Use JsonPrinter to render records as JSON โ€” great for structured logs and for inspecting payloads. It pretty-prints by default; pass pretty: false for a single compact line. Values that aren't JSON-encodable fall back to toString(), so it never throws.

final log = XLogger(
  name: 'QuranReader',
  outputs: [ConsoleOutput(printer: const JsonPrinter())],
);

log.info('search request', fields: {
  'query': 'mercy',
  'filters': {'surah': [1, 2, 18], 'exact': false},
});
{
  "time": "2026-05-24T09:12:03.114",
  "level": "INFO",
  "logger": "QuranReader",
  "seq": 0,
  "message": "search request",
  "fields": {
    "query": "mercy",
    "filters": {
      "surah": [1, 2, 18],
      "exact": false
    }
  }
}

Custom colors

const LoggerConfig(
  colorize: true,
  levelColors: {
    LogLevel.debug: AnsiColor.gray,
    LogLevel.info: AnsiColor.cyan,
    LogLevel.warning: AnsiColor.orange,
    LogLevel.error: AnsiColor.magenta,
  },
);

Filtering

// Global: only warnings and above.
XLogger(name: 'A', filter: const LevelFilter(LogLevel.warning));

// By predicate (inspect level, name, message, or fields).
PredicateFilter((e) => e.fields['important'] == true);

// By logger name.
const NameFilter(allow: {'QuranReader', 'AudioPlayer'});

// Combine.
LogFilter.allOf([
  const LevelFilter(LogLevel.info),
  NotFilter(const NameFilter(deny: {'Noisy'})),
]);

Filters also attach per output, so each sink can capture a different slice:

XLogger(
  name: 'QuranReader',
  filter: const LevelFilter(LogLevel.debug),   // file gets everything
  outputs: [
    FileOutput(fileName: 'app.log'),
    ConsoleOutput(filter: const LevelFilter(LogLevel.warning)), // console: warns+
  ],
);

Outputs

XLogger(
  name: 'QuranReader',
  outputs: [
    ConsoleOutput(),                       // colorized; errors โ†’ stderr
    FileOutput(fileName: 'app.log'),       // async, non-blocking
    StreamOutput(),                        // for an in-app log view
    MemoryOutput(capacity: 500),           // recent-lines ring buffer
  ],
);

Subscribe to a StreamOutput to render logs live in the UI:

log.stream?.listen((entry) {
  setState(() => visibleLines.addAll(entry.lines));
});

Writing to a file

By default FileOutput writes into the app's documents directory โ€” resolved automatically via path_provider on Android, iOS, macOS, Windows and Linux, no setup required:

FileOutput(fileName: 'agent.log'); // app documents dir, all platforms

Override the location when you need to โ€” a custom directory or an explicit path:

FileOutput(
  fileName: 'agent.log',
  directoryResolver: () async =>
      (await getApplicationSupportDirectory()).path,
);

FileOutput(filePath: '/some/writable/dir/agent.log');

Writes are buffered into an async IOSink, so logging never blocks the UI thread. Set flushEveryWrite: true for crash-safety at the cost of more I/O.

Exporting / sharing the log

await log.flush();                          // ensure buffered writes landed

final file = await log.getLogFile();        // the on-device log file
final history = await log.exportLogsAsString();
final recent = log.memoryDump;              // from a MemoryOutput, if attached

await log.dispose();                        // flush + close all outputs

To let the user share the file with anyone, hand its path to a share plugin:

final file = await log.getLogFile();
if (file != null) {
  await Share.shareXFiles([XFile(file.path)]); // package:share_plus
}

Example app

A runnable Flutter app lives in example/. It has a button for every level (๐Ÿ› Debug, ๐Ÿ’ก Info, โš ๏ธ Warning, โ›” Error) plus a JSON-payload button, a Text โ‡„ Pretty JSON toggle that re-renders the captured records live, and an Export & share button that writes the log file and opens the native share sheet โ€” so you can verify export on every platform.

cd example
flutter pub get

flutter run -d android     # Android device/emulator
flutter run -d ios         # iOS device/simulator
flutter run -d macos       # macOS desktop
flutter run -d windows     # Windows desktop
flutter run -d linux       # Linux desktop

Platform folders for all five targets are checked in. Export behavior:

Platform Export to file Native share sheet
Android โœ… โœ…
iOS โœ… โœ…
macOS โœ… โœ…
Windows โœ… โœ…
Linux โœ… falls back to showing the file path

On Linux, share_plus does not implement file sharing, so the example reports the on-device file path instead โ€” the export itself still succeeds.

Platform notes

  • This is a Flutter package. File / console output use dart:io, so they run on iOS, Android, and desktop โ€” not Flutter web (use ConsoleOutput only, or StreamOutput/MemoryOutput there).
  • FileOutput writes on-device; the user exports/shares the file from the app. In unit tests (no platform plugin), prefer an explicit filePath.

License

See LICENSE.

Libraries

x_logger
x_logger โ€” a configurable logger for tracking application workflows with customizable styles, color, filtering, and asynchronous export of log history to local files.