otel_riverpod 0.2.0 copy "otel_riverpod: ^0.2.0" to clipboard
otel_riverpod: ^0.2.0 copied to clipboard

OpenTelemetry instrumentation for `package:riverpod`. A ProviderObserver emitting short-lived spans for provider add, update, fail and dispose, so you can see your state graph.

example/otel_riverpod_example.dart

// Licensed under the Apache License, Version 2.0
// Copyright 2025, Mindful Software LLC, All rights reserved.

/// Runnable demo of `otel_riverpod`.
///
/// Initializes the OpenTelemetry SDK, installs [OTelRiverpodObserver]
/// on a [ProviderContainer], and drives one scenario per observer
/// hook — added, updated, family argument, auto-dispose, and a
/// failing provider.
///
/// Spans export to the standard OTLP endpoint (`http://localhost:4318`
/// by default; override with `OTEL_EXPORTER_OTLP_ENDPOINT`). Point it
/// at any OTLP-capable collector or backend and open the
/// `riverpod-otel-example` service to see the trace waterfall.
///
///   dart run example/otel_riverpod_example.dart
library;

import 'dart:async';

import 'package:dartastic_opentelemetry/dartastic_opentelemetry.dart';
import 'package:otel_riverpod/otel_riverpod.dart';
import 'package:riverpod/riverpod.dart';

Future<void> main() async {
  await OTel.initialize(
    serviceName: 'riverpod-otel-example',
    serviceVersion: '0.0.1',
  );

  // `recordValues: true` is fine for a demo — production apps should
  // leave it `false` because provider state often carries user data.
  final container = ProviderContainer(
    observers: [OTelRiverpodObserver(recordValues: true)],
  );

  await OTel.tracer().startActiveSpanAsync<void>(
    name: 'run-scenarios',
    fn: (_) async {
      // provider.added:<name> — didAddProvider on first read.
      await _scenario('plain-provider-add', () {
        final greeting = Provider<String>((_) => 'hello', name: 'greeting');
        container.read(greeting);
      });

      // provider.updated:<name> — didUpdateProvider per state change.
      await _scenario('notifier-update', () async {
        container.read(_counterProvider);
        container.read(_counterProvider.notifier).increment();
        container.read(_counterProvider.notifier).increment();
        // Let observer notifications drain.
        await Future<void>.delayed(Duration.zero);
      });

      // riverpod.provider.family + riverpod.provider.argument attrs.
      await _scenario('family-with-argument', () {
        container.read(_doubler(21));
      });

      // provider.disposed:<name> — auto-dispose fires on the next
      // microtask after the last listener drops.
      await _scenario('auto-dispose-and-recycle', () async {
        final sub = container.listen<int>(_ephemeral, (_, __) {});
        sub.close();
        await Future<void>.delayed(Duration.zero);
      });

      // provider.failed:<name> — status=Error + recordException event.
      await _scenario('failing-provider', () {
        try {
          container.read(_explodes);
        } on Object catch (e) {
          // The observer has already recorded the failure span; just
          // don't let it crash the demo.
          print('  expected failure: ${e.toString().split('\n').first}');
        }
      });

      // Dispose inside the parent span so the didDisposeProvider
      // spans join the `run-scenarios` trace rather than orphaning.
      container.dispose();
    },
  );

  await OTel.tracerProvider().forceFlush();
  await OTel.shutdown();
  print('done — inspect service `riverpod-otel-example` in your '
      'OTLP backend.');
}

Future<void> _scenario(String name, FutureOr<void> Function() body) async {
  print('--> $name');
  await OTel.tracer().startActiveSpanAsync<void>(
    name: name,
    fn: (_) async {
      await body();
    },
  );
}

final _counterProvider =
    NotifierProvider<_Counter, int>(_Counter.new, name: 'counter');

final _doubler = Provider.family<int, int>(
  (_, arg) => arg * 2,
  name: 'doubler',
);

final _ephemeral = Provider.autoDispose<int>(
  (_) => DateTime.now().millisecondsSinceEpoch,
  name: 'ephemeral',
);

final _explodes = Provider<int>(
  (_) => throw StateError('intentional demo failure'),
  name: 'explodes',
);

class _Counter extends Notifier<int> {
  @override
  int build() => 0;

  void increment() => state++;
}
0
likes
160
points
85
downloads

Documentation

API reference

Publisher

verified publisherdartastic.io

Weekly Downloads

OpenTelemetry instrumentation for `package:riverpod`. A ProviderObserver emitting short-lived spans for provider add, update, fail and dispose, so you can see your state graph.

Homepage
Repository (GitHub)
View/report issues

License

Apache-2.0 (license)

Dependencies

dartastic_opentelemetry, dartastic_opentelemetry_api, riverpod

More

Packages that depend on otel_riverpod