flutterrific_opentelemetry 0.4.0
flutterrific_opentelemetry: ^0.4.0 copied to clipboard
A Flutter package for OpenTelemetry based on dartastic_opentelemetry
Flutterrific OpenTelemetry SDK for Flutter #
Flutterrific OTel is an OpenTelemetry SDK for Flutter applications built on the Dartastic OpenTelemetry SDK, providing comprehensive observability for Flutter applications across all platforms.
Demo #
The Wondrous OpenTelemetry project instruments the Wondrous App for observability with Flutterrific OTel.
The main.dart and router.dart show how to set up your app with Flutterrific OpenTelemetry.
Overview #
Flutterrific OpenTelemetry implements the OpenTelemetry specification for Flutter, enabling developers to collect traces, metrics, and logs from Flutter applications. OpenTelemetry is a vendor-neutral standard for observability and is the second most active Cloud Native Computing Foundation (CNCF) project, after Kubernetes.
The CNCF OpenTelemetry Client Group is currently working on the specification for how OTel works on client apps. Flutterrific follows the client spec as it matures and proposes Flutter-specific semantic conventions for areas not yet covered.
Why OpenTelemetry for Flutter? #
- Report Errors: Get notified of errors in real time with structured context
- App Lifecycle: Track when users launch, pause, resume, and quit
- Watch Your Users: Understand navigation flows and improve conversion rates
- Get Metrics: Measure route load times, frame rates, and APDEX scores in production
- Structured Logs: Emit OTel Events for lifecycle, navigation, errors, and custom telemetry
- Future-Proof: OpenTelemetry is an industry standard with broad ecosystem support
- Vendor Neutral: Works with any OpenTelemetry-compatible backend (Grafana, Elastic, Datadog, etc.)
- Cross-Platform: Supports all Flutter platforms (Android, iOS, Web, Desktop)
- Performance: Designed for minimal overhead in production applications
Features #
Three OTel Signals #
Flutterrific supports all three OpenTelemetry signals:
| Signal | Description | Auto-Instrumented |
|---|---|---|
| Traces | Distributed traces with spans for navigation, lifecycle, interactions, errors, and performance | Yes |
| Metrics | Counters, histograms, and gauges for frame times, navigation durations, APDEX, errors | Yes |
| Logs | Structured OTel Events for lifecycle changes, navigation, errors, and custom events | Yes |
Auto-Instrumentation #
On FlutterOTel.initialize(), the SDK automatically instruments:
- App Lifecycle — Traces and log events for
resumed,paused,inactive,hidden,detachedtransitions - Navigation — Traces and log events for route pushes, pops, replaces, and removes (GoRouter and Navigator)
- Errors — Traces, metrics, and log events for Flutter errors reported via
FlutterOTel.reportError() - Performance Metrics — Frame times, page load times, navigation durations, APDEX scores
- User Interactions — Taps, scrolls, drags, long presses, form submissions, menu selections
Each auto-instrumented event produces an independent, short-lived trace (not chained into a single long trace), a metric data point, and (when enabled) a structured OTel log event.
Architecture #
Flutterrific follows a wrapper/decorator pattern at each layer:
FlutterOTel (static entry point)
├── UITracerProvider → wraps SDK TracerProvider
│ └── UITracer → wraps SDK Tracer, adds Flutter-specific span methods
├── UIMeterProvider → wraps SDK MeterProvider
│ └── UIMeter → wraps SDK Meter
├── UILoggerProvider → wraps SDK LoggerProvider
│ └── UILogger → wraps SDK Logger, adds Flutter-specific log methods
├── OTelLifecycleObserver → WidgetsBindingObserver for lifecycle
├── OTelNavigatorObserver → NavigatorObserver for routing
└── OTelInteractionTracker → User interaction tracking
All constructors are hidden. You create objects through static methods on FlutterOTel (or OTel from Dartastic for lower-level SDK access).
Quick Start #
1. Add Dependency #
dependencies:
flutterrific_opentelemetry: ^0.4.0
2. Initialize #
import 'package:flutter/material.dart';
import 'package:flutterrific_opentelemetry/flutterrific_opentelemetry.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize error handling
FlutterError.onError = (FlutterErrorDetails details) {
FlutterOTel.reportError(
'FlutterError.onError', details.exception, details.stack);
};
// Initialize OpenTelemetry — all three signals enabled by default
await FlutterOTel.initialize(
serviceName: 'my-flutter-app',
serviceVersion: '1.0.0',
// endpoint defaults to localhost:4317 (gRPC) or 4318 (HTTP on web)
);
runApp(MyApp());
}
3. Add the Route Observer #
For GoRouter:
final router = GoRouter(
observers: [FlutterOTel.routeObserver],
routes: [ /* ... */ ],
);
For standard Navigator:
MaterialApp(
navigatorObservers: [FlutterOTel.routeObserver],
// ...
);
That's it. With these three steps, your app automatically emits traces, metrics, and log events for all lifecycle changes, navigation, and errors.
Signals In Depth #
Traces #
Every auto-instrumented event creates a short, independent span with its own trace ID. This avoids the problem of a single long-running trace spanning the entire app session.
// Access the tracer for custom spans
final tracer = FlutterOTel.tracer;
// Create a custom span
final span = tracer.startSpan('fetch_user_data',
kind: SpanKind.client,
attributes: {'user.id': userId}.toAttributes(),
);
try {
final result = await apiClient.getUser(userId);
span.setStatus(SpanStatusCode.Ok);
return result;
} catch (e, stackTrace) {
span.recordException(e, stackTrace: stackTrace);
span.setStatus(SpanStatusCode.Error, e.toString());
rethrow;
} finally {
span.end();
}
The UITracer provides convenience methods:
| Method | Description |
|---|---|
startSpan() |
Start a span (must be ended manually) |
recordNavChange() |
Record a navigation change (starts and ends span) |
recordUserInteraction() |
Record a user interaction (starts and ends span) |
recordError() |
Record an error with stack trace |
recordPerformanceMetric() |
Record a performance measurement |
startNavigationChangeSpan() |
Start a navigation span (caller ends it) |
startAppLifecycleSpan() |
Start a lifecycle span (caller ends it) |
Metrics #
Metrics are automatically collected via OTelMetricsBridge:
| Metric | Type | Description |
|---|---|---|
flutter.frame.duration |
Histogram | Frame rendering times (ms) |
flutter.page.load_time |
Histogram | Page load times (ms) |
flutter.navigation.duration |
Histogram | Navigation transition times (ms) |
flutter.errors.count |
Counter | Error count |
flutter.interaction.response_time |
Histogram | User interaction response times (ms) |
flutter.paint.duration |
Histogram | Paint operation durations (ms) |
flutter.layout.shift_score |
Histogram | Layout shift scores |
flutter.apdex.score |
Gauge | APDEX performance index |
You can also record custom metrics:
// Using FlutterOTelMetrics
FlutterOTelMetrics.recordMetric(
name: 'my_custom_metric',
value: 42,
unit: 'ms',
metricType: 'histogram',
);
// Or directly with a Meter
final meter = FlutterOTel.meter(name: 'my-feature');
meter.createCounter(name: 'feature.usage', unit: '{count}')
.add(1, {'feature.name': 'dark_mode'}.toAttributes());
Logs (OTel Events) #
The Log Signal emits structured OTel Events — log records with an EventName field that identifies the event type. This follows the emerging OTel client instrumentation standard.
UILogger
UILogger wraps the SDK Logger with Flutter-specific convenience methods:
final logger = FlutterOTel.logger('my-feature');
// Standard log levels
logger.info('Feature loaded');
logger.warn('Cache miss');
logger.error('Failed to fetch data');
// Structured OTel Events
logger.emitEvent('user.action',
body: 'Button tapped',
attributes: {'button.id': 'submit'}.toAttributes(),
);
// Flutter error logging
FlutterError.onError = (details) {
logger.emitFlutterError(details);
};
// Lifecycle events (auto-emitted when enableAutoLogEvents is true)
logger.emitLifecycleEvent('resumed', previousState: 'paused');
// Navigation events (auto-emitted when enableAutoLogEvents is true)
logger.emitNavigationEvent('/details', fromRoute: '/home', action: 'push');
Auto Log Events
When enableAutoLogEvents is true (the default), Flutterrific automatically emits structured log events for:
| Event Name | Trigger | Key Attributes |
|---|---|---|
device.app.lifecycle |
App lifecycle state change | app_lifecycle.state, app_lifecycle.previous_state, app_lifecycle.duration |
browser.navigation |
Route change | navigation.route.name, navigation.previous_route_name, navigation.action |
device.app.error |
FlutterOTel.reportError() |
error.type, error.message, exception.stacktrace |
Set enableAutoLogEvents: false in initialize() to disable automatic log events while keeping manual logger access.
Semantic Conventions #
Flutterrific uses strongly-typed semantic enums for all attribute keys, following OTel conventions.
From the OTel Spec (via Dartastic API) #
These enums come from the standard OTel semantic conventions:
NavigationSemantics—navigation.route.name,navigation.action, etc.AppLifecycleSemantics—app_lifecycle.state,app_lifecycle.duration, etc.ErrorSemantics—error.type,error.message, etc.InteractionSemantics—interaction.type,interaction.target, etc.PerformanceSemantics—render.duration,frame.rate, etc.SessionViewSemantics—view.name,session.id, etc.UserSemantics—user.id,user.role, etc.
Flutter-Specific Proposals #
These enums are defined in FlutterSemantics and are proposed additions to the OTel client instrumentation spec. They cover areas not yet standardized:
FlutterErrorSemantics—error.context,error.widget,error.widget_context,error.locationFlutterPerformanceSemantics—perf.metric.name,perf.duration_msFlutterUISemantics—ui.typeFlutterScrollSemantics—scroll.positionFlutterRedirectSemantics—redirect.toFlutterLifecycleMetricSemantics—lifecycle.state(metric context)FlutterRouteMetricSemantics—route.name,route.action,navigation.from_route,navigation.to_routeFlutterEventNames—device.app.lifecycle,device.app.error,browser.navigation
Configuration #
Initialize Options #
await FlutterOTel.initialize(
// Required
serviceName: 'my-app',
// Optional — all have sensible defaults
appName: 'My App', // defaults to serviceName
serviceVersion: '1.0.0',
endpoint: 'https://collector:4317', // defaults to localhost:4317
// Traces
spanProcessor: null, // auto-creates BatchSpanProcessor
sampler: AlwaysOnSampler(),
flushTracesInterval: Duration(seconds: 30),
// Metrics
enableMetrics: true,
metricExporter: null, // auto-creates platform-specific exporter
metricReader: null, // auto-creates PeriodicExportingMetricReader
// Logs
enableLogs: true,
logRecordExporter: null, // auto-creates platform-specific exporter
logRecordProcessor: null,
logPrint: false, // bridge Dart print() to OTel logs
enableAutoLogEvents: true, // auto-emit lifecycle/nav/error events
// Resources
resourceAttributes: null,
detectPlatformResources: true,
commonAttributesFunction: null, // called on every span creation
// Security
secure: true,
dartasticApiKey: null,
tenantId: null,
);
Platform-Specific Exporters #
Flutterrific automatically selects the right protocol:
| Platform | Traces | Metrics | Logs |
|---|---|---|---|
| Android, iOS, Desktop | OTLP/gRPC | OTLP/gRPC | OTLP/gRPC |
| Web | OTLP/HTTP | OTLP/HTTP | OTLP/HTTP |
Web browsers cannot use gRPC due to browser limitations, so Flutterrific automatically switches to HTTP for web builds.
Common Attributes #
Use commonAttributesFunction to add attributes to every span (e.g., user ID, session info):
await FlutterOTel.initialize(
serviceName: 'my-app',
commonAttributesFunction: () => {
UserSemantics.userId.key: currentUser.id,
UserSemantics.userRole.key: currentUser.role,
SessionViewSemantics.sessionId.key: sessionManager.currentSessionId,
}.toAttributes(),
);
Environment Variables #
Standard OpenTelemetry environment variables are supported via --dart-define:
flutter run \
--dart-define=OTEL_SERVICE_NAME=my-flutter-app \
--dart-define=OTEL_SERVICE_VERSION=1.0.0 \
--dart-define=OTEL_EXPORTER_OTLP_ENDPOINT=https://collector:4317 \
--dart-define=OTEL_EXPORTER_OTLP_PROTOCOL=grpc \
--dart-define=OTEL_EXPORTER_OTLP_HEADERS=api-key=your-api-key
| Variable | Description | Default |
|---|---|---|
OTEL_EXPORTER_OTLP_ENDPOINT |
Collector endpoint | http://localhost:4317 |
OTEL_EXPORTER_OTLP_PROTOCOL |
Protocol (grpc or http/protobuf) |
grpc |
OTEL_EXPORTER_OTLP_HEADERS |
Additional headers | None |
OTEL_SERVICE_NAME |
Service name | None |
OTEL_SERVICE_VERSION |
Service version | None |
OTEL_LOG_LEVEL |
SDK internal log level | INFO |
OTEL_CONSOLE_EXPORTER |
Add console exporter for debugging | false |
Signal-specific variables (OTEL_EXPORTER_OTLP_TRACES_*, OTEL_EXPORTER_OTLP_METRICS_*) are also supported and take precedence.
Developer's Guide #
Widget-Level Tracking #
// Track button interactions
ElevatedButton(
onPressed: handleSubmit,
child: Text('Submit'),
).withOTelButtonTracking('submit_form');
// Error boundaries
RiskyWidget().withOTelErrorBoundary('risky_operation');
// Track interactions via the interaction tracker
FlutterOTel.interactionTracker.trackButtonClick(context, 'submit_btn');
FlutterOTel.interactionTracker.trackScroll(context, 'feed_list', scrollPosition);
FlutterOTel.interactionTracker.trackSwipeGesture(context, 'card', 'left');
Manual Screen Spans #
// Start a screen span (creates a new trace)
final span = FlutterOTel().startScreenSpan('checkout');
// ... user interacts with screen ...
// End when leaving
FlutterOTel().endScreenSpan('checkout');
Error Reporting #
// Report errors from any zone
FlutterOTel.reportError('network_error', error, stackTrace,
attributes: {'endpoint': '/api/users'},
);
// In Flutter error handler
FlutterError.onError = (details) {
FlutterOTel.reportError(
'FlutterError', details.exception, details.stack);
};
// For async errors
PlatformDispatcher.instance.onError = (error, stack) {
FlutterOTel.reportError('PlatformError', error, stack);
return true;
};
Custom OTel Events #
final logger = FlutterOTel.logger('checkout');
// Emit a structured event
logger.emitEvent('checkout.completed',
body: 'Order placed successfully',
attributes: {
'order.id': orderId,
'order.total': total,
'payment.method': 'credit_card',
}.toAttributes(),
);
Testing #
For widget tests, use SimpleSpanProcessor with ConsoleExporter to avoid gRPC timer conflicts with FakeAsync:
import 'package:flutterrific_opentelemetry/flutterrific_opentelemetry.dart';
Future<void> initializeForTest() async {
await FlutterOTel.initialize(
endpoint: 'http://localhost:4317',
serviceName: 'test-service',
spanProcessor: SimpleSpanProcessor(ConsoleExporter()),
enableMetrics: false,
enableLogs: false,
flushTracesInterval: null, // No periodic timer in tests
detectPlatformResources: false,
);
}
// In tests:
setUp(() async {
await FlutterOTel.reset();
await initializeForTest();
});
tearDown(() async {
await FlutterOTel.reset();
});
Local Development #
Run an OpenTelemetry collector locally:
# Grafana LGTM stack (Loki, Grafana, Tempo, Mimir) — all-in-one
docker run -p 3000:3000 -p 4317:4317 -p 4318:4318 --rm -ti grafana/otel-lgtm
Then open Grafana at http://localhost:3000 to see traces, metrics, and logs.
Flushing Data #
// Force flush all pending data (traces, metrics, logs)
FlutterOTel.forceFlush();
By default, traces are flushed every 30 seconds. Set flushTracesInterval to change this, or null to disable periodic flushing.
Cleanup #
// Clean up on app shutdown
FlutterOTel().dispose();
Platform Support #
| Platform | Support Level | Protocol | Notes |
|---|---|---|---|
| Android | Full | OTLP/gRPC | Complete feature support |
| iOS | Full | OTLP/gRPC | Complete feature support |
| Web | Full | OTLP/HTTP | Auto-switches due to browser limitations |
| Windows | Beta | OTLP/gRPC | Desktop support |
| macOS | Beta | OTLP/gRPC | Desktop support |
| Linux | Beta | OTLP/gRPC | Desktop support |
Examples #
- Basic Integration Example
- Wonderous OpenTelemetry - Complete app example based on Wonderous
Compatibility #
- Flutter: 3.7.0+
- Dart: 3.7.0+
- OpenTelemetry Specification: 1.31.0
- Platforms: Android, iOS, Web, Windows, macOS, Linux
Contributing #
We are looking for contributors and maintainers! Please see our Contributing Guide for details.
Development Setup #
git clone https://github.com/MindfulSoftwareLLC/flutterrific_opentelemetry.git
cd flutterrific_opentelemetry
flutter pub get
dart test
dart analyze
Governance #
This project follows the CNCF Code of Conduct and maintains open governance. We welcome community participation and contributions.
Security #
Security vulnerabilities should be reported privately to the maintainers. See our Security Policy for details.
Roadmap #
See our Roadmap for planned features and improvements.
License #
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.
Community #
- GitHub Issues - Bug reports and feature requests
- OpenTelemetry Community - Broader OpenTelemetry community
Acknowledgments #
Built on the foundation of:
- Dartastic OpenTelemetry SDK
- Dartastic OpenTelemetry API
- OpenTelemetry Specification
- Flutter Framework
Maintained by Michael Bushe and Mindful Software LLC.
This project aims to be contributed to the OpenTelemetry organization under the Cloud Native Computing Foundation.