comon_otel 0.0.1-alpha.1
comon_otel: ^0.0.1-alpha.1 copied to clipboard
OpenTelemetry SDK foundation for Dart with traces, metrics, logs, context propagation, and OTLP exporters.
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.