comon_otel
OpenTelemetry SDK foundation for Dart with traces, metrics, logs, propagation, and OTLP exporters behind a single public entrypoint.
This package lives in the monorepo at packages/comon_otel.
Features
Otel.init()bootstrap- zone-based active span propagation
TracerProvider,Tracer,Span,SpanContext, andSpanLinkMeterProvider,Meter, counters, histograms, and observable instrumentsLoggerProvider,OtelLogger, and log processors- W3C trace-context propagation, W3C baggage, B3, and composite propagators
- Baggage storage in zone context
- sync and async tracing helpers
- function extensions for ergonomic tracing
- console exporters for traces, metrics, and logs
- in-memory exporters for tests
- OTLP HTTP JSON, OTLP HTTP/protobuf, and OTLP gRPC exporters
Installation
dart pub add comon_otel
Quick Start
import 'package:comon_otel/comon_otel.dart';
Future<void> main() async {
await Otel.init(
serviceName: 'my-app',
exporter: OtelExporter.console,
);
final requests = Otel.instance.meter.createIntCounter('requests.total');
final userId = await Otel.instance.tracer.traceAsync<String>(
'load-user',
fn: () async {
requests.add(1, attributes: const <String, Object>{'route': '/user'});
Otel.instance.logger.info('Loading user');
return '42';
},
);
await Otel.shutdown();
print('Loaded user: $userId');
}
Scope
0.0.1-alpha.1 is intended as a foundation alpha for Dart services and libraries.
Supported in this alpha:
- traces
- metrics
- logs
- instrumentation scope metadata on tracer and meter acquisition, exported span and metric models, and OTLP payloads
- basic metric cardinality limiting with overflow aggregation for sync and async instruments
- context propagation with W3C trace-context, W3C baggage, B3, and composite propagators
- OTLP HTTP JSON exporters
- OTLP HTTP/protobuf exporters
- OTLP gRPC exporters
- batching, periodic metric reads, in-memory testing helpers, and collector-backed integration coverage
What this alpha is for:
- validating public API direction
- validating OTLP transport behavior against a real collector
- letting early adopters instrument Dart services and internal libraries
Not yet claimed in this alpha:
- full OpenTelemetry specification coverage
- finalized long-term API stability
- complete production bootstrap from environment variables alone
Configuration
Otel.init(...) gives you a compact programmatic surface for the most common
setup tasks:
serviceNameexporterendpointresourcespanProcessorsmetricReaderslogProcessorssamplerspanLimits
The package also supports environment-driven bootstrap for a useful subset of
OTEL_* settings, described later in the Environment Variables section.
Known Limitations
Known gaps that are intentionally still open after this alpha:
- environment bootstrap is partial: some
OTEL_*settings are implemented, but not the full expected env surface yet - default resource detection is intentionally minimal and currently focuses on process, runtime, OS, and host basics
- metric cardinality limiting is currently basic: it is configured only at the
MeterProvider/Otel.init(...)level, not per reader or via Views - OTLP spec fidelity still has gaps around advanced exporter configurability and some exporter contract details
- views, exemplars, Prometheus, Zipkin, declarative configuration, and compatibility layers are intentionally out of scope for
0.0.1-alpha.1
Bootstrap Defaults
The package now exposes a global propagator registry through Otel.propagator, Otel.setPropagator(...), and Otel.resetPropagator().
Default behavior:
Otel.init()installs a default composite propagator with W3C trace-context and W3C baggageOTEL_PROPAGATORScan override the global propagator during init- supported env propagator values are
tracecontext,baggage,b3,b3multi, andnone OTEL_SDK_DISABLED=truedisables telemetry export and switches tracing to non-recording spans- when
serviceNameis omitted andOTEL_SERVICE_NAMEis not set, the SDK falls back tounknown_service:<runtime> Resource.autoDetect(...)now applies minimal built-in process and host detectors, and also accepts customResourceDetectorimplementationsOtel.init(metricCardinalityLimit: ...)applies a default per-instrument metric series cap and routes excess attribute sets into an overflow series withotel.metric.overflow=true
Public Entry Point
The package exposes a single public entry point:
import 'package:comon_otel/comon_otel.dart';
Internally, the package uses a single source aggregator at lib/src/comon_otel.dart.
Each src/* directory with multiple files exposes its own local barrel file, and that internal aggregator re-exports those folder-level barrels.
Propagation
final carrier = <String, String>{};
await Otel.instance.tracer.traceAsync('request', fn: () async {
final baggage = Baggage.empty().withEntry('tenant.id', 'acme');
OtelContext.withBaggage(baggage, () {
const CompositePropagator(<TextMapPropagator>[
W3CTraceContextPropagator(),
W3CBaggagePropagator(),
]).inject(OtelContext.current, carrier);
});
});
final extracted = const W3CTraceContextPropagator().extract(carrier);
final child = Otel.instance.tracer.startSpan(
'downstream',
parentContext: extracted.spanContext,
);
await child.end();
final manualRemote = OtelContextSnapshot.remote(
traceId: const TraceId('4bf92f3577b34da6a3ce929d0e0e4736'),
spanId: const SpanId('00f067aa0ba902b7'),
traceFlags: TraceFlags.sampled,
traceState: const TraceState('vendor=value'),
);
const W3CTraceContextPropagator().inject(manualRemote, carrier);
Span Links
final link = SpanLink(
context: SpanContext.local(
traceId: const TraceId('4bf92f3577b34da6a3ce929d0e0e4736'),
spanId: const SpanId('00f067aa0ba902b7'),
traceFlags: TraceFlags.sampled,
),
attributes: <String, Object>{'batch.id': 'import-42'},
);
await Otel.instance.tracer.traceAsync(
'process-item',
links: <SpanLink>[link],
fn: () async {
Otel.instance.logger.info('linked work item');
},
);
Span links are preserved on exported SpanData and included in OTLP JSON and protobuf span payloads.
Testing
final helper = await OtelTestHelper.setup();
await Otel.instance.tracer.traceAsync('test-operation', fn: () async {
Otel.instance.logger.info('inside test');
});
await Otel.forceFlush();
expect(helper.spanExporter.lastSpanNamed('test-operation'), isNotNull);
expect(helper.logExporter.logs.single.body, 'inside test');
Otel.forceFlush() now waits for in-flight simple span and log exports in addition to batch processors and metric readers.
Collector-backed integration coverage is also available:
dart test -t integration test/otlp_collector_integration_test.dart
That suite starts a real otel/opentelemetry-collector-contrib container through Docker, exports telemetry over OTLP HTTP/protobuf and OTLP gRPC, and verifies collector-observed traces, metrics, and logs from the files written by the collector.
It also includes wire-level integration coverage for:
- per-signal OTLP HTTP endpoints
- per-signal OTLP headers
- per-signal OTLP compression overrides
- per-signal OTLP timeout overrides
- OTLP HTTP/protobuf retry recovery when the collector becomes available late
- OTLP gRPC retry recovery when the collector becomes available late
Matcher helpers are also exported for test assertions:
expect(
helper.spanExporter.spans,
contains(allOf(hasSpanNamed('test-operation'), hasStatus(SpanStatus.ok))),
);
expect(
helper.metricExporter.metrics,
contains(allOf(
hasMetricNamed('requests.total'),
hasMetricType(MetricInstrumentType.counter),
)),
);
final extracted = const CompositePropagator(<TextMapPropagator>[
W3CTraceContextPropagator(),
W3CBaggagePropagator(),
]).extract(<String, String>{
'traceparent': '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01',
'baggage': 'tenant.id=acme',
});
final expected = OtelContextSnapshot.remote(
traceId: const TraceId('4bf92f3577b34da6a3ce929d0e0e4736'),
spanId: const SpanId('00f067aa0ba902b7'),
traceFlags: TraceFlags.sampled,
);
expect(extracted, hasRemoteSpanContext(sampled: true));
expect(extracted.traceIdValue, expected.traceIdValue);
expect(extracted, hasBaggageEntry('tenant.id', 'acme'));
Semantic Attributes
The public SemanticAttributes surface now includes common keys for:
- service metadata
- HTTP
- database operations
- RPC
- network
- exception details
- Flutter-specific attributes
Metrics Semantics
Current metrics behavior is now closer to OpenTelemetry expectations:
- counters and up-down counters are aggregated cumulatively by attribute set
- histograms emit aggregated count, sum, min, max, and bucket data
- metric points can carry start timestamps for cumulative temporality
- OTLP metric export uses temporality and monotonic flags from the metric model
Integration Contracts
Core now exports stable building blocks for future integration packages:
OtelDatabaseMixinfor wrapping database operations in spans, metrics, and error logsOtelDbMetricsfor reusable database metric instrumentsSlowQueryDetectorfor threshold-based slow query loggingOtelLogBridgeandOtelLogExtensionfor adapting external logging packages
Composite Exporters
Core now includes composite exporters for fan-out delivery to multiple backends:
CompositeSpanExporterCompositeMetricExporterCompositeLogExporter
They return ExportResult.failure if any child exporter fails.
Isolate Support
Core now includes OtelIsolate and OtelIsolateContext for explicit context transfer across isolate boundaries.
OtelIsolate.captureCurrent()snapshots the active span context and baggage into a sendable message shapeOtelIsolate.run(...)restores baggage automatically inside the isolate callback- if the isolate runtime initializes
Otel,spanNamecan create a child span linked throughparentContext
OTLP HTTP JSON
await Otel.init(
serviceName: 'my-app',
endpoint: 'https://collector.example.com',
tracesEndpoint: 'https://traces.example.com/v1/traces',
metricsEndpoint: 'https://metrics.example.com/v1/metrics',
logsEndpoint: 'https://logs.example.com/v1/logs',
exporter: OtelExporter.otlpHttpJson,
otlpCompression: OtlpCompression.gzip,
otlpHeaders: <String, String>{
'authorization': 'Bearer <token>',
},
otlpTracesHeaders: <String, String>{
'x-trace-tenant': 'payments',
},
otlpTimeout: const Duration(seconds: 10),
otlpTracesTimeout: const Duration(seconds: 3),
otlpMetricsCompression: OtlpCompression.none,
otlpRetry: const OtlpRetryConfig(
maxAttempts: 3,
initialDelay: Duration(milliseconds: 200),
),
otlpTracesRetry: const OtlpRetryConfig(
maxAttempts: 5,
initialDelay: Duration(milliseconds: 100),
),
);
This uses POST requests to:
/v1/traces/v1/metrics/v1/logs
Current OTLP JSON encoding also includes:
- numeric span kind and status codes
sumpayloads for counters and up-down countershistogrampayloads with count, sum, min, max, explicit bounds, and bucket counts- grouped resources and scopes for traces, metrics, and logs
- default
user-agentheader emission for OTLP requests - optional gzip request compression via
otlpCompressionorOTEL_EXPORTER_OTLP_COMPRESSION=gzip - retry/backoff for retryable HTTP responses and transient transport failures, including
Retry-Afteron throttling responses - partial success handling for HTTP JSON and HTTP protobuf success responses without retrying accepted payloads
- resource-level SchemaURL propagation via
resourceSchemaUrlinOtel.init(...) - per-signal header overrides via
otlpTracesHeaders,otlpMetricsHeaders, andotlpLogsHeaders - per-signal timeout overrides via
otlpTracesTimeout,otlpMetricsTimeout, andotlpLogsTimeout - per-signal compression overrides via
otlpTracesCompression,otlpMetricsCompression, andotlpLogsCompression - per-signal retry overrides via
otlpTracesRetry,otlpMetricsRetry, andotlpLogsRetry
OTLP HTTP Protobuf
await Otel.init(
serviceName: 'my-app',
endpoint: 'https://collector.example.com',
exporter: OtelExporter.otlpHttp,
otlpHeaders: <String, String>{
'authorization': 'Bearer <token>',
},
otlpCompression: OtlpCompression.gzip,
otlpTimeout: const Duration(seconds: 10),
);
This uses OTLP over HTTP with application/x-protobuf request bodies and the same per-signal endpoint, header, timeout, compression, and retry configuration surface used by the JSON exporter.
OTLP gRPC
await Otel.init(
serviceName: 'my-app',
endpoint: 'http://localhost:4317',
exporter: OtelExporter.otlpGrpc,
otlpHeaders: <String, String>{
'x-tenant': 'payments',
},
otlpCompression: OtlpCompression.gzip,
otlpTimeout: const Duration(seconds: 5),
);
The default gRPC transport routes signals to the standard collector services:
/opentelemetry.proto.collector.trace.v1.TraceService/Export/opentelemetry.proto.collector.metrics.v1.MetricsService/Export/opentelemetry.proto.collector.logs.v1.LogsService/Export
It supports shared and per-signal headers, timeouts, compression, retry configuration, and resource-level SchemaURL propagation, and can be replaced in tests with an injected otlpGrpcTransport.
It also handles OTLP gRPC partial success responses without retrying the accepted payload.
Environment Variables
Supported environment-driven config currently includes:
OTEL_SDK_DISABLEDOTEL_PROPAGATORSOTEL_EXPORTER_OTLP_ENDPOINTOTEL_EXPORTER_OTLP_TRACES_ENDPOINTOTEL_EXPORTER_OTLP_METRICS_ENDPOINTOTEL_EXPORTER_OTLP_LOGS_ENDPOINTOTEL_EXPORTER_OTLP_PROTOCOL=http/jsonOTEL_EXPORTER_OTLP_PROTOCOL=http/protobufOTEL_EXPORTER_OTLP_PROTOCOL=grpcOTEL_EXPORTER_OTLP_TIMEOUTOTEL_EXPORTER_OTLP_TRACES_TIMEOUTOTEL_EXPORTER_OTLP_METRICS_TIMEOUTOTEL_EXPORTER_OTLP_LOGS_TIMEOUTOTEL_EXPORTER_OTLP_COMPRESSION=gzipOTEL_EXPORTER_OTLP_TRACES_COMPRESSIONOTEL_EXPORTER_OTLP_METRICS_COMPRESSIONOTEL_EXPORTER_OTLP_LOGS_COMPRESSIONOTEL_EXPORTER_OTLP_HEADERSOTEL_EXPORTER_OTLP_TRACES_HEADERSOTEL_EXPORTER_OTLP_METRICS_HEADERSOTEL_EXPORTER_OTLP_LOGS_HEADERSOTEL_RESOURCE_ATTRIBUTESOTEL_BSP_SCHEDULE_DELAYOTEL_BSP_EXPORT_TIMEOUTOTEL_BSP_MAX_QUEUE_SIZEOTEL_BSP_MAX_EXPORT_BATCH_SIZEOTEL_BLRP_SCHEDULE_DELAYOTEL_BLRP_EXPORT_TIMEOUTOTEL_BLRP_MAX_QUEUE_SIZEOTEL_BLRP_MAX_EXPORT_BATCH_SIZEOTEL_METRIC_EXPORT_INTERVALOTEL_METRIC_EXPORT_TIMEOUTOTEL_TRACES_SAMPLEROTEL_TRACES_SAMPLER_ARGOTEL_SPAN_ATTRIBUTE_COUNT_LIMITOTEL_SPAN_EVENT_COUNT_LIMITOTEL_SPAN_LINK_COUNT_LIMITOTEL_EVENT_ATTRIBUTE_COUNT_LIMITOTEL_LINK_ATTRIBUTE_COUNT_LIMIT
Programmatic values override environment values where both are provided.
Signal-specific headers override shared OTLP headers for the matching signal only.
Signal-specific timeouts override the shared OTLP timeout for the matching signal only.
Signal-specific compression overrides the shared OTLP compression for the matching signal only.
Explicit SpanLimits passed into Otel.init(...) override env-derived span limit values for the fields you set directly.
Advanced
- spec-compliance-matrix.md for feature-by-feature notes on implementation status
- CHANGELOG.md for release history
README.mdsections below for OTLP HTTP JSON, OTLP HTTP Protobuf, gRPC, and environment configuration details
Ecosystem
- ../comon_otel_flutter/README.md: Flutter lifecycle, navigation, performance, interaction, and error instrumentation
- ../comon_otel_dio/README.md: Dio client spans and outgoing HTTP propagation
Status
Implementation now includes tracing, metrics, logging, batch processors, periodic readers, baggage, and propagation foundations. OTLP exporters and environment-driven config are now in place across HTTP JSON, HTTP protobuf, and gRPC transports.