middleware_flutter_opentelemetry 1.0.16 copy "middleware_flutter_opentelemetry: ^1.0.16" to clipboard
middleware_flutter_opentelemetry: ^1.0.16 copied to clipboard

A Flutter package for OpenTelemetry based on middleware_dart_opentelemetry

Middleware Flutter OpenTelemetry SDK for Flutter #

pub.dev

License

Middleware Flutter OTel is an OpenTelemetry SDK for Flutter applications built on the Middleware OpenTelemetry SDK, providing comprehensive observability for Flutter applications across all platforms.

Demo #

The Wondrous OpenTelemetry project instruments the Wondrous App for observability with Middleware Flutter OTel. The main.dart and router.dart show how to set up your app with Middleware Flutter 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.

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 #

  • ๐Ÿš€ Simple Integration: Get started with just a few lines of code
  • ๐Ÿ‘ฃ Automatic Instrumentation: Navigation, app lifecycle, and user interaction tracking
  • ๐Ÿ“น Session Replay: Lightweight Session replay of application.
  • ๐Ÿ“Š Performance Metrics: Web vitals, APDEX scores, and custom performance metrics
  • ๐Ÿงฉ Widget Extensions: Easy-to-use extensions for widget-level observability
  • ๐Ÿž Error Tracking: Comprehensive error handling and reporting
  • ๐Ÿ“ Standards Compliant: Full adherence to OpenTelemetry specification
  • ๐ŸŒ Multi-Platform: Supports Android, iOS, Web, and Desktop platforms
  • ๐Ÿ’ช Context Propagation: Seamless trace correlation across async boundaries
  • ๐Ÿ”ง Configurable Sampling: Multiple sampling strategies for cost optimization
  • ๐Ÿงท Type-Safe Semantics: Strongly-typed semantic conventions

Quick Start #

1. Add Dependency #

dependencies:
  middleware_flutter_opentelemetry: ^1.0.0

2. Initialize #

import 'package:flutter/material.dart';
import 'package:middleware_flutter_opentelemetry/flutterrific_otel.dart';

  // Initialize error handling
  FlutterError.onError = (FlutterErrorDetails details) {
    FlutterOTel.reportError(
      'FlutterError.onError', details.exception, details.stack);
  };

  runZonedGuarded(() {
    // Initialize OpenTelemetry
    FlutterOTel.initialize(
      serviceName: 'my-flutter-app',
      serviceVersion: '1.0.0',
      tracerName: 'main',
      middlewareAccountKey: "*****", // Obtain from RUM Flutter installation page
      endpoint: 'https://<account>.middleware.io',  
      // Configure your exporter endpoint
      resourceAttributes: {
        'env': 'production',
        'service.namespace': 'mobile-apps',
      }
    );
    
    runApp(MyApp());
  }, (error, stack) {
    FlutterOTel.reportError('Zone Error', error, stack);
  });
}

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.location
  • FlutterPerformanceSemantics โ€” perf.metric.name, perf.duration_ms
  • FlutterUISemantics โ€” ui.type
  • FlutterScrollSemantics โ€” scroll.position
  • FlutterRedirectSemantics โ€” redirect.to
  • FlutterLifecycleMetricSemantics โ€” lifecycle.state (metric context)
  • FlutterRouteMetricSemantics โ€” route.name, route.action, navigation.from_route, navigation.to_route
  • FlutterEventNames โ€” 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();

3. Session Replay #

Flutter SDK has the capability of session recording, to start session recording call FlutterOTel.startSessionRecording();
& to stop session recording call FlutterOTel.stopSessionRecording()

class MyAppState extends State<MyApp> {
  
  @override
  void initState() {
    Timer(Duration(milliseconds: 500), () {
      FlutterOTel.startSessionRecording();
    });
    super.initState();
  }
  
  @override
  void dispose(){
    try {
      FlutterOTel.stopSessionRecording();
    } catch (e) {}
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return RepaintBoundary(
        key: FlutterOTel.repaintBoundaryKey,
        child: ...;
  }
}

Recording Quality

Control the session replay frame quality with RecordingOptions, passed to FlutterOTel.initialize. The defaults are tuned for low bandwidth; raise qualityValue (and optionally minShortSidePx) for a sharper replay.

import 'package:middleware_flutter_opentelemetry/middleware_flutter_opentelemetry.dart';

FlutterOTel.initialize(
  serviceName: 'my-flutter-app',
  tracerName: 'main',
  middlewareAccountKey: '*****',
  endpoint: 'https://<account>.middleware.io',
  // Medium quality preset (good balance of clarity vs. bandwidth):
  recordingOptions: const RecordingOptions(
    qualityValue: 35,    // JPEG quality 1โ€“100 (default 10). ~35 is medium.
    minShortSidePx: 480, // Frame short-side resolution (default 320).
  ),
);

RecordingOptions knobs:

Option Default Description
qualityValue 10 JPEG quality (1โ€“100). Low โ‰ˆ 10, medium โ‰ˆ 35, high โ‰ˆ 60.
minShortSidePx 320 Frames are scaled so the short side is this many pixels.
screenshotInterval 500 ms How often a frame is captured.
archiveChunkSize 10 Frames bundled per upload batch.

Tip: bandwidth grows quickly with quality. Avoid qualityValue above ~60 unless you also lower minShortSidePx.

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

Environment Variables #

Middleware OpenTelemetry supports standard OpenTelemetry environment variables as defined in the OpenTelemetry Specification.

These environment variables can be set to configure the SDK behavior without changing code. Signal-specific variables take precedence over general ones.

Service Configuration #

Variable Description Example
OTEL_SERVICE_NAME Sets the service name my-dart-app
OTEL_SERVICE_VERSION Sets the service version 1.0.0
OTEL_RESOURCE_ATTRIBUTES Additional resource attributes as comma-separated key=value pairs environment=production,region=us-west

OTLP Exporter Configuration #

Variable Description Default Example
OTEL_EXPORTER_OTLP_ENDPOINT The OTLP endpoint URL http://localhost:4317 for gRPC, http://localhost:4318 for HTTP https://otel-collector:4317
OTEL_EXPORTER_OTLP_PROTOCOL The protocol to use http/protobuf grpc, http/protobuf, http/json
OTEL_EXPORTER_OTLP_HEADERS Additional headers as comma-separated key=value pairs None api-key=secret,tenant=acme
OTEL_EXPORTER_OTLP_INSECURE Whether to use insecure connection false true
OTEL_EXPORTER_OTLP_TIMEOUT Export timeout in milliseconds 10000 5000
OTEL_EXPORTER_OTLP_COMPRESSION Compression to use None gzip

Signal-Specific Configuration #

Traces

Variable Description Default Example
OTEL_TRACES_EXPORTER Trace exporter to use otlp console, none
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT Traces-specific endpoint Uses general endpoint https://traces.example.com
OTEL_EXPORTER_OTLP_TRACES_PROTOCOL Traces-specific protocol Uses general protocol grpc
OTEL_EXPORTER_OTLP_TRACES_HEADERS Traces-specific headers Uses general headers trace-key=value
OTEL_EXPORTER_OTLP_TRACES_INSECURE Traces-specific insecure setting Uses general setting true
OTEL_EXPORTER_OTLP_TRACES_TIMEOUT Traces-specific timeout Uses general timeout 30000
OTEL_EXPORTER_OTLP_TRACES_COMPRESSION Traces-specific compression Uses general compression gzip

Metrics

Variable Description Default Example
OTEL_METRICS_EXPORTER Metrics exporter to use otlp console, none
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT Metrics-specific endpoint Uses general endpoint https://metrics.example.com
OTEL_EXPORTER_OTLP_METRICS_PROTOCOL Metrics-specific protocol Uses general protocol http/protobuf
OTEL_EXPORTER_OTLP_METRICS_HEADERS Metrics-specific headers Uses general headers metric-key=value
OTEL_EXPORTER_OTLP_METRICS_INSECURE Metrics-specific insecure setting Uses general setting false
OTEL_EXPORTER_OTLP_METRICS_TIMEOUT Metrics-specific timeout Uses general timeout 60000
OTEL_EXPORTER_OTLP_METRICS_COMPRESSION Metrics-specific compression Uses general compression gzip

Logging Configuration #

Variable Description Example
OTEL_LOG_LEVEL SDK internal log level TRACE, DEBUG, INFO, WARN, ERROR, FATAL
OTEL_LOG_METRICS Enable metrics logging true, 1, yes, on
OTEL_LOG_SPANS Enable spans logging true, 1, yes, on
OTEL_LOG_EXPORT Enable export logging true, 1, yes, on
OTEL_CONSOLE_EXPORTER Add console exporter for debugging true, 1, yes, on

Usage Example with Flutter #

When running a Flutter app:

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://otel-collector:4317 \
  --dart-define=OTEL_EXPORTER_OTLP_PROTOCOL=grpc \
  --dart-define=OTEL_EXPORTER_OTLP_HEADERS=Authorization=your-api-key

Advanced Usage #

Custom Tracing #

void fetchUserData() async {
  final tracer = FlutterOTel.tracer;
  
  final span = tracer.startSpan('fetch_user_data', attributes: {
    'user.id': userId,
    'api.endpoint': '/users',
  });
  
  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();
  }
}

Widget-Level Tracking #

// Track button interactions
ElevatedButton(
  onPressed: handleSubmit,
  child: Text('Submit'),
).withOTelButtonTracking('submit_form');

// Track text field
TextField(
  decoration: const InputDecoration(
    labelText: 'Enter something',
    border: OutlineInputBorder(),
  ),
).withOTelTextFieldTracking('demo_text_field')

// Error boundaries
RiskyWidget().withOTelErrorBoundary('risky_operation');
## Configuration

### Environment Variables

Standard OpenTelemetry environment variables are supported:

```bash
# Exporter endpoint
--dart-define=OTEL_EXPORTER_OTLP_ENDPOINT=https://your-collector:4317

# Protocol selection (default is http/protobuf as per OTel spec)
--dart-define=OTEL_EXPORTER_OTLP_PROTOCOL=grpc

# Service information
--dart-define=OTEL_SERVICE_NAME=my-flutter-app
--dart-define=OTEL_SERVICE_VERSION=1.0.0
```

### Local Development

For local development, run an OpenTelemetry collector on localhost:4317, the default. 

```bash
docker run -p 3000:3000 -p 4317:4317 -p 4318:4318 --rm -ti grafana/otel-lgtm
```

Examples #

Contributing #

We are looking for contributors and maintainers! Please see our Contributing Guide for details.

Development Setup #

# Clone the repository
git clone https://github.com/middleware-labs/flutter-sdk.git
cd flutter-sdk

# Install dependencies
make install

# Run tests
make test

# Run all checks
make all

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 #

Acknowledgments #

Built on the foundation of: