flog_dart 0.11.0 copy "flog_dart: ^0.11.0" to clipboard
flog_dart: ^0.11.0 copied to clipboard

Flutter companion for flog terminal log viewer. Structured logging, idempotent Dio instrumentation, Network Inspector, and mocks.

flog_dart #

Flutter companion for flog — a terminal-native log viewer + network inspector.

flog_dart runs a WebSocket server inside your Flutter app on port 9753 (or the first free port in [9753, 9762]). When you open the flog TUI on your dev machine, it connects to the app and streams logs, HTTP / SSE / WebSocket traffic, and synchronises mock rules.

Everything is compile-time-disabled in release builds — AOT tree-shakes the whole package out of the binary.

Installation #

flog_dart is debug-only. Put it under dev_dependencies to keep it out of release dependency resolution:

dev_dependencies:
  flog_dart: ^0.11.0

If you need to reference types from flog_dart in your own code (e.g. a subclass of FlogLogger), use a regular dependencies: entry; flogEnabled still tree-shakes the implementation out in release builds.

Requirements #

  • Dart >=3.3.0 <4.0.0
  • Flutter >=3.19.0
  • Dio >=5.10.0 <6.0.0

Dio 5.10.0 and 5.11.0 are exercised in independent compatibility fixtures. Dio 4 is no longer declared because current flog instrumentation uses Dio 5 APIs and the former >=4.0.0 claim was not backed by compatibility tests.

Quick start #

One line in main():

import 'package:flog_dart/flog_dart.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  Flog.init();
  runApp(MyApp());
}

Flog.init({int port = 9753}) is synchronous and zero-blocking. It:

  1. Starts FlogServer on the first free port in [port, port+9].
  2. Registers hooks for debugPrint, FlutterError.onError, and PlatformDispatcher.onError so every framework log and crash ends up in flog.
  3. Fetches app name / version / package name via package_info_plus in the background and publishes it to any connected TUI.

Logging #

final log = FlogLogger('Network');
log.i('-> GET /api/users');
log.e('Connection failed', error: e, stackTrace: st);

Shorthands: v / d / i / w / e. Full-word equivalents: verbose / debug / info / warning / error. Pass error: + stackTrace: on debug / warning / error when you have them.

Set FlogLogger.printToConsole = true to also echo every log to the Flutter debug console — handy when you're not attached to the flog TUI but still want to see what flog_dart would have sent.

Network inspector #

Instrument the Dio instance your application already owns:

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

final dio = Dio(BaseOptions(baseUrl: 'https://api.example.com'));
instrumentDio(dio);
final response = await dio.get('/users');

instrumentDio returns the same real Dio instance, so the complete API from the resolved Dio version remains available. It installs:

  1. FlogMockInterceptor — checks the mock rules synced from the flog TUI. If a rule matches, it resolves the request locally with the canned response; flog still sees it and tags it Mocked.
  2. FlogHttpInterceptor — records the request, response, error, headers, and bodies (subject to FlogHttpConfig).
  3. FlogTimingHttpClientAdapter - records response timing.
  4. Replay registration - re-executes selected requests through this Dio.

Installation is idempotent and enforces Mock -> HTTP logging -> business order. Repeated calls do not duplicate interceptors or wrap the adapter again. Configure httpClientAdapter before calling instrumentDio. If the application later replaces the adapter or moves interceptors, call instrumentDio(dio) again to restore the timing wrapper and ordering.

Dio 5.11 HTTP QUERY works through the native Dio API:

final response = await dio.query('/search',
  data: {'filter': 'active'},
  queryParameters: {'page': 2},
);

Configuration #

final dio = Dio(BaseOptions(baseUrl: 'https://api.example.com'));
instrumentDio(
  dio,
  config: FlogHttpConfig(
    includeRequestHeaders: true,
    includeResponseHeaders: true,
    includeRequestBody: true,
    includeResponseBody: true,
    maxBodySize: 10 * 1024 * 1024,
    filter: (options) => !options.path.startsWith('/health'), // exclude noisy endpoints
  ),
);

FlogDio migration #

The old constructor remains available as a deprecated extension-type facade:

final FlogDio legacy = FlogDio(baseUrl: 'https://api.example.com');
final Dio dio = legacy;

It retains constructor, Dio assignment, SSE, options, adapter, transformer, interceptors, and clone migration paths without hand-writing the Dio interface. See MIGRATION.md for runtime type semantics and SDK requirements.

SSE #

v0.8 splits the SSE subsystem into three composable StreamTransformers. Most users can ignore the transformers and call dio.sse on an instrumented real Dio (or on the deprecated FlogDio facade):

final sse = await dio.sse('/chat/completions',
  method: 'POST',
  data: {'model': 'gpt-4', 'messages': [...]},
);

// v0.8 — preferred: typed events
await for (final SseEvent e in sse.events) {
  if (e.data == '[DONE]') break;
  print('${e.event ?? "message"}[${e.id}]: ${e.data}');
}

SseEvent models a single W3C EventSource event: event (type, or null for the default message), data (multi-line data: lines joined with \n), id, retry (ms), and any preceding : comments.

v0.7 .stream is still supported #

SseResponse.stream is a Stream<String> of joined data: payloads with the OpenAI-style [DONE] terminator filtered out. It is annotated @Deprecated and will be removed in v1.0, but keeps working:

// ignore: deprecated_member_use
await for (final data in sse.stream) { print(data); }

.stream and .events share one subscription to the underlying byte stream — pick one; listening to both raises a StateError.

Custom pipelines (advanced) #

For full control, compose the transformers yourself. This lets you, for example, raise the default 1 MiB buffer cap or skip the reporter entirely:

import 'package:flog_dart/flog_dart.dart';

final events = responseBody.stream
    .cast<List<int>>()
    .transform(const SseByteDecoder(maxBufferBytes: 4 * 1024 * 1024))
    .transform(const SseLineDecoder())
    .transform(FlogSseReporter(url: url, method: 'POST'));

Or use FlogSseParser.wrap / FlogSseParser.wrapTyped — the v0.7 convenience shims, unchanged:

await for (final data in FlogSseParser.wrap(
  responseBody.stream,
  url: '/api/chat/completions',
  method: 'POST',
)) {
  final json = jsonDecode(data);
  // ...
}

WebSocket #

FlogWebSocket wraps web_socket_channel:

final ws = await FlogWebSocket.connect('wss://example.com/ws');
ws.send(jsonEncode({'type': 'hello'}));
ws.stream.listen((data) => print(data)); // broadcast stream (DART-006)
await ws.close();

Every send, every inbound frame, and the eventual close event go to flog. Binary frames are forwarded as "<binary: N bytes>" (see PROTOCOL §4.3).

Mocking #

Mock rules are authored in the flog TUI (Network tab → M to open the mock rules panel, or M on a selected request to fork one from it). Each rule has a URL pattern (substring match), optional method filter, status code, response body, optional delay, and enabled flag.

When you create or toggle a rule the TUI sends it to the running app over the WebSocket control channel ({"type":"mock_sync","rules":"…"}). FlogMockInterceptor updates its in-memory rules and begins intercepting matching requests. Matched requests are still logged by FlogHttpInterceptor and appear in the inspector tagged Mocked.

Matching semantics: substring on the full URL, first-match-wins, case-sensitive. Optional method filter applies when set.

Replay #

In the flog TUI, selecting a captured HTTP request in the Network tab and pressing r sends a {"type":"replay"} frame to the app. The Dart side re-executes the request through the same Dio instance, so mock rules still apply. The new request is logged with EntrySource::Replay.

flogEnabled tree-shaking #

flogEnabled is a compile-time constant:

Condition flogEnabled
Default debug build true
Default release build (dart.vm.product) false
--dart-define=APP_FLAVOR=release false
--dart-define=APP_FLAVOR=<anything else> true
--dart-define=FLOG_ENABLED=true true
--dart-define=FLOG_ENABLED=false false

When flogEnabled == false, every entry point in flog_dart is if (!flogEnabled) return; at the top, so AOT removes the whole package from the final binary.

Tests #

cd flog_dart
dart test
dart analyze

The flog_dart/test/ suite includes the W3C-compliant SSE parser contract (DART-001/002 regression tests) — they exercise FlogSseParser directly against realistic SSE streams.

v0.8 → v1.0 migration preview #

SseResponse.stream is @Deprecated as of v0.8 and will be removed in v1.0. Switch call sites to .events at your convenience; the wire protocol does not change.

License #

MIT.

0
likes
140
points
153
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Flutter companion for flog terminal log viewer. Structured logging, idempotent Dio instrumentation, Network Inspector, and mocks.

Homepage
Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

dio, flutter, meta, package_info_plus, web_socket_channel

More

Packages that depend on flog_dart