flutter_logging_service 0.2.0
flutter_logging_service: ^0.2.0 copied to clipboard
A comprehensive logging service for Flutter apps with file persistence, log aggregation, crash tracking, sessions, masking, and isolate-backed writes.
Siglat
flutter_logging_service
Production-minded logging for Flutter — persistent files, crash capture,
sessions, masking, live streaming, and an in-app viewer.
Install · Quick start · Features · Config · Example · العربية
The name siglat comes from Arabic سجلات (sijillāt): records / registers / logs — plural of سجل (sijill).
Why #
Most Flutter apps start with print / debugPrint, then need:
- logs that survive process death
- a separate crash trail
- PII redaction before anything leaves the device
- a way to view / export / attach logs to a bug report
Siglat covers that path without pulling in a separate console package. Console output is built in (ANSI-colored); remote sinks are left to your app. File I/O runs off the UI isolate.
Package name on disk: flutter_logging_service · Repo: Zyzto/Siglat
Features at a glance #
| Area | What you get |
|---|---|
| Files | Rotating main log + dedicated crash file, size + age retention |
| Console | Internal ANSI printer; muted in release by default |
| APIs | Log, Loggable mixin, or LoggingService directly |
| Sessions | sid= on file lines without breaking [LEVEL] markers |
| Privacy | Opt-in default masks + custom MaskingRules |
| Live | LoggingService.stream, ring buffer, LogViewer widget |
| Export | Text dump, zip, keyword search, GitHub issue body helper |
| Perf | Optional isolate writer, write queue, backpressure |
Platforms: Android, iOS, Linux, macOS, Windows (full). Web: console + memory buffer (no file isolate).
Install #
dependencies:
flutter_logging_service:
git:
url: https://github.com/Zyzto/Siglat.git
ref: main # prefer a tag (e.g. v0.2.0) once published
import 'package:flutter_logging_service/flutter_logging_service.dart';
Quick start #
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await LoggingService.init(
LoggingConfig(
appName: 'MyApp',
enableDefaultMasking: true,
sessionStartExtra: '1.2.3',
),
);
runApp(const MyApp());
}
Log.info('User signed in');
Log.warning('Slow network', error: e, stackTrace: st);
Log.severe('Uncaught failure', error: e, stackTrace: st);
await LoggingService.exportLogsZip();
Initialize before other services that log.
Ways to log #
Log — static helper #
Auto-detects *Service / *Dao / *Repository from the stack in debug/profile. In release, stack parsing is skipped (no component); use Loggable if you need a name.
Log.debug('cache miss');
Log.info('synced 12 rows');
Log.error('upload failed', error: e, stackTrace: st);
Loggable — mixin #
Component name from runtimeType (cheap in release).
class ExpenseRepository with Loggable {
Future<void> save() async {
logInfo('saving expense');
try {
// …
} catch (e, st) {
logError('save failed', error: e, stackTrace: st);
}
}
}
LoggingService — explicit component #
LoggingService.info('route restored', component: 'Nav');
LoggingService.severe(
'Flutter framework error',
component: 'CrashHandler',
error: details.exception,
stackTrace: details.stack,
);
Levels #
| Level | Typical use | Notes |
|---|---|---|
DEBUG |
Dev noise | Skipped entirely in release |
INFO |
Normal events | Default min level in release |
WARNING |
Recoverable issues | |
ERROR |
Handled failures | Never aggregated |
SEVERE |
Crashes / fatal | Also written to the crash file |
Configuration #
Only appName is required. Sensible defaults cover most apps.
await LoggingService.init(
LoggingConfig(
appName: 'MyApp',
// Files
logFileName: 'myapp.log',
crashLogFileName: 'myapp_crashes.log',
logDirectory: null, // → documents/logs
maxLogFileSize: 5 * 1024 * 1024,
maxLogFiles: 5,
maxTotalLogSize: 50 * 1024 * 1024,
logsRetention: const Duration(days: 14),
// Behavior
enableAggregation: true,
aggregationTimeout: const Duration(milliseconds: 100),
minLogLevel: null, // DEBUG in debug, INFO in release
bufferLimit: 2000,
startNewSession: true,
sessionStartExtra: '1.2.3',
enableDefaultMasking: false,
// Console
enableConsole: true,
enableConsoleInRelease: false,
// Perf (IO platforms)
useIsolateWriter: true,
writeQueueCapacity: 500,
perLevelQuotas: null,
quotaWindow: const Duration(seconds: 60),
),
);
| Flag | Default | Meaning |
|---|---|---|
enableConsole |
true |
ANSI console output |
enableConsoleInRelease |
false |
Allow console in release (info+ only; debug stays skipped) |
useIsolateWriter |
true |
File append/rotate/zip on a dedicated isolate |
enableDefaultMasking |
false |
Built-in email / bearer / JWT / password / api-key patterns |
startNewSession |
true |
New sid on each init |
Sessions #
final sid = LoggingService.startSession(extra: 'resumed');
// LoggingService.sessionId
File lines keep a stable shape for UI colorizers:
[2026-08-06T12:00:00.000Z] [INFO] [MyService] hello sid=s1_abc123
[LEVEL] is never replaced by the session id.
Masking #
Applied before console, stream, aggregation, and file write.
await LoggingService.init(
LoggingConfig(appName: 'MyApp', enableDefaultMasking: true),
);
LoggingService.addMaskingRule(
MaskingRule('super-secret', replacement: '[HIDDEN]'),
);
LoggingService.addMaskingRule(
MaskingRule(r'sk_live_[A-Za-z0-9]+', isRegExp: true, replacement: '[KEY]'),
);
formatLogsForGitHub and exports use masked content.
Live stream & LogViewer #
LoggingService.stream.listen((LogRecord r) {
// r.maskedMessage, r.levelName, r.component, r.sessionId, …
});
// In-app viewer (i18n-agnostic labels; override via LogViewerLabels)
Navigator.of(context).push(
MaterialPageRoute<void>(builder: (_) => const LogViewer()),
);
Web and memory-only mode feed the viewer from the ring buffer when files are unavailable.
Files, export, search #
await LoggingService.flush(); // aggregation + write queue
final text = await LoggingService.getLogContent(maxLines: 500);
final path = await LoggingService.exportLogs(); // .txt bundle
final zip = await LoggingService.exportLogsZip(type: ExportType.all);
final hits = await LoggingService.searchLogs(keywords: ['ERROR', 'timeout']);
await LoggingService.clearLogs();
await LoggingService.clearLogsBefore(const Duration(days: 7));
final issueBody = await LoggingService.formatLogsForGitHub(
'Steps to reproduce…',
);
getLogContent, searchLogs, and exports flush for you. If you read raw files yourself after heavy logging, call flush() first.
Console behavior #
- Format:
[appName] [LEVEL] message(ANSI: debug gray, info green, warning blue, error red) - Debug / profile: on when
enableConsoleis true - Release: off unless
enableConsoleInRelease: true enableConsoleInReleasedoes not bring backDEBUG(hard skip + default min level INFO)SEVERE→ file[SEVERE], console[ERROR](intentional parity with the old printer)
No easy_logger dependency — printing is internal.
Aggregation #
Similar method(params) messages (same component + level) are batched for the file. Unique messages still hit the console immediately. Errors and severe logs are never aggregated.
[INFO] [AGGREGATED] fetch() called 12 times (id: 1, 2, 3 …)
Isolate writer & backpressure #
On IO platforms with useIsolateWriter: true (default), append / rotate / clear / zip run on a dedicated isolate so the UI isolate only enqueues work.
When the write queue is saturated:
debug/infomay be droppederror/severeare never dropped
Optional perLevelQuotas + quotaWindow add soft rate limits.
Lifecycle #
LoggingService.setMinLogLevel(LogLevel.warning);
LoggingService.setAggregationEnabled(false);
await LoggingService.flush();
await LoggingService.dispose(); // then init() may be called again
Testing #
setUp(() async {
await LoggingService.init(
const LoggingConfig(
appName: 'Test',
startNewSession: false,
enableAggregation: false,
enableConsole: false,
),
store: MemoryLogFileStore(),
);
});
tearDown(() async {
await LoggingService.dispose();
});
LoggingService.disableFileLogging() also forces the memory store. Prefer injecting MemoryLogFileStore for deterministic tests.
Example #
cd example
flutter pub get
flutter run
The example covers sessions, masking, zip export, and the live LogViewer.
License #
MPL-2.0 — weak copyleft, commercial use allowed. Modified package files stay under MPL; your app can remain closed-source.