obsi_flutter 1.0.1 copy "obsi_flutter: ^1.0.1" to clipboard
obsi_flutter: ^1.0.1 copied to clipboard

Production Flutter error capture and Navigator telemetry for Obsi.

obsi_flutter #

pub package CI License: MIT

obsi_flutter connects Flutter runtime behavior to obsi. It provides:

  • global capture for Flutter framework and root-isolate errors;
  • navigation spans and breadcrumbs;
  • navigation transition counts and visible-screen duration metrics;
  • optional structured navigation logs.

The obsi core is required and owns providers, exporters, sampling, error policy, redaction, and shutdown. This package does not choose a backend.

Installation #

flutter pub add obsi obsi_flutter
import 'package:flutter/material.dart';
import 'package:obsi/obsi.dart';
import 'package:obsi_flutter/obsi_flutter.dart';

How it works #

FlutterError.onError ─────────────┐
PlatformDispatcher.onError ───────┼→ Obsi ErrorManager → configured exporter
                                  │
NavigatorObserver callbacks ──────┴→ traces + metrics + breadcrumbs + logs

The error integration temporarily owns Flutter's global handlers. The navigation observer is a normal NavigatorObserver and can be attached to Navigator 1.0 or router packages that accept standard observers.

Complete configuration #

Configure Obsi before installing global Flutter handlers:

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  final resource = Resource({
    'service.name': 'checkout-mobile',
    'service.version': '1.0.0',
  });
  Obsi.configure(ObsiProvider(
    traces: TracerProvider(
      resource: resource,
      processor: BatchSpanProcessor(const ConsoleSpanExporter()),
    ),
    logs: LoggerProvider(
      resource: resource,
      processor: BatchLogProcessor(const ConsoleLogExporter()),
    ),
    metrics: MeterProvider(
      resource: resource,
      readers: [PeriodicMetricReader(const ConsoleMetricExporter())],
    ),
    errors: ErrorManager(
      resource: resource,
      exporter: const ConsoleErrorExporter(),
    ),
  ));

  ObsiFlutterErrorIntegration().install();
  runApp(const App());
}

Production applications normally replace console exporters with OTLP, Sentry, Crashlytics, or custom exporters.

For readable local output, all four core console exporters provide a pretty constructor. Flutter applications can route complete records through debugPrint so long stack traces are handled by Flutter's console integration:

final prettyLogs = ConsoleLogExporter.pretty(
  options: const PrettyConsoleOptions(colors: true),
  writer: debugPrint,
);

final logs = LoggerProvider(
  processor: BatchLogProcessor(prettyLogs),
);

The same API is available on ConsoleSpanExporter.pretty, ConsoleMetricExporter.pretty, and ConsoleErrorExporter.pretty. Their unnamed constructors continue to emit newline-delimited JSON.

Capturing Flutter errors #

ObsiFlutterErrorIntegration captures:

  • FlutterError.onError for framework failures;
  • PlatformDispatcher.instance.onError for otherwise uncaught root-isolate asynchronous failures.
final flutterErrors = ObsiFlutterErrorIntegration();

flutterErrors.install(
  fatal: true,
  preserveExistingHandlers: true,
  requireConfiguredManager: true,
);

Existing handlers are preserved and invoked by default. Installing the same instance again is a no-op. Installing another instance while one owns the handlers throws StateError, preventing handlers from being restored out of order.

When an application deliberately replaces handlers or has a controlled teardown path, restore the previous handlers before closing Obsi:

flutterErrors.uninstall();
await Obsi.shutdown();

uninstall() is idempotent for its owner and throws StateError if another component changed handler ownership unexpectedly. Secondary isolates require their own Obsi configuration or Errors.listenToIsolateErrors().

Observing navigation #

Attach one observer to each navigator:

MaterialApp(
  navigatorObservers: [
    ObsiNavigatorObserver(
      tracer: Obsi.tracer,
      meter: Obsi.meter('navigation'),
      logger: Obsi.logger('navigation'),
      navigatorName: 'root',
    ),
  ],
  home: const HomePage(),
);

The observer records push, pop, replace, remove, and user-gesture transitions. It also counts transitions and records how long the visible screen remained on top of its navigator.

For nested navigators, install one observer per navigator and assign a distinct navigatorName such as root, checkout, or tabs.account. This keeps screen duration state separate.

Route names and privacy #

By default, the observer uses RouteSettings.name and ignores unnamed routes. It never falls back to Route.toString() because that output can include route arguments or user data.

final observer = ObsiNavigatorObserver(
  navigatorName: 'root',
  options: ObsiNavigatorObserverOptions(
    routeNameResolver: (route) => route.settings.name,
    shouldInstrument: (route) => route.settings.name != '/debug',
    attributes: (operation, route, previousRoute) => {
      'app.navigation.source': 'primary',
    },
    includeUnnamedRoutes: false,
    recordBreadcrumbs: true,
    emitLogs: false,
    traceUserGestures: true,
    onInstrumentationError: diagnostics,
  ),
);

Prefer stable templates such as /products/:id over /products/87421. Callback errors and invalid attributes are reported through onInstrumentationError and never escape into Navigator.

Navigation breadcrumbs are enabled by default and become part of later Obsi error reports. Structured logs are opt-in because Obsi logs already become generic log breadcrumbs; enabling both may duplicate similar context.

The observer emits:

Signal Name Meaning
Counter navigation.transition.count Number of navigation operations by route and navigator.
Histogram navigation.screen.visible.duration Seconds for which a route remained visible.
Span Navigation operation name Push, pop, replace, remove, or gesture transition.
Breadcrumb Navigation category Recent route activity attached to error reports.

Keep route names and custom metric attributes low-cardinality.

Ownership and lifecycle #

The observer does not own its navigator, tracer, meter, logger, or Obsi provider. Flutter disposes the observer with the navigator lifecycle.

The error integration owns global handlers only between install() and uninstall(). A mobile process may terminate without an asynchronous shutdown window; flush telemetry at controlled lifecycle boundaries when the host application provides them.

API inventory #

Concept Public API When to use it
Global Flutter errors ObsiFlutterErrorIntegration Capture framework and uncaught root-isolate failures through Obsi.
Navigation telemetry ObsiNavigatorObserver Add traces, metrics, breadcrumbs, and optional logs to a Navigator.
Observer configuration ObsiNavigatorObserverOptions Configure route resolution, filtering, attributes, unnamed routes, gestures, logs, and diagnostics.
Route naming ObsiRouteNameResolver Convert a route into a stable, sanitized name or template.
Route filtering ObsiRoutePredicate Exclude routes from navigation telemetry.
Custom metadata ObsiNavigationAttributeBuilder Add validated attributes for a transition.
Navigation attributes ObsiNavigationAttributes Reference stable attribute names emitted by the observer.
Navigation metrics ObsiNavigationMetrics Reference the transition-count and screen-duration metric names.

Only declarations exported by package:obsi_flutter/obsi_flutter.dart are stable. The example integration is tested locally on Android and iOS before a release.

Choose obsi_error_sentry or obsi_error_crashlytics for remote error delivery. See obsi for signal configuration, privacy, and lifecycle. Licensed under the MIT License.

0
likes
160
points
62
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Production Flutter error capture and Navigator telemetry for Obsi.

Repository (GitHub)
View/report issues
Contributing

Topics

#observability #navigation #errors #tracing #flutter

License

MIT (license)

Dependencies

flutter, obsi

More

Packages that depend on obsi_flutter