Structured logging
You’ll configure production-ready logging: JSON lines on stdout, an access log without bodies, your own log lines tied to the request id, and a custom logger that redacts secrets.
Prerequisites
Section titled “Prerequisites”- A gisila project.
1. Pick level and format
Section titled “1. Pick level and format”import 'dart:io';
import 'package:gisila/gisila.dart';
final prod = Platform.environment['APP_ENV'] == 'production';
final app = GisilaApp( config: AppConfig( logLevel: prod ? LogLevel.info : LogLevel.debug, logFormat: prod ? LogFormat.json : LogFormat.text, ),);ConsoleLogger writes info and below to stdout and warning and above to stderr. Container platforms (including the gisila panel) collect both.
2. Tune the access log
Section titled “2. Tune the access log”One line per request is on by default (logRequests: true), at info, with method, path, query, status, duration, remote address, request id and principal id. Bodies are off:
AppConfig( logRequests: true, logRequestBody: false, // never in production: passwords, tokens logResponseBody: false,)When enabled, bodies are truncated to 2048 characters; streams and server-sent events are never read.
3. Log from your code
Section titled “3. Log from your code”Use app.config.logger so your lines share the format:
class OrderService extends Service { Logger get _log => GisilaApp.maybeOf(ctx.request)!.config.logger;
Future<Order> place(OrderForm form) async { final order = await _repo.insert(form); _log.info('order placed', fields: { 'request_id': ctx.requestId, 'user_id': ctx.principal?.id, 'order_id': order.id, 'total_cents': order.totalCents, }); return order; }}The convenience methods (info, warning, …) don’t attach the request id on their own, so pass it as a field, or build a LogEvent with requestId: and call logger.log(...). Clients can send their own X-Request-Id (1 to 128 safe characters) to correlate across services; it is echoed back on the response.
Keep field values small and never log secrets or full request bodies.
4. Plug in a custom logger
Section titled “4. Plug in a custom logger”Implement Logger to send logs elsewhere or to transform them. This one redacts sensitive fields and forwards to the console:
class RedactingLogger extends Logger { RedactingLogger(this.inner); final Logger inner; static final _sensitive = RegExp(r'pass|token|secret|authorization', caseSensitive: false);
@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: _sensitive.hasMatch(f.key) ? '[redacted]' : f.value, }, ));}
final app = GisilaApp( config: AppConfig(logger: RedactingLogger(ConsoleLogger(format: LogFormat.json))),);When you pass logger, logLevel and logFormat are ignored; configure them on the logger you pass.
Verify it works
Section titled “Verify it works”APP_ENV=production dart run bin/server.dart &curl -s localhost:8080/orders -H 'x-request-id: demo-123' > /dev/null{"method":"GET","path":"/orders","query":"","status":401,"duration_ms":1,"remote":"127.0.0.1:53422","ts":"2026-10-31T09:00:00.000Z","level":"info","msg":"GET /orders -> 401 (1ms)","requestId":"demo-123"}In tests, capture lines with ConsoleLogger(stdoutSink: lines.add, stderrSink: lines.add).