Skip to main content

carapacik_dio_logger 2.0.0 copy "carapacik_dio_logger: ^2.0.0" to clipboard
carapacik_dio_logger: ^2.0.0 copied to clipboard

Dio logger with normal and compact modes, bounded redacted events, safe console output and cURL export.

carapacik_dio_logger #

A portable Dio interceptor with normal and compact modes, bounded redacted events, Unicode-safe console output, and optional POSIX cURL export.

dependencies:
  carapacik_dio_logger: ^2.0.0

Requires Dio ^5.11.0 (including HTTP QUERY support). No Flutter or filesystem dependency.

Request/response mode and body format #

import 'package:carapacik_dio_logger/carapacik_dio_logger.dart';
import 'package:dio/dio.dart';

final dio = Dio(BaseOptions(baseUrl: 'https://api.example.test'));
dio.interceptors.add(CarapacikDioLogger());

mode controls request/response details: normal prints blocks with enabled sections, while compact prints one completion summary per observed attempt. Compact summaries exclude headers, query parameters, request options and cURL sections.

Body output has independent controls:

  • requestBody, responseBody and errorBody enable the corresponding body.
  • compactBody: false (default) keeps readable multiline previews.
  • compactBody: true prints each enabled body on one line.

Every body preview is a separate event/message in either mode. It is never embedded in the request/response detail block or completion summary. An enabled cURL section may also contain its own redacted representation of the request body. compactBody does not enable bodies by itself. Disabled bodies and null data produce no body message; empty strings, maps and lists are still valid bodies.

mode Body enabled compactBody Output
normal No Either Request/response blocks only
normal Yes false Blocks + separate multiline bodies
normal Yes true Blocks + separate single-line bodies
compact No Either Completion summary only
compact Yes false Completion summary + separate multiline bodies
compact Yes true Completion summary + separate single-line bodies

Headers and bodies default to enabled in normal mode; bodies default to off in compact mode. cURL and stack traces are opt-in. Redaction applies only to explicitly configured fields and headers.

Compact summaries without bodies:

CarapacikDioLogger(
  mode: DioLogMode.compact,
  requestBody: false,
  responseBody: false,
  errorBody: false,
);
[HTTP] GET api.example.test/items → 200 124ms
[HTTP] POST api.example.test/items → 503 42ms badResponse
[HTTP] GET api.example.test/items → - 20001ms connectionTimeout

Compact summaries with a multiline body:

CarapacikDioLogger(
  mode: DioLogMode.compact,
  responseBody: true,
  compactBody: false,
);

The response and its body are emitted in order as separate messages. compactBody only changes the contents of the body message.

Message 1 — response:

[HTTP] GET api.example.test/items → 200 124ms

Message 2 — body (compactBody: false):

[HTTP] GET api.example.test/items → 200 124ms [responseBody]
{
  "ok": true
}

With compactBody: true, the body is still the next, separate message. Only its JSON formatting changes:

Message 1 — response:

[HTTP] GET api.example.test/items → 200 124ms

Message 2 — body (compactBody: true):

[HTTP] GET api.example.test/items → 200 124ms [responseBody] {"ok":true}

The opposite combination works too:

CarapacikDioLogger(
  mode: DioLogMode.normal,
  compactBody: true,
  requestBody: true,
  responseBody: true,
);

This prints normal detail blocks and separate single-line body messages. JSON compaction preserves whitespace inside string values. Plain-text line feeds and tabs become \n and \t; other control characters remain escaped, and omission markers remain visible.

errorBody inherits responseBody unless explicitly set. response: false disables accepted response events and their bodies; error: false disables failure events and their bodies. Body flags work in either mode.

Interceptor constructor #

All constructor parameters are shown below with their defaults. A null requestBody or responseBody is resolved from mode; a null errorBody inherits the resolved responseBody. limits: null creates DioLogLimits().

CarapacikDioLogger(
  mode: DioLogMode.normal,
  compactBody: false,
  request: true,
  requestHeader: true,
  requestQueryParameters: true,
  requestBody: null, // normal: true; compact: false
  requestExtra: false,
  extraKeys: const {},
  response: true,
  responseHeader: true,
  responseBody: null, // normal: true; compact: false
  error: true,
  errorBody: null, // inherits responseBody
  errorMessage: true,
  errorStackTrace: false,
  convertFormData: true,
  showCurl: false,
  showLog: true,
  showRedirects: false,
  showResponseMetadata: false,
  label: 'HTTP',
  context: null,
  redaction: const DioLogRedaction(),
  limits: null,
  filter: null,
  responseFilter: null,
  errorFilter: null,
  levelResolver: null,
  onLog: null,
  colorized: false,
  chunkSize: 1024,
  wrapWidth: null,
  logPrint: print,
);

Mode and request output #

Parameter Default What is printed
mode DioLogMode.normal normal: request and completion blocks with enabled detail sections. compact: completion line. Does not select body formatting.
compactBody false true: a separate single-line message for each enabled body. false: a separate multiline message. Independent of mode; does not enable bodies.
request true Normal Options section: response type, content type, timeouts, redirects and connection settings. false removes this section only; other request flags are independent. No effect on compact summaries.
requestHeader true Normal Request headers section and cURL -H arguments, with explicitly selected values redacted. No compact header section.
requestQueryParameters true Normal Query section and query in the displayed URL/cURL; false removes these query details. Compact endpoints always omit queries.
requestBody null → normal true, compact false Separate [requestBody] message in either mode; also controls the cURL payload. compactBody selects multiline or single-line format. No message for null data.
requestExtra false Normal Extra section when enabled with a nonempty extraKeys allowlist. No compact section.
extraKeys const {} Names allowed in Extra, e.g. {'operation'} prints {"operation":"create-item"} when that key exists. Other keys are omitted.

Response and error output #

Parameter Default What is printed
response true Accepted response completion and optional body. false suppresses both in either mode. Acceptance follows Dio's validateStatus, not a hard-coded status range.
responseHeader true Normal Response headers section for accepted responses and failures with a response. No compact header section.
responseBody null → normal true, compact false Separate [responseBody] message in either mode, if data is non-null. Format follows compactBody.
error true Failure completion and optional error body. false suppresses both in either mode.
errorBody null → resolved responseBody Separate [errorBody] message in either mode, formatted by compactBody. Requires a failure with non-null response data. Can be enabled independently of responseBody.
errorMessage true Normal Error section with the sanitized Dio message. Compact always keeps the failure type, e.g. connectionTimeout, without dumping the message.
errorStackTrace false Normal Stack trace section when enabled, bounded by string limits. No compact stack trace.
showRedirects false Normal Redirects section, e.g. ["302 GET https://api.example.test/items"], when the adapter provides redirects.
showResponseMetadata false Normal Response metadata section with httpVersion, declaredBytes, realUri and isRedirect. Compact text omits this section; events still carry available protocol and declared size fields.

Shared controls and cURL #

Parameter Default What is printed
convertFormData true With cURL body output enabled, includes multipart text via --form-string. false prints # Multipart payload omitted. instead. Body previews still show bounded multipart metadata; uploads are never read.
showCurl false Normal cURL (POSIX shell) section. No effect on compact text. Explicit toCurl() remains available in either mode.
showLog true false disables interceptor output, callbacks and timing entirely. It does not disable an explicit toCurl() call.
label 'HTTP' Prefix in every summary, e.g. label: 'API' prints [API] GET ...; normal block headings also use this label.
context null Application fields appended to every summary/body message, e.g. operation=create-item. Null adds nothing; values are bounded and optionally redacted.
redaction const DioLogRedaction() Empty hidden-key sets by default. Replaces configured secrets with [REDACTED], e.g. {"password":"[REDACTED]"}; custom URI/text sanitizers transform displayed values.
limits null → DioLogLimits() Bounds previews; omitted content gets markers such as … [truncated], [max depth] or [entries omitted]. See the limit values below.
filter null (request, phase) => false suppresses that phase before serialization. Body events have their own phases and are filtered separately.
responseFilter null Returning false suppresses an accepted response and its body; request events are unaffected.
errorFilter null Returning false suppresses a failure and its body; request events are unaffected.
levelResolver null → defaultLevel Changes completion event severity and console color, not summary wording. Request events remain debug. For example, returning DioLogLevel.warning colors completions yellow when colors are enabled.

Output destination and console layout #

Parameter Default What is printed
onLog null When supplied, receives DioLogEvent instead of built-in console output. Use DioLogFormatter for text or event.toJson() for storage. Raw event sections retain their readable previews; configure DioLogFormatter(compactBody: true) to minify bodies.
colorized false ANSI console colors: debug cyan, info green, warning yellow, error red. Does not affect onLog events.
chunkSize 1024 Maximum UTF-8 bytes per logPrint call, including ANSI (minimum 32). Long messages may be split into several calls; this does not truncate the logical event.
wrapWidth null Null adds no word wrapping. A value such as 80 wraps at spaces with indentation (minimum 20), including compact body lines. Long tokens are left to byte chunking.
logPrint print Receives formatted console chunks. For example, debugPrint uses Flutter's throttling. Not invoked when onLog is supplied.

Output examples #

Normal mode with request options, headers and query details disabled:

CarapacikDioLogger(
  request: false,
  requestHeader: false,
  requestQueryParameters: false,
  responseHeader: false,
);

For a POST body {'name': 'Example'} and a 201 response {'ok': true}, four separate messages are emitted:

Message 1 — request:

┏ HTTP REQUEST
[HTTP] POST api.example.test/items
URL: https://api.example.test/items
┗━━━━━━━━━━━━━━━━━━━━

Message 2 — request body:

[HTTP] POST api.example.test/items [requestBody]
{
  "name": "Example"
}

Message 3 — response:

┏ HTTP RESPONSE
[HTTP] POST api.example.test/items → 201 16ms
URL: https://api.example.test/items
┗━━━━━━━━━━━━━━━━━━━━

Message 4 — response body:

[HTTP] POST api.example.test/items → 201 16ms [responseBody]
{
  "ok": true
}

With normal defaults, Request headers, Options and Response headers sections are also included; Query is included if the URL has a query. Optional sections appear only when enabled and applicable data exists.

The same exchange with compact summaries, compact bodies and application context:

CarapacikDioLogger(
  mode: DioLogMode.compact,
  requestBody: true,
  responseBody: true,
  compactBody: true,
  context: (_) => {'operation': 'create-item'},
);

Request body message:

[HTTP] POST api.example.test/items operation=create-item [requestBody] {"name":"Example"}

Response message:

[HTTP] POST api.example.test/items → 201 16ms operation=create-item

Next message — response body:

[HTTP] POST api.example.test/items → 201 16ms operation=create-item [responseBody] {"ok":true}

In compact mode, an HTTP failure with errorBody: true and compactBody: true prints a completion message and a separate body message:

Error message:

[HTTP] GET api.example.test/items → 503 42ms badResponse

Next message — error body:

[HTTP] GET api.example.test/items → 503 42ms badResponse [errorBody] {"message":"Unavailable"}

Elapsed times are illustrative. Network failures with no response have no response body. showLog: false produces no output in either mode.

Application context and filtering #

The package does not assign special meaning to application headers. Add your own bounded fields through context:

CarapacikDioLogger(
  mode: DioLogMode.compact,
  context: (request) => {'operation': request.extra['operation'] ?? '-'},
  filter: (request, phase) => !request.path.endsWith('/health'),
  responseFilter: (response) => response.statusCode != 304,
  errorFilter: (error) => error.type != DioExceptionType.cancel,
);

Context is repeated on any body event. Extra data is not dumped by default: normal mode requires both requestExtra: true and an extraKeys allowlist. Filters run before body preview/serialization. Callbacks must be fast.

Default severity: request events and cancellations use debug; 5xx completions use error; other Dio failures and 4xx completions use warning; remaining completions use info. levelResolver can replace the completion policy, including for statuses accepted by validateStatus.

Redaction and limits #

Nothing is masked by default: hiddenHeaders and hiddenFields are empty. Authorization, Cookie, passwords and tokens remain visible when their sections are enabled. Choose exactly what to hide:

CarapacikDioLogger(
  redaction: const DioLogRedaction(
    hiddenHeaders: {'authorization', 'cookie', 'set-cookie'},
    hiddenFields: {'password', 'token', 'access_token'},
  ),
  limits: DioLogLimits(
    maxBodyBytes: 8192,
    maxStringLength: 1024,
    maxCollectionEntries: 30,
    maxDepth: 5,
    maxEndpointLength: 160,
  ),
);

Matching ignores case and separators. Masked values become [REDACTED]; network objects and the actual request are never changed. The same configured policy applies to request/response/error previews and cURL. There are no implicitly added hidden keys. Limits and control-character escaping still apply with empty hidden-key sets; compact mode still shows only its selected summary.

Use sanitizeUri for application-specific secrets in paths and sanitizeText for secrets in plain text/error messages. The callback receives complete input before escaping or whitespace folding. If the input exceeds its preview limit, the entire value is omitted without calling the callback; this avoids revealing a secret whose closing delimiter would be truncated. Callback output is escaped and bounded as usual. Arbitrary prose cannot be redacted by structured field names. URI credentials and fragments are retained in detailed URIs by default; remove them explicitly with sanitizeUri: (uri) => uri.replace(userInfo: '', fragment: ''). The sanitizer escapes control codes and bounds traversal before serialization. Cycles, unknown objects, binary payloads and streams get a short description; streams are never consumed and multipart uploads are never finalized for logging. Repeated multipart fields remain separate entries.

Console and file output #

logPrint defaults to Dart’s print function. In Flutter, pass logPrint: debugPrint to use Flutter's throttled output. Set showLog explicitly for your build policy; the package does not infer debug/release mode.

wrapWidth is an optional readability hint. chunkSize is a separate UTF-8 transport limit (default 1024, minimum 32 bytes), not a payload limit. Chunking preserves Unicode scalar values and each colored chunk owns its ANSI reset. The console/IDE may still wrap lines using its own width. A body is one logical event even when the console must split its transport output.

To deliver structured events instead of console output, provide onLog:

final formatter = DioLogFormatter(
  mode: DioLogMode.compact,
  compactBody: true,
);
final interceptor = CarapacikDioLogger(
  mode: DioLogMode.compact,
  onLog: (event) {
    final text = formatter.format(event); // no timestamp/level duplication
    // Forward text and event.level to your application's logger.
  },
);

onLog replaces built-in printing. Configure your own DioLogFormatter with the desired mode and compactBody. The interceptor’s mode determines which details are collected; its compactBody only affects built-in console formatting. Each event is a bounded snapshot containing the timestamp, phase, level, endpoint, status, elapsed time, optional response metadata, application context and sections. It does not retain Response or RequestOptions objects. jsonEncode(event.toJson()) produces a JSONL record with no ANSI or console wrapping. Use one record per event; do not store console chunks as records.

On platforms that support dart:io, an application can write events to an IOSink:

final sink = File('http.jsonl').openWrite(mode: FileMode.append);
final logger = CarapacikDioLogger(
  mode: DioLogMode.compact,
  onLog: (event) => sink.writeln(jsonEncode(event.toJson())),
);
// Observe sink.done errors in your application; on shutdown:
await sink.flush();
await sink.close();

The example requires dart:io and dart:convert imports in the application. Directory selection, disk errors, rotation, retention and shutdown belong to the output owner. The interceptor does not block requests on asynchronous outputs. Sync callback failures and asynchronous onLog failures are contained so logging cannot replace a network result.

cURL #

Enable showCurl: true in normal mode, or call logger.toCurl(requestOptions) to export without console wrapping or ANSI. Commands target POSIX shells, not PowerShell/cmd. Quoting handles single quotes, dollar signs and backticks. Request header/body/query flags also govern cURL. If redaction is configured, an export may need credentials restored before replay.

Multipart text uses --form-string. Upload files are explicitly omitted: MultipartFile.filename does not establish a local file path. Streams and payloads outside preview limits are omitted with a comment; the package does not emit a truncated command as if it were complete. HTTP methods are not restricted to GET/POST/PUT; QUERY and other methods may carry bodies.

Supported body types #

HTTP content types and Dart runtime types are different layers. This logger previews the data available to the interceptor and does not decode every MIME format itself. The following examples show body text. Every body has a separate message with a [requestBody], [responseBody] or [errorBody] prefix. compactBody controls its layout independently of request/response mode.

Examples containing [REDACTED] assume the corresponding hidden keys were configured.

Data observed by the interceptor compactBody: false compactBody: true / behavior
JSON Map or List, including nested values Indented, redacted JSON {"items":[1,2],"password":"[REDACTED]"}
JSON object/array supplied as a bounded String Parsed for field redaction and formatted as JSON {"ok":true}; oversized serialized strings are omitted before parsing
String: plain text, HTML, XML, CSV Bounded text with control characters escaped first line\nsecond line; spaces within text are preserved; no HTML/XML/CSV field parser
Finite num or bool JSON scalar 42, 3.14, true
null body No body event No separate body event; completion still prints
Empty map/list/string {}, [] or an empty payload {}, [] or a body marker with an empty payload
FormData Fields, file metadata and original counts One-line JSON; repeated field names remain separate entries
URL-encoded request supplied as a Map Redacted JSON representation of the map One-line JSON; cURL uses --data-urlencode
Pre-encoded URL-encoded String Plain text Plain text on one line; use sanitizeText for secrets in encoded strings
Uint8List <bytes: 3> Same summary; raw bytes are never dumped
List<int> response with ResponseType.bytes <bytes: 3> Same summary
Ordinary List<int> in a JSON request/response JSON array [1,2,3]; distinct from Uint8List
Upload Stream<List<int>> <runtimeType: not read> Same summary; no subscription, buffering or consumption
Streaming response ResponseBody <ResponseBody: not read> Same body summary; completion also includes streamReady
ByteBuffer from custom application code <runtimeType: not read> Same summary; not automatically treated as a supported raw Dio upload
Custom objects, including DTOs <runtimeType: not serialized> Same summary; the logger does not call arbitrary toString() or toJson()
Cyclic or oversized values Markers at configured traversal/output limits Same markers on one line

Dio exposes four response modes: json, plain, bytes and stream. JSON MIME recognition includes application/json, text/json and +json subtypes such as application/problem+json. A decoded problem-details response therefore uses the same Map preview as ordinary JSON. See Dio response modes and JSON transformation.

Images, PDFs, ZIP archives, protobuf and MessagePack are summarized as bytes when received with ResponseType.bytes; the logger does not parse these formats. SSE and NDJSON received as streams remain unread, so logging individual stream events belongs to the consumer. A complete NDJSON string uses the plain-text preview. Custom Dio transformers may return other objects; serialize DTOs before the logger if you want a structured preview.

For comparison, pretty_dio_logger's implementation formats response maps/lists, prints Uint8List values in chunks, and falls back to toString() for other types. It combines multipart entries into a map, which can replace repeated names. Its compact option flattens suitable nested values inside a boxed response; it is not a single completion-line mode. This package instead preserves repeated form fields, summarizes bytes/streams, bounds data before formatting and separates body messages from request/response output. Body compaction is an independent option.

Multipart and URL-encoded forms #

Body previews show the input observed by the interceptor, before Dio transforms it into wire bytes. The examples below use requestBody: true and redaction: const DioLogRedaction(hiddenFields: {'password', 'token'}). Each multiline example uses compactBody: false; each single-line example uses compactBody: true. Both layouts work with either request/response mode.

multipart/form-data #

final form = FormData()
  ..fields.addAll([
    const MapEntry('tag', 'one'),
    const MapEntry('tag', 'two'),
    const MapEntry('token', 'secret'),
  ])
  ..files.add(MapEntry(
    'upload',
    MultipartFile.fromBytes([1, 2, 3], filename: 'photo.png'),
  ));
await dio.post<dynamic>('/upload', data: form);

Separate body message with compactBody: false:

[HTTP] POST api.example.test/upload [requestBody]
{
  "fields": [
    {
      "tag": "one"
    },
    {
      "tag": "two"
    },
    {
      "token": "[REDACTED]"
    }
  ],
  "files": [
    {
      "upload": {
        "filename": "photo.png",
        "contentType": "image/png",
        "bytes": 3
      }
    }
  ],
  "fieldCount": 3,
  "fileCount": 1
}

Separate body message with compactBody: true:

[HTTP] POST api.example.test/upload [requestBody] {"fields":[{"tag":"one"},{"tag":"two"},{"token":"[REDACTED]"}],"files":[{"upload":{"filename":"photo.png","contentType":"image/png","bytes":3}}],"fieldCount":3,"fileCount":1}

Repeated fields remain separate. Files show their name, content type and declared length, never their contents. The logger does not read or finalize uploads. Counts describe the original form even when preview limits omit some entries.

With showCurl: true, normal mode also includes this command (with request headers disabled here to focus on the payload):

# Upload files omitted: MultipartFile.filename is not a local path.
curl -i \
  -X 'POST' \
  --form-string 'tag=one' \
  --form-string 'tag=two' \
  --form-string 'token=[REDACTED]' \
  --url 'https://api.example.test/upload'

convertFormData: false omits multipart payloads from cURL, but keeps the body metadata preview. requestBody: false removes both the preview and cURL payload.

application/x-www-form-urlencoded #

await dio.post<dynamic>(
  '/token',
  data: {
    'name': 'Jane Doe',
    'tag': ['one', 'two'],
    'password': 'secret',
  },
  options: Options(contentType: Headers.formUrlEncodedContentType),
);

Separate body message with compactBody: false:

[HTTP] POST api.example.test/token [requestBody]
{
  "name": "Jane Doe",
  "tag": [
    "one",
    "two"
  ],
  "password": "[REDACTED]"
}

Separate body message with compactBody: true:

[HTTP] POST api.example.test/token [requestBody] {"name":"Jane Doe","tag":["one","two"],"password":"[REDACTED]"}

The map is shown as readable JSON, not as percent-encoded wire bytes. The cURL export uses --data-urlencode so cURL handles spaces and reserved characters, with repeated arguments for list values:

curl -i \
  -X 'POST' \
  --data-urlencode 'name=Jane Doe' \
  --data-urlencode 'tag=one' \
  --data-urlencode 'tag=two' \
  --data-urlencode 'password=[REDACTED]' \
  --url 'https://api.example.test/token'

This is a redacted representation, not a capture of Dio's encoded bytes or custom list-format/transformer behavior. Pass a map for field-based redaction; a pre-encoded string such as name=Jane+Doe&password=secret is plain text to the logger and requires a suitable sanitizeText callback to hide its secrets.

Timing and metadata #

Elapsed time is measured with a Stopwatch for each observed attempt. It covers the observed interceptor pipeline, not individual DNS, TLS, TTFB or server timings. Stream responses are labeled streamReady: the transfer and file write can complete or fail later. Stream consumption and download completion need instrumentation at their owning layer.

HTTP version and declared Content-Length are recorded when available. They depend on the adapter/response; Content-Length is not measured wire traffic. Normal mode can include redirect and response metadata sections through showRedirects and showResponseMetadata. Redirect and final-URI metadata depend on the adapter. All Dio error types, including transformTimeout, are preserved. Retry attempts remain separate; an earlier interceptor resolving/rejecting without forwarding can bypass this interceptor. Place the logger after middleware whose final request changes you want to see.

Tests #

dart format --set-exit-if-changed -o none .
dart analyze --fatal-infos
dart test
dart test --platform chrome
dart test --coverage-path=coverage/lcov.info --coverage-package='^carapacik_dio_logger$' --branch-coverage

The coverage report includes this package's library code, excluding dependencies.

11
likes
160
points
504
downloads

Documentation

API reference

Publisher

verified publishercarapacik.dev

Weekly Downloads

Dio logger with normal and compact modes, bounded redacted events, safe console output and cURL export.

Repository (GitHub)
View/report issues

Topics

#dio #http #network #logging

License

MIT (license)

Dependencies

dio

More

Packages that depend on carapacik_dio_logger