Skip to content

Logging

import 'package:gisila/gisila.dart';

Pluggable sink for structured log events. The framework logs startup, shutdown, unhandled exceptions, authenticator failures and (with logRequests) one access-log line per request through AppConfig.logger.

abstract class Logger {
Logger();
LogLevel get level;
void log(LogEvent event);
void trace(String message, {Map<String, Object?>? fields});
void debug(String message, {Map<String, Object?>? fields});
void info(String message, {Map<String, Object?>? fields});
void warning(String message, {Map<String, Object?>? fields, Object? error, StackTrace? stackTrace});
void error(String message, {Map<String, Object?>? fields, Object? error, StackTrace? stackTrace});
void fatal(String message, {Map<String, Object?>? fields, Object? error, StackTrace? stackTrace});
}

When to use: log through app.config.logger from your own code so everything shares one format. Implement Logger to send logs somewhere other than the console (a file, OpenTelemetry, a vendor SDK) or to wrap another logger (redaction, fan-out).

Member Description
level Minimum level emitted. The convenience methods drop anything below it before building an event.
log(event) Emit one event. Implementations should also respect level.
trace … fatal Build a LogEvent with the current UTC time and call log. warning, error and fatal also take an error and stack trace.

Example

class RedactingLogger extends Logger {
RedactingLogger(this.inner);
final Logger inner;
static const _secret = {'password', 'token', 'authorization'};
@override
LogLevel get level => inner.level;
@override
void log(LogEvent e) => inner.log(LogEvent(
level: e.level,
message: e.message,
timestamp: e.timestamp,
requestId: e.requestId,
principalId: e.principalId,
error: e.error,
stackTrace: e.stackTrace,
fields: {
for (final f in e.fields.entries)
f.key: _secret.contains(f.key.toLowerCase()) ? '[redacted]' : f.value,
},
));
}
final app = GisilaApp(
config: AppConfig(logger: RedactingLogger(ConsoleLogger(format: LogFormat.json))),
);

The default logger: writes to stdout, and to stderr for warning and above.

class ConsoleLogger extends Logger {
ConsoleLogger({
LogLevel level = LogLevel.info,
LogFormat format = LogFormat.text,
void Function(String line)? stdoutSink,
void Function(String line)? stderrSink,
});
final LogLevel level;
final LogFormat format;
}

When to use: almost always, in text format for development and json in production where a collector (Loki, CloudWatch, the gisila panel’s log view) reads stdout. Pass stdoutSink/stderrSink to capture lines in tests.

Text format:

2026-10-31T09:12:44.120Z [INFO ] req=Xb3k9QpLm2Ty7vRa user=42 GET /orders -> 200 (12ms) method=GET path=/orders query= status=200 duration_ms=12 remote=127.0.0.1:50122

JSON format: the event’s fields, then ts, level, msg, and requestId, principalId, error, stack when present. The event’s own keys win over fields with the same name.

{"method":"GET","path":"/orders","query":"","status":200,"duration_ms":12,"remote":"127.0.0.1:50122","ts":"2026-10-31T09:12:44.120Z","level":"info","msg":"GET /orders -> 200 (12ms)","requestId":"Xb3k9QpLm2Ty7vRa","principalId":"42"}

One structured log record.

class LogEvent {
LogEvent({
required LogLevel level,
required String message,
required DateTime timestamp,
String? requestId,
String? principalId,
Object? error,
StackTrace? stackTrace,
Map<String, Object?>? fields,
});
}
Field Description
level, message, timestamp Severity, text, time (the framework uses UTC).
requestId, principalId Set by the access log; set them yourself when building events in a request.
error, stackTrace The failure, if any.
fields Arbitrary key/value pairs. The access log uses method, path, query, status, duration_ms, remote, and requestBody/responseBody when enabled.
app.config.logger.log(LogEvent(
level: LogLevel.info,
message: 'order placed',
timestamp: DateTime.now().toUtc(),
requestId: ctx.requestId,
principalId: ctx.principal?.id,
fields: {'orderId': order.id, 'total': order.total},
));
enum LogLevel {
trace(0), debug(1), info(2), warning(3), error(4), fatal(5), off(6);
final int priority;
bool operator >=(LogLevel other);
}

Severities from most to least verbose. off silences everything. >= compares priorities.

enum LogFormat { text, json }

Output format of ConsoleLogger.

See also: Structured logging guide, AppConfig logging fields.