Skip to main content

flutter_onscreen_logger 3.2.0 copy "flutter_onscreen_logger: ^3.2.0" to clipboard
flutter_onscreen_logger: ^3.2.0 copied to clipboard

A Flutter package that allows you to display logs on the screen of your app for easier debugging.

Flutter On-Screen Logger #

Pub Package Pub Package GitHub Repo stars

A Flutter package that displays logs over your app for easier debugging and can capture a session without an overlay for sharing through your support flow.

flutter_onscreen_logger flutter_onscreen_logger

Features #

  • Log exceptions and errors in a user-friendly way.
  • Easily integrate with Dio for HTTP request logging.
  • Simple API for logging custom messages.
  • Customizable display options for logs.
  • Programmatic capture controls for development, production, and runtime settings.
  • Export retained session logs directly as text or UTF-8 bytes for feedback attachments.

Getting Started #

  1. Add the package to your pubspec.yaml file:

    dependencies:
      flutter_onscreen_logger: ^3.2.0
    
  2. Configure your main.dart to integrate the library:

    • Wrap your runApp() method in main() with runZonedGuarded() to handle errors globally.
    • Optionally call OnScreenLog.init() to choose capture and scrolling options.
    • Call OnScreenLog.onError() during startup to capture Flutter framework errors.
    • Log asynchronous errors received by the zone's error callback.
    void main() {
      runZonedGuarded(() {
        WidgetsFlutterBinding.ensureInitialized();
    
        OnScreenLog.init(
          enabled: true,
          enabledTypes: LogItemType.values.toSet(),
          autoScroll: true,
        );
        OnScreenLog.onError();
    
        //...other code...
           
        runApp(MyApp());
      }, (error, stack) {
        OnScreenLog.e(title: error.toString(), message: stack.toString());
      });
    }
    
    • Add the overlay using your app's builder, so it inherits the active app theme.
    • Return a Stack containing the builder's child first and LoggerOverlayWidget() last.
    • Show the overlay when useful for your app. Its visibility is independent of capture. This example displays it only in debug mode (kDebugMode comes from package:flutter/foundation.dart).
    MaterialApp(
      // ...your theme, routes, and other app properties...
      home: MyHomePage(title: 'MyApp'),
      builder: (context, child) => Stack(
        fit: StackFit.expand,
        children: [
          child ?? const SizedBox.shrink(),
          if (kDebugMode) LoggerOverlayWidget(),
        ],
      ),
    );
    

App theme integration #

Use the same builder setup with MaterialApp, MaterialApp.router, or GetMaterialApp. The logger reads Flutter's inherited Theme, so it follows the active theme and runtime light/dark changes without depending on how the app manages theme state. The handle and jump button use ThemeData.primaryColor, and the options popup inherits the app's theme, including popupMenuTheme. Message cards and quick filters retain their type colors and black background. Quick filters use a fixed style with 8-pixel corners, independent of the app's chip shape, typography, and spacing. Accessibility text scaling still applies.

If your app already has a builder, keep its existing content in the stack along with the logger. Keep the supplied child in the tree so navigation still works.

Migrating an existing integration: move the logger from the outer Stack(children: [MaterialApp(...), LoggerOverlayWidget()]) into the app's builder. A sibling cannot inherit the theme inside MaterialApp or GetMaterialApp. The package's earlier setup instructions placed it outside that theme.

For custom placement outside the themed subtree, supply an explicit override:

LoggerOverlayWidget(theme: activeAppTheme)

Rebuild with the updated ThemeData whenever that external theme changes. The override takes precedence over the inherited theme. Without an override or an inherited theme, Flutter supplies its fallback theme.

Usage #

You can use the on-screen logger to log your own custom message throughout your project using the following methods:

e() - Log Errors #

Logs error messages with a red color.

OnScreenLog.e(
  title: 'Network Error',
  message: 'Unable to fetch data from server.',
);

i() - Log Info #

Logs info messages with a white color.

OnScreenLog.i(
  title: 'API Response Received!',
  message: '* API Response:\n$data',
);

s() - Log Success #

Logs success messages with a green color.

OnScreenLog.s(
  title: 'Login Successful',
  message: 'User has logged in successfully.',
);

w() - Log Warnings #

Logs warning messages with a orange color.

OnScreenLog.w(
  title: 'Low Disk Space',
  message: 'Disk space is running low, consider cleaning up.',
);

exportReport() - Export without files or a share sheet #

Available in 3.1.0. Retrieve a session report directly for an in-app feedback attachment. Import only package:flutter_onscreen_logger/flutter_onscreen_logger.dart:

OnScreenLog.init(enabled: true);

// Mounting the overlay is optional, including in release builds.
OnScreenLog.i(
  title: 'Budget',
  message: 'Budget screen opened',
);

try {
  final LogReport report = await OnScreenLog.exportReport();
  final bytes = report.toUtf8Bytes();

  // Application code decides where to send the attachment:
  await feedbackRepository.uploadLog(
    bytes: bytes,
    filename: 'session.log',
    contentType: 'text/plain; charset=utf-8',
  );
} catch (error) {
  // Handle an export or upload failure in your application's feedback flow.
}

feedbackRepository belongs to your application; the library adds no uploader dependency. See the runnable export example for a version requiring no application services.

The public immutable LogReport contains text, UTC generatedAt, entryCount, and nullable firstEntryAt / lastEntryAt (the first and last entries in capture order, not a timestamp sort). toUtf8Bytes() returns a fresh UTF-8 buffer without a byte-order mark; modifying it cannot change the report. Generation and entry timestamps in the text use UTC ISO 8601, retaining subsecond precision. Titles, types, multiline descriptions, Unicode, stack traces, and already-captured structured HTTP details use the same report formatter as Share all.

  • Export snapshots all retained entries when invoked, in capture order, even when timestamps are identical. Later additions or clearing cannot change that report; concurrent calls are independent. Flush any logging deferred by application code before requesting the snapshot.
  • Display filters do not limit export. Capture-excluded entries and messages discarded while paused cannot be reconstructed. Exporting while paused or disabled includes previously retained entries without resuming capture.
  • An empty capture returns a readable empty report, entryCount: 0, and null first/last entry timestamps. It is a successful result.
  • Formatting failures complete the Future with an error. Handle them with try/catch; they are not converted into empty reports or logged recursively.
  • Export works without a widget BuildContext or mounted overlay in debug, profile, and release builds. It does not change navigation, focus, scrolling, visibility, capture settings, or the retained buffer.
  • Export and UTF-8 conversion perform no file, share-sheet, clipboard, storage permission, gallery, upload, or network operations. Only the application's uploader in the example accesses the network. shareAll() continues to create a temporary file and open native sharing, using the same formatter.

Only retained content and basic report metadata are included; export does not collect device identifiers, account data, or environment information. Avoid logging secrets and private content. Existing network redaction applies before storage; exporting adds no new automatic redaction for application-written logs. Large sessions still require memory for the retained entries, report text, and encoded bytes. Optional retention limits are available from 3.2.0 (below).

captureSnapshot() - Freeze now, export or retry later #

Available in 3.2.0. Freeze the logs when a feedback flow opens, even if the user has not yet chosen whether to attach them:

final LogSnapshot snapshot = OnScreenLog.captureSnapshot();

// Later, including after logging, clearing, pausing or changing limits:
try {
  final report = await OnScreenLog.exportReport(snapshot: snapshot);
  final bytes = report.toUtf8Bytes();
  // Pass bytes to your application's attachment uploader.
} catch (error) {
  // Retry export with the same snapshot to use the original captured logs.
}

Snapshot capture is synchronous and does not format a report or perform I/O. It owns immutable entries, structured details, original entry timestamps, capturedAt, retention counters, captureEnabled, enabledTypes, and retention. It does not retain application callbacks or overlay state. Flush any deferred logging before capturing. Snapshot-creation errors propagate synchronously; formatting errors complete exportReport with an error and leave the snapshot usable. A deterministic formatting failure may fail again on retry.

Each retry uses the snapshot's original UTC timestamp as LogReport.generatedAt, producing identical text and UTF-8 bytes. No live controller is needed when exporting a supplied snapshot. Calling exportReport() without an argument still captures the current logs immediately, and Share all uses the same formatter.

The library keeps no snapshot history. Your app owns snapshot lifetime: release references when the feedback flow ends, and handle account/session boundaries in your app. Clearing the live logger cannot invalidate a snapshot you already hold.

configureRetention() - Optional rolling capture limits #

Available in 3.2.0. All limits are unlimited by default. Configure any combination before logging, or change it later:

OnScreenLog.configureRetention(
  maxEntries: 500,
  maxUtf8Bytes: 1024 * 1024,
  maxEntryCharacters: 4000,
  oversizedEntryPolicy: OversizedEntryPolicy.truncate,
);

These are example values, not a prescribed production preset. Each call replaces all retention settings; omitted limits become unlimited. Inspect the current immutable settings through OnScreenLog.retention.

// Count-only retention does not truncate messages.
OnScreenLog.configureRetention(maxEntries: 1000);

// Restore unlimited capture without clearing entries or counters.
OnScreenLog.resetRetention();
  • Limits apply when an entry is accepted, and stricter settings trim existing entries immediately. Oldest entries are evicted first, preserving capture order. Removing limits cannot recover discarded data.
  • maxEntries must be positive. maxEntryCharacters must be positive and limits combined textual field content in user-perceived Unicode characters (grapheme clusters), including the visible … truncation marker. It preserves joined emoji and combining marks. Plain entries count title plus description, including stack traces. HTTP entries count method, URL, request ID, error, headers and body once; generated labels and duplicate display/export representations are instead covered by the byte budget. Identifying fields take priority over large bodies.
  • maxUtf8Bytes bounds the complete UTF-8 report, including separators, timestamps and retention headers. A conservative 512-byte metadata reserve makes 512 the minimum accepted limit; the remaining budget holds formatted entries. A metadata-only budget can retain no entries. All entry fields and generated labels count toward this limit. Your application's extra diagnostic header is outside the report and must have its own allowance.
  • An entry larger than the available byte budget is shortened by default. Choose OversizedEntryPolicy.drop to discard it instead. An explicit character limit still applies first. If even a shortened entry cannot fit, it is dropped without unnecessarily evicting useful history. HTTP cards and exports both use shortened copies; the logger does not retain their original oversized payloads.
  • Invalid limits throw ArgumentError before changing settings. Retention failures propagate without recursively logging or showing UI. Capture remains independent of the overlay and build mode; display filters do not affect retention or export.

LogReport and LogSnapshot expose cumulative omittedEntries and truncatedEntries since clearAll(). Omitted entries were accepted then evicted, or dropped because they could not fit. Truncated entries were shortened, counted once each even if shortened again. An entry shortened and later dropped/evicted can count in both; these counters can exceed the current entryCount. Paused or capture-excluded events do not increment counters. Exported text includes both counters when limits are active or either counter is nonzero.

clearAll() clears entries and counters, while preserving capture and retention settings. It does not change already-frozen snapshots or reports. New LogReport constructor fields default to zero for compatibility with existing callers.

The byte budget is not a Dart heap limit. Strings, structured card data, formatting, UTF-8 conversion, and caller-held snapshots/reports use additional memory. Retention bounds the live buffer, not snapshots held by your app, and cannot prevent allocations made by application code before calling the logger.

shareAll() - Share All Logs #

Allows you to share all the log entries that have been captured so far. This method will gather the logs and open a sharing interface for the user to share the log messages.

OnScreenLog.shareAll();

clearAll() - Clear All Logs #

Clears all log entries currently stored in the logger, effectively resetting the log data. This is useful if you want to start a new logging session or remove sensitive log data.

OnScreenLog.clearAll();

Configure capture and scrolling #

Call OnScreenLog.init() before your first log to choose the session's initial settings. Initialization is optional: the logger still initializes lazily with capture enabled, every message type enabled, and auto-scroll enabled.

OnScreenLog.init(
  enabled: true,
  enabledTypes: {LogItemType.error, LogItemType.warning},
  autoScroll: false,
);
Option Default Effect
enabled true Whether new entries are captured at all, including when the overlay is absent.
enabledTypes Every LogItemType Types accepted into the session. An empty set captures nothing.
autoScroll true Whether the overlay follows new entries while already at the bottom.
networkLogOptions NetworkLogOptions() Which HTTP headers and bodies connectors capture and the redactor applied before storage.

Disabling capture discards future entries from OnScreenLog methods, the default Dio/HTTP connectors, and installed Flutter error capture. Existing entries remain available to share or clear. Resuming records new entries only; discarded entries are never replayed. Removing or collapsing the overlay does not change these settings. Connectors using a custom log callback have their own sink; these settings apply when that callback submits entries to OnScreenLog.

Change individual settings at runtime without resetting the others:

OnScreenLog.configure(enabled: false); // Stop capture, retain existing logs.
OnScreenLog.configure(enabled: true); // Resume capture.
OnScreenLog.configure(
  enabledTypes: {LogItemType.error, LogItemType.warning},
  autoScroll: false,
);
OnScreenLog.configure(enabledTypes: LogItemType.values.toSet());

configure() updates only the supplied options. Calling init() again applies its complete settings, including defaults for omitted options, to the existing session. Neither call clears captured logs or changes the overlay's quick filters. The menu's pause/resume and auto-scroll actions use the same state as these APIs.

Read OnScreenLog.isEnabled, OnScreenLog.isAutoScrollEnabled, and the immutable OnScreenLog.enabledTypes set to inspect the current settings. Use OnScreenLog.isEnabledFor(type) before preparing expensive log data:

if (OnScreenLog.isEnabledFor(LogItemType.info)) {
  OnScreenLog.i(message: buildDiagnosticReport());
}

Capture only during development #

Choose this if release builds should capture no session logs. Existing logging calls can stay in your application code:

OnScreenLog.init(enabled: kDebugMode);

// In the Stack returned by your app's builder:
if (kDebugMode) LoggerOverlayWidget(),

Capture production support sessions without an overlay #

Keep capture enabled and display the overlay only during development. Your production settings or support page can offer a share button:

OnScreenLog.init(enabled: true);

// In the Stack returned by your app's builder:
if (kDebugMode) LoggerOverlayWidget(),

// In your settings/support page:
TextButton.icon(
  onPressed: OnScreenLog.shareAll,
  icon: const Icon(Icons.share),
  label: const Text('Share session logs'),
),

To reduce production capture to warnings and errors while retaining every type during development:

OnScreenLog.init(
  enabled: true,
  enabledTypes: kDebugMode
      ? LogItemType.values.toSet()
      : {LogItemType.warning, LogItemType.error},
  autoScroll: kDebugMode,
);

// Later, apply an app setting without changing the type selection:
OnScreenLog.configure(enabled: captureEnabledByUser);

Quick filters and options #

Tap the info, success, warning, and error chips to include or exclude types independently. All types start selected; deselecting every type shows an empty view. These filters only affect the view, so Share all still shares the complete captured session. In contrast, enabledTypes controls which new entries are stored in the first place. The top-right Logger options menu contains Pause logging / Resume logging, Auto-scroll (configurable at startup), Share all, and Clear all. Pausing discards incoming entries, including connector logs, while keeping existing entries available. It remains paused when the overlay is collapsed; resuming captures only new entries and does not replay skipped messages. This is independent of auto-scroll.

Filter chips use the same colors as their message types: outlined when excluded and filled when selected. Every menu action has an icon; toggle icons reflect the current logging/auto-scroll state.

When newer messages are below the viewport, a small button at the bottom right shows how many filtered messages are below the last visible entry. Partially visible entries count as visible. Tap it to jump to the actual latest entry; it disappears at the bottom. Scrolling up suspends automatic following so new messages do not interrupt reading. Returning to the bottom resumes following if auto-scroll is enabled. Jumping does not change the auto-scroll preference.

Automatic network logging #

Two independent connector packages live in packages/. They use the same logger session whether or not LoggerOverlayWidget is mounted.

Dio #

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

final dio = Dio();
dio.interceptors.add(OnScreenLoggerInterceptor());

package:http #

HTTP uses a client wrapper because it has no Dio-style interceptor API. Make requests through the wrapper (top-level http.get calls are not intercepted).

import 'package:http/http.dart' as http;
import 'package:flutter_onscreen_logger_http/flutter_onscreen_logger_http.dart';

final client = OnScreenLoggerClient(http.Client());
final response = await client.get(Uri.parse('https://example.com'));
client.close(); // Also closes the wrapped client.

For local development, add the desired connector with a path dependency:

dependencies:
  flutter_onscreen_logger:
    path: /path/to/flutter_onscreen_logger
  flutter_onscreen_logger_dio:
    path: /path/to/flutter_onscreen_logger/packages/flutter_onscreen_logger_dio
  # Or flutter_onscreen_logger_http with its corresponding path.
dependency_overrides:
  flutter_onscreen_logger:
    path: /path/to/flutter_onscreen_logger

The connectors log methods, URL paths, response status, and failures. URL credentials and query values are omitted. Headers and bodies stay off by default. Enable selected fields and redact their formatted text before it enters the session:

OnScreenLog.init(
  networkLogOptions: NetworkLogOptions(
    includeRequestHeaders: true,
    includeResponseHeaders: true,
    includeRequestBody: true,
    includeResponseBody: true,
    redactor: LogRedactor.redact, // Your app's redactor, e.g. token/password rules.
  ),
);

NetworkLogOptions can also be passed to an individual OnScreenLoggerInterceptor(options: ...) or OnScreenLoggerClient(options: ...) to override the global setting. Redaction runs before the entry is stored, so the overlay, copy action, and shared session use the same sanitized snapshot. Implement the callback to return safe text, for example by redacting sensitive JSON keys and token patterns. includeResponseBody captures decoded Dio responses; the HTTP connector does not read or buffer response streams. It captures buffered http.Request bodies only when enabled. Each connector accepts an optional log: (LogItem item) { ... } callback for a custom sink. Logging failures do not interrupt requests.

Capturing a session without the overlay #

Keep OnScreenLog enabled and call OnScreenLog.i/e/w/s or use either connector without adding the widget. Configure enabledTypes to choose what is captured. Offer OnScreenLog.shareAll() in your app's support flow to export the captured session. Logs are held in memory for the current process, and written to a temporary file when shared; they are not persisted across app restarts. This does not schedule background execution or collect logs from other isolates.

Developing the packages #

Run flutter pub get, flutter analyze, and flutter test in the repository root and in each connector directory. Connector pubspec_overrides.yaml files resolve the core package locally and are excluded from publication by pub. The connector packages must be published separately before hosted dependencies can be used by consumers.

Version 3 toolchain and migration #

Version 3 requires Flutter 3.44.3 or newer and Dart 3.12 or newer. The repository pins Flutter 3.44.3 in .fvmrc; run fvm use 3.44.3 to install/select that SDK on another machine. SDK installations are excluded from Git and pub archives. The .fvm/flutter_sdk link is generated locally by FVM.

The Android example uses Gradle 9.7.1, Android Gradle Plugin 9.3.3, Kotlin 2.4.20, and Java 17 source/target compatibility. Run Gradle with JDK 17 or newer (Flutter can use Android Studio's bundled JDK). The iOS example targets iOS 15 or newer. Update consuming applications' native build settings before upgrading from version 2, since the latest plugin dependencies have higher platform/toolchain requirements.

Direct dependencies are updated to fluttertoast 10.0.0, get 4.7.3, intl 0.20.3, path_provider 2.1.6, and share_plus 13.3.0. Sharing uses the current SharePlus.instance.share(ShareParams(...)) API; the public OnScreenLog calls remain compatible. Some transitive dependencies are constrained by Flutter or upstream plugins; this project does not force incompatible dependency overrides.

Before publishing, run the following with the selected SDK:

fvm flutter pub get
fvm flutter analyze
fvm dart format --output=none --set-exit-if-changed lib test example/lib example/test packages
fvm flutter test
fvm dart pub outdated --no-dev-dependencies --up-to-date --no-dependency-overrides
fvm dart pub publish --dry-run

Run analysis and tests in example/ and each connector directory too. Publish the core version 3.0.0 package before the connectors, which depend on ^3.0.0. Pub.dev assigns the final hosted score after publication and reanalysis.

The root .pubignore keeps the nested connector sources out of the core package archive. Export each connector directory outside the repository before publishing it, so it is checked as its own package:

mkdir -p /tmp/logger-release
git archive HEAD packages/flutter_onscreen_logger_dio | tar -x -C /tmp/logger-release
cd /tmp/logger-release/packages/flutter_onscreen_logger_dio
fvm dart pub publish --dry-run

Repeat with flutter_onscreen_logger_http in the archive path and working directory for the HTTP connector. Publish the core package first so the connectors' ^3.0.0 dependency is available.

Try both connectors in the example #

Run fvm flutter pub get and fvm flutter run from example/. Its local pubspec_overrides.yaml selects the connector packages from this repository. Tap Test Dio requests or Test HTTP requests. Each makes two GET requests to JSONPlaceholder: /posts/1 (200) and /posts/0 (404), demonstrating automatic request, success, and error logs. These calls need an internet connection and send no credentials. The Dio demo enables body logging for this public test data; the HTTP demo logs metadata. Both clients are closed when the example screen is disposed. Network button tests use mock transports, so the automated suite runs offline.

Reading network logs #

Dio and HTTP entries use dedicated network cards. The collapsed summary shows the method, request/response/failure stage, HTTP status when available, endpoint, elapsed time, and a request ID shared by related entries. Timing measures the Dio response or failure, and HTTP time to response headers; it is not a measure of downloading an HTTP response stream.

Tap a card's header to inspect the full captured URL, failure category, and payload. Dio bodies captured with logBodies: true are formatted as indented JSON when possible. Large bodies scroll inside a bounded panel, so the Copy log action stays accessible. Copying and sharing include the entire captured body, not just the visible portion. HTTP streams remain unbuffered and its connector continues to log metadata only. Headers, credentials, and query values remain omitted by the connectors.

You can also submit structured network entries from a custom integration:

OnScreenLog.log(LogItem.network(
  type: LogItemType.success,
  details: HttpLogDetails(
    method: 'GET',
    url: 'https://api.example.com/orders/42',
    phase: HttpLogPhase.response,
    requestId: 'orders-42',
    statusCode: 200,
    duration: const Duration(milliseconds: 120),
    body: HttpLogDetails.formatBody({'id': 42, 'status': 'delivered'}),
  ),
));

Custom integrations choose which URL and payload information to capture. LogItem.network keeps the card and its text export consistent. Existing OnScreenLog.i/e/w/s calls and plain LogItem entries keep their original layout.

8
likes
160
points
478
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A Flutter package that allows you to display logs on the screen of your app for easier debugging.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

characters, flutter, fluttertoast, get, path_provider, share_plus

More

Packages that depend on flutter_onscreen_logger