structured_log_dio 0.1.0-dev.3
structured_log_dio: ^0.1.0-dev.3 copied to clipboard
StructuredLogDioInterceptor — a dio Interceptor that logs every request, response and failure as structured_log entries, with sensitive headers and query parameters redacted.
structured_log_dio #
Читать на русском.
Logs every request a dio client sends and
how it ended — response, error, timeout, cancellation — as
structured_log entries.
StructuredLogDioInterceptor is an ordinary dio Interceptor: add it to
dio.interceptors, and every call reports through the sinks you have
already configured — the console, a file, the in-app log viewer
(structured_log_flutter), or a structured_log_server via
structured_log_remote_sync.
Features #
- One line to install —
dio.interceptors.add(StructuredLogDioInterceptor()) - Request and outcome, paired —
http_request_idties a call's two entries together; the outcome carriesstatus_codeandduration_ms - Levels by status — 2xx/3xx at
debug, 4xx atwarning, 5xx and failures without a response aterror, cancellations atdebug; each adjustable or off - Secrets stay out by default — headers and bodies are not logged
unless asked for;
Authorization, cookies and API-key headers are redacted even then, and so are password- and token-like fields in a body; token-like query parameters and URL user info are always redacted - Its own category — every entry carries
category: 'http', for aLogSinkto route and the log viewer to filter on - Never breaks a call — a describer or filter that throws costs the entry, not the request
Installation #
Published on pub.dev as a pre-release (0.1.0-dev.2):
dependencies:
dio: ^5.4.0
structured_log: ^0.3.0
structured_log_dio: ^0.1.0-dev.2
Breaking change after 0.1.0-dev.2: with a body turned on, a string body
whose content type is neither JSON nor a form is now written only by its
size, <N chars>, instead of as it is. logUnrecognizedBodies: true brings
the old behaviour back. See Keeping secrets out of the log.
Quick Start #
import 'package:dio/dio.dart';
import 'package:structured_log/structured_log.dart';
import 'package:structured_log_dio/structured_log_dio.dart';
Future<void> main() async {
StructlogConfiguration.configure(
sinks: [LogSink(name: 'console', output: coloredConsoleOutput)],
);
final dio = Dio(BaseOptions(baseUrl: 'https://api.example.com'))
..interceptors.add(StructuredLogDioInterceptor());
await dio.get('/items', queryParameters: {'access_token': 's3cr3t'});
}
DEBUG: http_request {"category":"http","http_request_id":1,"method":"GET","url":"https://api.example.com/items?access_token=REDACTED"}
DEBUG: http_response {"category":"http","http_request_id":1,"method":"GET","url":"https://api.example.com/items?access_token=REDACTED","status_code":200,"duration_ms":59}
example/main.dart runs offline against a server it
starts itself (dart run example/main.dart).
Add the interceptor last. Interceptors run in order, so the last one logs the request that actually leaves — with the headers the others added — and sees each response before any of them can turn it into something else.
What gets logged #
| Entry | When | Fields |
|---|---|---|
http_request |
the request is about to be sent | http_request_id, method, url; request_headers, request_body if enabled |
http_response |
a response arrived and validateStatus accepted it |
the above, status_code, duration_ms; response_headers, response_body if enabled |
http_error |
anything else: a rejected status, a timeout, a refused connection, a cancellation | the above, status_code if there was a response, error_type (the DioExceptionType), error |
The level of an outcome follows its status code wherever it arrives from,
so a 404 is a warning whether validateStatus accepted it or not. error
is left out for badResponse: dio's message for it only restates the
status code.
http_request_id counts calls per interceptor instance, starting at 1.
Keeping secrets out of the log #
-
Headers and bodies are off by default.
logHeaders,logRequestBodyandlogResponseBodyturn them on. -
With headers on, the values of
defaultRedactedHeaders—authorization,proxy-authorization,cookie,set-cookie,x-api-key— becomeREDACTED. PassredactedHeadersto change the set (lower-case names; compared case-insensitively). -
Query parameters named in
defaultRedactedQueryParameters—access_token,refresh_token,id_token,token,api_key,apikey,password,client_secret— are always redacted; passredactedQueryParametersto change the set. User info in the URL (https://user:pass@host) is always dropped. -
Bodies, once on, are redacted before they are written. Fields named in
redactedBodyFieldsbecomeREDACTED, at any depth and regardless of case. The default,defaultRedactedBodyFields, isstructured_log's owndefaultSensitiveKeys—password,token,access_token,client_secret,api_key,authorizationand their spelling variants — so a body and a log entry are redacted by one list. What gets redacted depends on the body:- a map or a list — redacted as it is;
- a string whose content type is JSON (
application/json,*+json) or a form (application/x-www-form-urlencoded) — parsed, redacted and written back; one that does not parse is written as<unparseable body>, since what cannot be parsed cannot be redacted; - a string of any other type — only its length,
<N chars>: there is no knowing where a secret sits in text of unknown shape.logUnrecognizedBodies: truewrites such strings as they are.
The result is then JSON-encoded and cut to 1000 characters. A
describeBodyof your own receives the body already redacted; returningnullfrom it leaves the field out. To redact more than the defaults, spread them into your own set — passing a set replaces the defaults rather than adding to them:
StructuredLogDioInterceptor(
logRequestBody: true,
logResponseBody: true,
redactedBodyFields: {...defaultRedactedBodyFields, 'otp', 'pin'},
);
Configuration #
StructuredLogDioInterceptor(
// Which outcomes are logged, and at what level; null turns one off.
levels: const HttpLogLevels(request: null, clientError: LogLevel.info),
// Leave health checks out — both of a call's entries.
filter: (options) => options.path != '/health',
category: 'network',
);
Raw bytes and streams are summarised rather than dumped
(<42 bytes>, <stream>, <FormData: 2 fields, 0 files>).
API Reference #
| Symbol | Description |
|---|---|
StructuredLogDioInterceptor({logger, loggerName, category, levels, logHeaders, logRequestBody, logResponseBody, redactedHeaders, redactedQueryParameters, describeBody, filter, redactedBodyFields, logUnrecognizedBodies}) |
The interceptor. Without logger it calls getLogger(loggerName) (dio) on every entry, so a later StructlogConfiguration.configure reaches it too. category: null binds no category. |
HttpLogLevels({request, success, clientError, serverError, failure, cancel}) |
A LogLevel? per outcome; null turns it off. |
HttpBodyDescriber |
Object? Function(Object? body) — turns a body, already redacted, into an entry value; null omits the field. |
describeHttpBody(body) |
The default describer: strings as they are, maps and lists JSON-encoded, bytes/streams/FormData summarised, cut to defaultHttpBodyMaxLength (1000) characters. |
redactedBodyFields |
Body field names whose values become REDACTED, case-insensitively, at any depth. Defaults to defaultRedactedBodyFields; a set passed here replaces it. |
logUnrecognizedBodies |
Whether a string body that is neither JSON nor a form is written as it is. false by default: it is written as <N chars>. |
defaultRedactedHeaders, defaultRedactedQueryParameters, defaultRedactedBodyFields, redactedValue |
The default redaction sets and the value (REDACTED) that replaces what they match. defaultRedactedBodyFields is structured_log's defaultSensitiveKeys. |
Related packages #
The rest of the structured_log family:
Core
structured_log— structured JSON logging with context binding, processors and multi-sink routing
In-app log viewer
structured_log_flutter— headless viewer core:LogBufferandLogViewerControllerstructured_log_material— Material 3 log viewerstructured_log_fluent— Fluent UI (WinUI-style) log viewerstructured_log_cupertino— Cupertino (iOS-style) log viewer
Shipping logs to a server
structured_log_remote_sync— batching, retrying sink for a self-hostedstructured_log_server
Integrations
structured_log_bloc—BlocObserverforbloc/flutter_blocstructured_log_http_client—package:httpclient wrapper that logs HTTP callsstructured_log_go_router— logsgo_routernavigationstructured_log_cherrypick— observer for thecherrypickDI container
License #
See LICENSE.