Dartastic OpenTelemetry API for Dart

Pub Version CI coverage License OpenTelemetry API Specification

A Dart implementation of the OpenTelemetry API that strictly adheres to the OpenTelemetry (OTel) specification. This package provides a vendor-neutral, implementation-agnostic API for observability instrumentation in Dart and Flutter applications.


Dartastic.io: AI Code Healing and Real User Monitoring for Flutter

See what your users experience. Find problems with Flutter apps in production. Fix them with AI.

Dartastic.io brings AI code healing, real user monitoring, and OpenTelemetry observability together in one platform built for Dart and Flutter. (Patents pending)

Dartastic AI answering "Why did crashes jump after the last release?" over a Flutter App Health
dashboard

Dartastic AI — From Production Problems to Code Fixes

Dartastic AI analyzes production telemetry alongside your application source code to diagnose problems and propose fixes.

  • Investigate production issues using crashes, errors, traces, metrics, and real user telemetry.
  • Find the offending code by connecting observed problems with your application's source in Dartastic Pub or your source code repos.
  • Generate code patches that address the underlying problems.
  • Create actionable issues in your issue tracker, complete with findings and the proposed code patch.

Dartastic RUM — See What Your Users See

Understand how people actually experience your Flutter application.

  • Replay user sessions in the Dartastic Observatory session player.
  • Choose your replay fidelity, from privacy-preserving widget outlines to pixel-level reproductions of the user experience.
  • Investigate real user problems using session replay and production observability data. Dartastic RUM Session Replay

Dartastic Observatory — Observability Built for Flutter

A complete observability backend where Flutter and Dart are first-class citizens.

  • Flutter-specific dashboards for application performance, health, and reliability.
  • Alerts and notifications through email, Slack, and PagerDuty.
  • Dartastic Cloud Observatory — Start with a 30-day free trial on isolated, shared infrastructure, as low as $39/mo.
  • Dartastic Hosted Observatory — Run the observability stack in a private managed environment, sized from Mini to Enterprise XL.
  • Dartastic Self-Hosted Observatory — The whole platform inside your own network. Your keys, your cluster, with nothing leaving your perimeter.

Dartastic Pro OTel Runtime — Native OpenTelemetry Performance

Collect production telemetry without burdening Flutter's main isolate.

  • Native OpenTelemetry runtime that moves telemetry processing off the main isolate.
  • Native crash reporting for problems that Dart can't catch.
  • Widget performance diagnostics to identify janky widgets.
  • On-device PII filtering before sensitive data leaves your application.
  • Source-level error diagnostics powered by the Dartastic Symbolizer API.
  • Platform-native metrics for iOS, Android, and Linux, including iOS MetricKit and Android Vitals.
  • OpenTelemetry compatibility — Send telemetry to Dartastic Observatory or any OpenTelemetry platform.

Dartastic Labs — OpenTelemetry Integrations

Instrument more of your application without writing everything yourself.

  • 50+ open-source integrations for Dart and Flutter libraries including Dio, Shelf, and Logger. Look for otel_* in pub.flutter-io.cn.
  • 600+ Pro integrations for services and libraries including Anthropic, AWS, Azure, and Stripe.

More Dartastic Tools

Dartastic Pub

A private Dart package registry for sharing packages and plugins with your team, partners, and customers.

Dartastic Symbolizer

Turn production errors into lines of source code through a web API, while keeping your source code artifacts private.

Professional Support and Training

Dartastic.io offers commercial support for the open-source dartastic_opentelemetry and dartastic_opentelemetry_api packages and the complete Dartastic product suite.

  • Professional support with available around-the-clock coverage and response times under four hours on qualifying plans.
  • OpenTelemetry training for teams instrumenting Dart, Flutter, mobile, and web applications.

Open Standards. No Mandatory Backend.

The Dartastic OpenTelemetry API and SDK are open source and standards-based. You can use them independently of Dartastic's commercial services and send telemetry to any compatible OpenTelemetry backend.

Dartastic's commercial products add native performance, advanced diagnostics, session replay, AI-assisted remediation, and managed observability services.

Explore Dartastic.io


API Overview

Developers generally do not code with the API, they code with the SDK via the OTel class. This OpenTelemetry API for Dart exists as a standalone library to strictly adhere to the OpenTelemetry specification which separates API and SDK concerns. The specification requires that the API can be dropped into an app without an SDK and it will work in a no-op fashion.

This API is rarely used without an SDK. The SDK for this API is implemented by dartastic_opentelemetry, the Dartastic OpenTelemetry SDK. To instrument Dart and Flutter apps, include the latest dartastic_opentelemetry and use its OTel class.

About the API - use the SDK

This dartastic_opentelemetry_api OTel API for Dart and Flutter exists as a standalone library to strictly adhere to the OpenTelemetry specification which separates API and the SDK. The specification requires that the API can be dropped into an app without an SDK and it will work in a no-op fashion. You could include just dartastic_opentelemetry_api in your pubspec.yaml to get a no-op implementation as required by the OTel specification, though this would be a rare use case. Typically, instrumenters will include dartastic_opentelemetry in their pubspec.yaml and this dartastic_opentelemetry_api will be a transitive dependency.

Another direct use for this library is for developers who write instrumentation libraries.
This OpenTelemetry API is pluggable. You can create your own OTelFactory to implement your own SDK implementation. See the Dartastic OTel SDK's OTelSDKFactory for an example.

Features

  • ✅ Complete OpenTelemetry API implementation for Dart
  • ✅ Typed OTel Semantic Convention enums for the full OTel registry — attribute keys, attribute values, metrics, events, and entities for all 90 registry namespaces.
  • ✅ Strict adherence to the OpenTelemetry specification
    • All MUST and SHOULD requirements are implemented
    • Most, if not all, MAY requirements are implemented
  • ✅ Supported signal types:
    • Traces
    • Metrics
    • Logs
  • ✅ Fully typed API with strong Dart type safety
  • ✅ Cross-platform compatibility - works across all Dart environments (Servers, Mobile, Web, Desktop)
  • ✅ No-op implementation for safely including in any application
  • ✅ Pluggable API design - create your own SDK implementation using OTelFactory

Demos 🎬

  • Dart OTel Reference Demo The Dart OpenTelemetry Reference Demo A reference implementation for use of this SDK demonstrating well-instrumented:
  • Dart server applications,
  • Dart CLIs
  • Flutter apps
  • Dart CloudRun functions
  • Dart Cloud Functions (Firebase Functions in Dart)
  • Serverpod backends

Getting Started

Typically, you wouldn't use this library and will use Dartastic OTel dartastic_opentelemetry instead to get a working OTel implementation in your Dart or Flutter application.

Installation

Add the package to your pubspec.yaml:

dependencies:
  dartastic_opentelemetry_api: ^0.12.0

Then run:

dart pub get

Using with an SDK

This API is rarely used without an SDK. For a fully functional OpenTelemetry implementation, use one of the following:

  • Dart Backend Applications: Use the Dartastic OTel SDK.
    dependencies:
      dartastic_opentelemetry: ^1.0.0
    

Each layer exports all the relevant classes to the next layer so you only have to include one library in your pubspec.yaml.

Direct API Usage (No-op Mode)

If you need a no-op OpenTelemetry implementation (unusual but compliant with the OTel spec):

dependencies:
  dartastic_opentelemetry_api: ^0.12.0

Usage

The entrypoint for almost all object creation is the OTelAPI class. Again this would rarely be used, instead use the OTel class from dartastic_opentelemetry which has the same methods with additional methods for SDK objects like Resource and SpanProcessor.

All constructors are package-internal except OTelAPIFactory's — use OTelAPI to create API objects. Convenience static factories such as Attributes.of, plus copyWith, copyWithout, and toJson methods, are provided on objects such as Attributes, Baggage, and Context.

In order to strictly comply with the limited types the OpenTelemetry specification allows for Attributes there's no generic OTelAPI.attribute<T> creation method and instead, to provide a typesafe API, there are 8 creation methods for String, bool, int, double and Lists of those types, i.e. OTelAPI.attributeString('foo', 'bar'), OTelAPI.attributeIntList('baz', [1, 2, 3]).

Usage Examples

Basic Tracing Example

This is a no-op when using OTelAPI. Use OTel from the SDK to record real traces.

Prefer typed enum keys over raw strings for attributes. The API ships enums for every namespace in the OTel semantic conventions (Http, Url, Server, Db, User, etc.) For app-specific attributes that aren't in a convention, define your own enum implementing OTelSemantic.

import 'package:dartastic_opentelemetry_api/dartastic_opentelemetry_api.dart';

/// Example-only attribute keys for things not in the OTel semantic
/// conventions. Rename this in your own code (e.g. `CheckoutAttribute`)
/// so the names reflect your domain.
enum ExampleAttribute implements OTelSemantic {
  operationSuccess('operation.success'),
  operationValue('operation.value'),
  retryCount('retry.count'),
  requestDuration('request.duration'),
  requestSuccess('request.success'),
  tags('tags');

  @override
  final String key;
  @override
  String toString() => key;
  const ExampleAttribute(this.key);
}

void main() {
  // Get a tracer.
  final tracer = OTelAPI.tracerProvider().getTracer('example-service');

  // tracer.startSpan() does NOT activate the span (per the OpenTelemetry
  // specification). Use tracer.withSpan to make a span active for a scope
  // so that any spans started inside are parented to it via the active
  // context. Use withSpanAsync for asynchronous scopes.
  final rootSpan = tracer.startSpan('main-operation');
  try {
    tracer.withSpan(rootSpan, () {
      rootSpan.setBoolAttribute(ExampleAttribute.operationSuccess.key, true);

      // Child span — parented to rootSpan via the active context. Wrap
      // each span in its own try/catch/finally so exceptions are recorded
      // on the right span and the span is always ended.
      final childSpan = tracer.startSpan('sub-operation');
      try {
        tracer.withSpan(childSpan, () {
          childSpan.setIntAttribute(ExampleAttribute.operationValue.key, 42);
        });
        // No setStatus(SpanStatusCode.Ok) here: Ok is the default, so
        // only Error needs to be set explicitly.
      } catch (e, stackTrace) {
        // Per the OTel spec: recordException first, then setStatus(Error).
        childSpan.recordException(e, stackTrace: stackTrace);
        childSpan.setStatus(SpanStatusCode.Error, e.toString());
        rethrow;
      } finally {
        childSpan.end();
      }
    });
  } catch (e, stackTrace) {
    rootSpan.recordException(e, stackTrace: stackTrace);
    rootSpan.setStatus(SpanStatusCode.Error, e.toString());
    rethrow;
  } finally {
    rootSpan.end();
  }
}

Using Context and Baggage

import 'package:dartastic_opentelemetry_api/dartastic_opentelemetry_api.dart';

void main() {
  // Create baggage with user info
  Baggage baggage = OTelAPI.baggageForMap({
    'userId': 'user-123',
    'tenant': 'example-tenant'
  });

  // Create a context with this baggage
  Context context = OTelAPI.context(baggage: baggage);
  
  // Activate the context for a scope. runSync uses Zones, so the baggage
  // propagates to nested code, including any async callbacks started inside.
  context.runSync(() {
    // Read baggage off the active context.
    final currentBaggage = Context.current.baggage;
    final userId = currentBaggage?.getEntry('userId')?.value;
  });
}

Running traced work in another isolate

Isolates share no mutable state, so context and configuration do not cross the boundary on their own. Context.runIsolate carries them for you — the OTel configuration, the active context (the propagated SpanContext arrives isRemote: true, like a context extracted from W3C headers), and your installed error handler:

final result = await Context.current.runIsolate(() async {
  // OTel is configured, Context.current is this context, and the
  // error handler behaves as in the parent.
  return heavyComputation();
});

Isolates spawned directly (Isolate.spawn, compute) start unconfigured. See doc/isolates.md for the full story, including the SendPort pattern for aggregating error reports across isolates.

Error handling

Per the OpenTelemetry error-handling principles, the API never throws into application code when misused — invalid input degrades safely and is reported. Where those reports go is yours to configure:

// Default: reports are logged via OTelLog and never throw.
// Route them to your own sink instead:
OTelAPI.setErrorHandler((error, stackTrace) {
  myTelemetryHealthMonitor.record(error, stackTrace);
});

// Strict mode for development — crash on any OTel misuse:
OTelAPI.setErrorHandler((error, stackTrace) =>
    Error.throwWithStackTrace(error, stackTrace ?? StackTrace.current));

// Passing null restores the default handler.
OTelAPI.setErrorHandler(null);

The handler receives the library's internal error reports (invalid attribute keys, malformed input, dropped data). Exceptions thrown by your own code inside withSpan blocks are never routed here — they always rethrow; how they are recorded on the span is controlled by the SDK's SpanExceptionOptions.

Like every other OpenTelemetry global, the handler is held by the installed OTelFactory: setErrorHandler stores it on the factory — buffering it until one is installed, so the call is safe in any order relative to initialize() — and passing null returns to the factory's overridable defaultErrorHandler (logging via OTelLog, unless an SDK factory substitutes its own). Factories are per-isolate; Context.runIsolate re-installs your handler in child isolates (doc/isolates.md).

Working with Attributes

import 'package:dartastic_opentelemetry_api/dartastic_opentelemetry_api.dart';

void main() {

  // Using of
  Attributes equalToTheAbove = Attributes.of({
    'example_string_key': 'foo',
    'example_double_key': 42.1,
    'example_bool_list_key': [true, false, true],
    'example_int_list_key': [42, 43, 44],
  });

  // Using the typesafe API methods. Mix API convention enums (e.g.
  // ServiceResource) with your own enum (ExampleAttribute) for
  // non-convention keys — never use raw strings.
  Attributes attributes = OTelAPI.attributes([
    OTelAPI.attributeString(Service.serviceName.key, 'payment-processor'),
    OTelAPI.attributeInt(ExampleAttribute.retryCount.key, 3),
    OTelAPI.attributeDouble(ExampleAttribute.requestDuration.key, 0.125),
    OTelAPI.attributeBool(ExampleAttribute.requestSuccess.key, true),
    OTelAPI.attributeStringList(
        ExampleAttribute.tags.key, ['payment', 'critical']),
  ]);

  // Using attributesFromSemanticMap — same typed-enum principle, no
  // `.key` accessor on each entry. Mixes any combination of
  // OTel-spec semconv enums with your own ExampleAttribute-style enums.
  Attributes fromMap = OTelAPI.attributesFromSemanticMap({
    Http.httpRequestMethod: 'GET',
    Url.urlFull: 'https://api.example.com/users',
    Http.httpResponseStatusCode: 200,
    Deployment.deploymentEnvironmentName: 'production',
    User.userRoles: ['admin', 'operator'],
  });
}

Working with logging

This is a no-op when using OTelAPI. Use the OTel SDK to emit real logs.

import 'package:dartastic_opentelemetry_api/dartastic_opentelemetry_api.dart';

final otelLoggerProvider = OTelAPI.loggerProvider();
final otelLogger = otelLoggerProvider.getLogger('dart-otel-api-faux-db-service');
final attrs = OTelAPI.attributesFromSemanticMap({
  Db.dbOperationName: 'update',
  Db.dbCollectionName: 'orders',
  Db.dbResponseReturnedRows: 3,
});

otelLogger.emit(
  eventName: 'order_update',
  severityNumber: Severity.INFO,
  body: 'Order update completed.',
  attributes: attrs,
);

See the /example folder for more complete examples.

API Overview

Main API Components

  • OTelAPI - The main entry point for creating API objects
  • Tracer - Creates spans for tracing operations
  • Span - Represents a unit of work or operation
  • Context - Carries execution metadata across API boundaries
  • Baggage - Provides a mechanism to propagate key-value pairs alongside a context
  • Attributes - Represent key-value pairs with a known set of value types

Important OTelAPI Methods

The entrypoint for almost all object creation is the OTelAPI class. In real applications, you would typically use the OTel class from an SDK implementation.

// Get the tracer provider
APITracerProvider provider = OTelAPI.tracerProvider();

// Create baggage
Baggage baggage = OTelAPI.baggageForMap({'userId': 'user-123'});

// Create a context carrying the baggage
Context context = OTelAPI.context(baggage: baggage);

// Create attributes
Attribute attr1 = OTelAPI.attributeString('key', 'value');
Attribute attr2 = OTelAPI.attributeInt('count', 42);
Attributes attributes = OTelAPI.attributes([attr1, attr2]);

CNCF Contribution and Alignment

This project aims to align with Cloud Native Computing Foundation (CNCF) best practices:

  • Interoperability - Works with the broader OpenTelemetry ecosystem
  • Specification compliance - Strictly follows the OpenTelemetry specification
  • Vendor neutrality - Provides a foundation for any OpenTelemetry SDK implementation

For Instrumentation Library Developers

This API can be used directly by those writing instrumentation libraries. By coding against this API rather than a specific SDK implementation, your instrumentation will work with any compliant OpenTelemetry SDK.

To create your own SDK implementation, implement the OTelFactory interface. See the Dartastic OTel SDK's OTelSDKFactory for an example.

AI Usage

Practically all code in Dartastic was originally generated by Claude. EVERY character is reviewed by a human for and checked for compliance with the OTel spec.

Additional Resources

License

Apache 2.0 - See the LICENSE file for details.

Acknowledgements

This Dart API and the Dartastic SDK are made with 💙 by Michael Bushe at Dartastic.io.

Libraries

dartastic_opentelemetry_api
OpenTelemetry API for Dart