Line data Source code
1 : // Copyright The OpenTelemetry Authors
2 : // SPDX-License-Identifier: Apache-2.0
3 :
4 : import 'dart:async';
5 : import 'dart:typed_data';
6 :
7 : import 'package:meta/meta.dart';
8 :
9 : import '../dartastic_opentelemetry.dart';
10 :
11 : /// Main entry point for the OpenTelemetry SDK.
12 : ///
13 : /// The [OTel] class provides static methods for initializing the SDK and
14 : /// creating OpenTelemetry objects such as Tracers, Spans, Meters, and other
15 : /// components necessary for instrumentation.
16 : ///
17 : /// To use the SDK, you must first call [initialize] to set up the global
18 : /// configuration and install the SDK implementation. After initialization,
19 : /// you can use the various factory methods to create OpenTelemetry objects.
20 : ///
21 : /// Example usage:
22 : /// ```dart
23 : /// await OTel.initialize(
24 : /// serviceName: 'my-service',
25 : /// serviceVersion: '1.0.0',
26 : /// endpoint: 'https://otel-collector.example.com:4317',
27 : /// );
28 : ///
29 : /// final tracer = OTel.tracer();
30 : /// final span = tracer.startSpan('my-operation');
31 : /// // ... perform work ...
32 : /// span.end();
33 : /// ```
34 : ///
35 : /// The resources from platform resource detection are merged
36 : /// with resource attributes with resource attributes taking priority.
37 : /// The values must be valid Attribute types (String, bool, int, double, or
38 : /// List\<String>, List\<bool>, List\<int> or List\<double>).
39 : class OTel {
40 : static OTelSDKFactory? _otelFactory;
41 : static Sampler? _defaultSampler;
42 : static TimeProvider? _defaultTimeProvider;
43 :
44 : /// The global default exception handling options applied by the withSpan
45 : /// family of methods. Configured via `OTel.initialize(...)` and propagated
46 : /// to TracerProviders. Per-call `exceptionOptions` override this. Null
47 : /// until set by initialize(); the Tracer falls back to
48 : /// [SpanExceptionOptions.defaults] when unset.
49 : static SpanExceptionOptions? _defaultSpanExceptionOptions;
50 :
51 : /// Whether print interception is enabled (set via initialize).
52 : static bool _logPrintEnabled = false;
53 :
54 : /// OTelLogger name for print interception (set via initialize).
55 : static String _logPrintLoggerName = 'dart.print';
56 :
57 : /// Lazily initialized DartLogBridge for print interception.
58 : static DartLogBridge? _logBridge;
59 :
60 : /// Lazily initialized zone specification for print interception.
61 : static ZoneSpecification? _printInterceptionZoneSpec;
62 :
63 : /// Default resource for the SDK.
64 : ///
65 : /// This is set during initialization and used by tracer and meter providers
66 : /// that don't have a specific resource set.
67 : static Resource? defaultResource;
68 :
69 : /// Default service name used if none is provided.
70 : static const defaultServiceName = '@dart/dartastic_opentelemetry';
71 :
72 : /// Default OTEL endpoint.
73 : ///
74 : /// Defaults to the OTLP/HTTP port (4318) since http/protobuf is the default
75 : /// protocol per the OpenTelemetry specification. When using gRPC, this is
76 : /// replaced by [defaultGrpcEndpoint] (port 4317).
77 : static const defaultEndpoint = 'http://localhost:4318';
78 :
79 : /// Default OTLP/gRPC endpoint.
80 : ///
81 : /// Per the OpenTelemetry specification
82 : /// (specification/protocol/exporter.md#configuration-options), the default
83 : /// endpoint is `http://localhost:4317` for OTLP/gRPC and
84 : /// `http://localhost:4318` for the two HTTP protocols. The endpoint default
85 : /// is picked per signal after the protocol is resolved (issue #220).
86 : static const defaultGrpcEndpoint = 'http://localhost:4317';
87 :
88 : /// Default tracer name used if none is provided.
89 : static const String _defaultTracerName = 'dartastic';
90 :
91 : /// Default tracer name that can be customized.
92 37 : static String defaultTracerName = _defaultTracerName;
93 :
94 : /// Default tracer version.
95 : static String defaultTracerVersion = '1.0.0';
96 :
97 : /// Initializes the OpenTelemetry SDK with the specified configuration.
98 : ///
99 : /// This method must be called before any other OpenTelemetry operations.
100 : /// It sets up the global configuration and installs the SDK implementation.
101 : ///
102 : /// @param endpoint The endpoint URL for the OpenTelemetry collector (default: http://localhost:4318)
103 : /// @param secure Transport security for OTLP/gRPC when the endpoint carries
104 : /// no scheme. This is the code equivalent of `OTEL_EXPORTER_OTLP_INSECURE`
105 : /// (inverted - `secure: true` means `insecure=false`) and takes precedence
106 : /// over it, per the spec rule that every environment variable has a direct
107 : /// code configuration equivalent.
108 : ///
109 : /// The option is deliberately narrow, per the OTLP exporter specification
110 : /// (`protocol/exporter.md`, "Insecure"):
111 : ///
112 : /// - **OTLP/HTTP ignores it entirely** - the endpoint's scheme always
113 : /// decides, so `https://host:4318` is TLS and `http://host:4318` is not.
114 : /// - **A scheme on the endpoint wins over this parameter.** The spec says
115 : /// an `https` or `http` scheme "takes precedence over the `insecure`
116 : /// configuration setting", so passing `secure: true` alongside
117 : /// `http://host:4317` does not produce TLS.
118 : /// - It therefore applies only to **OTLP/gRPC with a scheme-less
119 : /// endpoint**, such as `my-collector:4317` - the native gRPC form, and
120 : /// the one case where nothing else can express the choice.
121 : ///
122 : /// Leave it null (the default) to let the endpoint scheme decide, then
123 : /// `OTEL_EXPORTER_OTLP_INSECURE`, then the spec default. That default is
124 : /// *secure*, but the default endpoint is `http://localhost:4317`, whose
125 : /// scheme wins - so an unconfigured SDK talks plaintext to a local
126 : /// collector, which is what you want for local development.
127 : /// @param serviceName Name that uniquely identifies the service (default: "@dart/dartastic_opentelemetry")
128 : /// @param serviceVersion Version of the service (defaults to the OTel spec version)
129 : /// @param tracerName Name of the default tracer (default: "dartastic")
130 : /// @param tracerVersion Version of the default tracer (default: null)
131 : /// @param resourceAttributes Additional attributes for the resource
132 : /// @param spanProcessor Custom span processor (default: BatchSpanProcessor with OtlpGrpcSpanExporter)
133 : /// @param sampler Sampling strategy to use (default: ParentBased(root=AlwaysOn) per the spec)
134 : /// @param spanKind Default span kind (default: SpanKind.server)
135 : /// @param metricExporter Custom metric exporter for metrics
136 : /// @param metricReader Custom metric reader for metrics
137 : /// @param exemplarFilter Custom exemplar filter for metrics
138 : /// @param enableMetrics Whether to enable metrics collection (default: true)
139 : /// @param enableLogs Whether to enable logs collection and auto-configure exporter (default: true).
140 : /// When enabled, the logs exporter is configured based on OTEL_LOGS_EXPORTER env var.
141 : /// @param logRecordExporter Custom log record exporter (overrides OTEL_LOGS_EXPORTER)
142 : /// @param logRecordProcessor Custom log record processor (overrides auto-configuration)
143 : /// @param detectPlatformResources Whether to detect platform resources (default: true)
144 : /// @param logPrint Whether to intercept print() calls and route them to OTel logs (default: false).
145 : /// When enabled, all print() calls within [runWithPrintInterception] will be captured
146 : /// as INFO level logs. Set to true to automatically bridge print statements to OpenTelemetry.
147 : /// @param logPrintLoggerName OTelLogger name for print-intercepted logs (default: 'dart.print')
148 : /// @param timeProvider Clock used for span start, end, and event timestamps.
149 : /// When omitted, defaults to the platform-aware `defaultTimeProvider`:
150 : /// `SystemTimeProvider` (`DateTime.now`, microsecond floor) on native;
151 : /// `WebTimeProvider` (`window.performance.now()` + `timeOrigin`, sub-
152 : /// millisecond) on Dart-on-JS web and Wasm — so web users pick up sub-
153 : /// ms span timing automatically with no opt-in. Pass a custom provider
154 : /// only to override the platform default, e.g. a fake clock in tests.
155 : /// @param oTelFactoryCreationFunction Factory function for creating OTelSDKFactory instances
156 : /// @return A Future that completes when initialization is done
157 : /// @throws StateError if called more than once
158 : /// @throws ArgumentError if required parameters are invalid
159 153 : static Future<void> initialize({
160 : String? endpoint,
161 : bool? secure,
162 : String? serviceName,
163 : String? serviceVersion,
164 : String? tracerName,
165 : String? tracerVersion,
166 : Attributes? resourceAttributes,
167 : SpanProcessor? spanProcessor,
168 : Sampler? sampler,
169 : SpanExceptionOptions spanExceptionOptions = const SpanExceptionOptions(),
170 : SpanKind spanKind = SpanKind.server,
171 : MetricExporter? metricExporter,
172 : MetricReader? metricReader,
173 : ExemplarFilter? exemplarFilter,
174 : bool enableMetrics = true,
175 : bool enableLogs = true,
176 : LogRecordExporter? logRecordExporter,
177 : LogRecordProcessor? logRecordProcessor,
178 : bool detectPlatformResources = true,
179 : bool logPrint = false,
180 : String logPrintLoggerName = 'dart.print',
181 : List<String>? otlpHeaderLogAllowlist,
182 : TimeProvider? timeProvider,
183 : OTelFactoryCreationFunction? oTelFactoryCreationFunction =
184 : otelSDKFactoryFactoryFunction,
185 : }) async {
186 : // Has to run before anything logs OTLP headers.
187 153 : OTelEnv.applyHeaderLogAllowlist(otlpHeaderLogAllowlist);
188 : // Apply OTEL_LOG_LEVEL before the env parsing below emits its debug
189 : // output — otherwise an env-configured debug level misses exactly the
190 : // lines that diagnose env configuration.
191 153 : initializeLogging();
192 :
193 153 : final sdkDisabled = OTelEnv.isSdkDisabled();
194 1 : if (sdkDisabled && OTelLog.isDebug()) {
195 1 : OTelLog.debug('OTel: OTEL_SDK_DISABLED=true, skipping all signal setup');
196 : }
197 :
198 : // Apply environment variables exactly once
199 153 : final envServiceConfig = OTelEnv.getServiceConfig();
200 : const OtlpEnvironmentValues emptyOtlp = (
201 : endpoint: null,
202 : protocol: null,
203 : headers: null,
204 : insecure: null,
205 : timeout: null,
206 : compression: null,
207 : certificate: null,
208 : clientKey: null,
209 : clientCertificate: null
210 : );
211 : const BlrpEnvironmentValues emptyBlrp = (
212 : scheduleDelay: null,
213 : exportTimeout: null,
214 : maxQueueSize: null,
215 : maxExportBatchSize: null
216 : );
217 : const BspEnvironmentValues emptyBsp = (
218 : scheduleDelay: null,
219 : exportTimeout: null,
220 : maxQueueSize: null,
221 : maxExportBatchSize: null
222 : );
223 :
224 : final otlpTracesConfig =
225 153 : !sdkDisabled ? OTelEnv.getOtlpConfig(signal: 'traces') : emptyOtlp;
226 : final otlpMetricsConfig = enableMetrics && !sdkDisabled
227 129 : ? OTelEnv.getOtlpConfig(signal: 'metrics')
228 : : emptyOtlp;
229 : final otlpLogsConfig = enableLogs && !sdkDisabled
230 126 : ? OTelEnv.getOtlpConfig(signal: 'logs')
231 : : emptyOtlp;
232 : final tracesExporters = !sdkDisabled
233 295 : ? (OTelEnv.getExporters(signal: 'traces') ?? ['otlp'])
234 1 : : <String>[];
235 : final metricsExporters = enableMetrics && !sdkDisabled
236 255 : ? (OTelEnv.getExporters(signal: 'metrics') ?? ['otlp'])
237 29 : : <String>[];
238 : final logsExporters = enableLogs && !sdkDisabled
239 250 : ? (OTelEnv.getExporters(signal: 'logs') ?? ['otlp'])
240 35 : : <String>[];
241 : final blrpConfig =
242 126 : enableLogs && !sdkDisabled ? OTelEnv.getBlrpConfig() : emptyBlrp;
243 153 : final bspConfig = !sdkDisabled ? OTelEnv.getBspConfig() : emptyBsp;
244 :
245 : final envServiceName =
246 : serviceName == null ? envServiceConfig.serviceName : null;
247 : final envServiceVersion =
248 : serviceVersion == null ? envServiceConfig.serviceVersion : null;
249 :
250 : serviceName ??= envServiceName;
251 : serviceVersion ??= envServiceVersion;
252 :
253 : final envEndpoint = endpoint == null ? otlpTracesConfig.endpoint : null;
254 : final envInsecure = secure == null ? otlpTracesConfig.insecure : null;
255 :
256 : endpoint ??= envEndpoint;
257 : // Keep the caller's explicit choice (null when not given): the
258 : // metrics/logs configurations resolve security against their own
259 : // per-signal endpoints and env vars, so they must receive the
260 : // original parameter rather than the traces-resolved value (#253).
261 : final explicitSecure = secure;
262 153 : secure = OTelEnv.resolveOtlpSecure(
263 : explicitSecure: secure,
264 : envInsecure: envInsecure,
265 : endpoint: endpoint,
266 : );
267 :
268 : // Apply defaults if still null.
269 : serviceName ??= defaultServiceName;
270 : serviceVersion ??= '1.0.0';
271 :
272 : // Log environment variable usage
273 153 : if (OTelLog.isDebug()) {
274 : if (envServiceName != null) {
275 2 : OTelLog.debug('Using service name from environment: $serviceName');
276 : }
277 : if (envServiceVersion != null) {
278 1 : OTelLog.debug(
279 1 : 'Using service version from environment: $serviceVersion',
280 : );
281 : }
282 : if (envEndpoint != null) {
283 2 : OTelLog.debug('Using endpoint from environment: $endpoint');
284 : }
285 : if (envInsecure != null) {
286 2 : OTelLog.debug('Using insecure setting from environment: $envInsecure');
287 : }
288 : }
289 :
290 : // The API auto-installs its OTelAPIFactory if API-only code runs
291 : // before the SDK initializes. The SDK must upgrade it to the SDK (per spec).
292 153 : final existingFactory = OTelFactory.otelFactory;
293 6 : if (existingFactory != null && !existingFactory.isAPIFactory) {
294 4 : throw StateError(
295 : 'OTel.initialize() can only be called once. If you need multiple endpoints or service names or versions create a named TracerProvider',
296 : );
297 : }
298 5 : if (existingFactory != null && OTelLog.isDebug()) {
299 3 : OTelLog.debug(
300 : 'OTel.initialize: replacing the auto-installed no-op API factory '
301 : 'with the SDK factory. API objects obtained before initialize() '
302 : 'remain no-ops.',
303 : );
304 : }
305 :
306 32 : if (endpoint != null && endpoint.isEmpty) {
307 2 : throw ArgumentError(
308 : 'endpoint must not be the empty string.',
309 : ); //TODO validate url
310 : }
311 153 : if (serviceName.isEmpty) {
312 2 : throw ArgumentError('serviceName must not be the empty string.');
313 : }
314 153 : if (serviceVersion.isEmpty) {
315 2 : throw ArgumentError('serviceVersion must not be the empty string.');
316 : }
317 : final factoryFactory =
318 : oTelFactoryCreationFunction ?? otelSDKFactoryFactoryFunction;
319 151 : _defaultSampler = sampler ?? ParentBasedSampler(const AlwaysOnSampler());
320 : _defaultSpanExceptionOptions = spanExceptionOptions;
321 : _defaultTimeProvider = timeProvider;
322 : OTel.defaultTracerName = tracerName ?? _defaultTracerName;
323 : OTel.defaultTracerVersion = tracerVersion ?? defaultTracerVersion;
324 :
325 153 : final createdFactory = factoryFactory(
326 : apiEndpoint: endpoint ?? defaultEndpoint,
327 : apiServiceName: serviceName,
328 : apiServiceVersion: serviceVersion,
329 : );
330 153 : OTelFactory.otelFactory = createdFactory;
331 153 : if (createdFactory is OTelSDKFactory) {
332 : _otelFactory = createdFactory;
333 : }
334 :
335 153 : _installGlobalPropagator();
336 :
337 153 : if (OTelLog.isDebug()) {
338 : final traceProtocol = otlpTracesConfig.protocol ?? 'http/protobuf';
339 266 : OTelLog.debug(
340 : 'OTel initialized with endpoint: '
341 109 : '${endpoint ?? (traceProtocol == 'grpc' ? defaultGrpcEndpoint : defaultEndpoint)}, '
342 : 'service: $serviceName',
343 : );
344 : }
345 :
346 : // Initialize resource with correct precedence
347 306 : var mergedResource = OTel.resource(OTel.attributes());
348 :
349 : if (detectPlatformResources) {
350 55 : final resourceDetector = PlatformResourceDetector.create();
351 55 : final platformResource = await resourceDetector.detect();
352 55 : mergedResource = mergedResource.merge(platformResource);
353 : }
354 :
355 : // Always run EnvVarResourceDetector for OTEL_RESOURCE_ATTRIBUTES
356 : final envVarResource =
357 612 : await CompositeResourceDetector([EnvVarResourceDetector()]).detect();
358 153 : mergedResource = mergedResource.merge(envVarResource);
359 :
360 : // Apply OTEL_SERVICE_NAME (and VERSION) via service name variables
361 153 : final serviceResourceAttributes = {
362 153 : Service.serviceName.key: serviceName,
363 153 : Service.serviceVersion.key: serviceVersion,
364 : };
365 153 : mergedResource = mergedResource.merge(
366 306 : OTel.resource(OTel.attributesFromMap(serviceResourceAttributes)));
367 :
368 : // Finally, explicit programmatic arguments outrank everything
369 : if (resourceAttributes != null) {
370 3 : final initResources = OTel.resource(resourceAttributes);
371 3 : mergedResource = mergedResource.merge(initResources);
372 : }
373 :
374 : OTel.defaultResource = mergedResource;
375 :
376 153 : if (OTelLog.isDebug()) {
377 133 : OTelLog.debug('Final resource:');
378 532 : mergedResource.attributes.toList().forEach((attr) {
379 399 : if (attr.key == Service.serviceName.key) {
380 532 : OTelLog.debug(' ${attr.key}: ${attr.value}');
381 : }
382 : });
383 : }
384 :
385 : if (!sdkDisabled) {
386 153 : TracesConfiguration.configureTracerProvider(
387 : endpoint: endpoint,
388 : secure: secure,
389 : spanProcessor: spanProcessor,
390 : sampler: sampler,
391 : spanExceptionOptions: spanExceptionOptions,
392 : resource: OTel.defaultResource,
393 : otlpConfig: otlpTracesConfig,
394 : exporters: tracesExporters,
395 : bspConfig: bspConfig,
396 : );
397 : }
398 :
399 : if (enableMetrics && !sdkDisabled) {
400 129 : MetricsConfiguration.configureMeterProvider(
401 : endpoint: endpoint,
402 : secure: explicitSecure,
403 : metricExporter: metricExporter,
404 : metricReader: metricReader,
405 : resource: OTel.defaultResource,
406 : exemplarFilter: exemplarFilter,
407 : otlpConfig: otlpMetricsConfig,
408 : exporters: metricsExporters,
409 : );
410 : }
411 :
412 : if (enableLogs && !sdkDisabled) {
413 126 : LogsConfiguration.configureLoggerProvider(
414 : endpoint: endpoint,
415 : secure: explicitSecure,
416 : logRecordExporter: logRecordExporter,
417 : logRecordProcessor: logRecordProcessor,
418 : resource: OTel.defaultResource,
419 : otlpConfig: otlpLogsConfig,
420 : exporters: logsExporters,
421 : blrpConfig: blrpConfig,
422 : );
423 : }
424 :
425 : // Store print interception configuration (lazily initialized when needed)
426 : _logPrintEnabled = logPrint;
427 : _logPrintLoggerName = logPrintLoggerName;
428 :
429 2 : if (logPrint && OTelLog.isDebug()) {
430 2 : OTelLog.debug(
431 2 : 'OTel: Print interception enabled with logger: $logPrintLoggerName');
432 : }
433 : }
434 :
435 : /// Ensures the print interception bridge is initialized.
436 : /// Called lazily when runWithPrintInterception is first used.
437 2 : static void _ensurePrintInterceptionInitialized() {
438 : if (_logBridge != null) return;
439 :
440 2 : final logger = OTel.logger(_logPrintLoggerName);
441 2 : _logBridge = DartLogBridge.install(
442 : logger,
443 : minimumSeverity: Severity.TRACE,
444 : );
445 2 : _printInterceptionZoneSpec = _logBridge!.createZoneSpecification();
446 :
447 2 : if (OTelLog.isDebug()) {
448 2 : OTelLog.debug(
449 2 : 'OTel: Print interception bridge initialized with logger: $_logPrintLoggerName');
450 : }
451 : }
452 :
453 : /// Creates a Resource with the specified attributes and schema URL.
454 : ///
455 : /// Resources represent the entity producing telemetry, such as a service,
456 : /// process, or device. They are a collection of attributes that provide
457 : /// identifying information about the entity.
458 : ///
459 : /// @param attributes Attributes describing the resource
460 : /// @param schemaUrl Optional URL of the schema defining the attributes
461 : /// @return A new Resource instance
462 153 : static Resource resource(Attributes? attributes, [String? schemaUrl]) {
463 153 : _getAndCacheOtelFactory();
464 153 : return (_otelFactory as OTelSDKFactory).resource(
465 9 : attributes ?? OTel.attributes(),
466 : schemaUrl,
467 : );
468 : }
469 :
470 : /// Creates a new ContextKey with the given name.
471 : ///
472 : /// Context keys are used to store and retrieve values in a Context.
473 : /// Each instance will be unique, even with the same name, per the OTel spec.
474 : /// The name is for debugging purposes only.
475 : ///
476 : /// @param name The name of the context key (for debugging only)
477 : /// @param isTransferable When `true`, values stored under this key transfer
478 : /// across isolate boundaries via `Context.runIsolate()`. Defaults to `false`
479 : /// (custom keys are local to their isolate). Built-in `Baggage` and
480 : /// `SpanContext` always transfer regardless of this flag.
481 : /// @return A new ContextKey instance
482 2 : static ContextKey<T> contextKey<T>(String name,
483 : {bool isTransferable = false}) {
484 2 : _getAndCacheOtelFactory();
485 1 : return _otelFactory!.contextKey<T>(
486 : name,
487 1 : ContextKey.generateContextKeyId(),
488 : isTransferable: isTransferable,
489 : );
490 : }
491 :
492 : /// Creates a new Context with optional Baggage and SpanContext.
493 : ///
494 : /// Contexts are used to propagate information across the execution path,
495 : /// such as trace context, baggage, and other cross-cutting concerns.
496 : ///
497 : /// @param baggage Optional baggage to include in the context
498 : /// @param spanContext Optional span context to include in the context
499 : /// @return A new Context instance
500 19 : static Context context({Baggage? baggage, SpanContext? spanContext}) {
501 19 : _getAndCacheOtelFactory();
502 38 : var context = OTelFactory.otelFactory!.context(baggage: baggage);
503 : if (spanContext != null) {
504 3 : context = context.copyWithSpanContext(spanContext);
505 : }
506 : return context;
507 : }
508 :
509 : /// Gets a TracerProvider for creating Tracers.
510 : ///
511 : /// If name is null, this returns the global default TracerProvider, which shares
512 : /// the endpoint, serviceName, serviceVersion, sampler and resource set in initialize().
513 : /// If the name is not null, it returns a TracerProvider for the name that was added
514 : /// with addTracerProvider.
515 : ///
516 : /// The endpoint, serviceName, serviceVersion, sampler and resource set flow down
517 : /// to the [Tracer]s created by the TracerProvider and the [Span]
518 : /// created by those tracers
519 : /// @param name Optional name of a specific TracerProvider
520 : /// @return The TracerProvider instance
521 153 : static TracerProvider tracerProvider({String? name}) {
522 153 : _getAndCacheOtelFactory();
523 153 : final tracerProvider = OTelAPI.tracerProvider(name) as TracerProvider;
524 : // Ensure the resource is properly set
525 153 : if (tracerProvider.resource == null && defaultResource != null) {
526 153 : tracerProvider.resource = defaultResource;
527 153 : if (OTelLog.isDebug()) {
528 133 : OTelLog.debug('OTel.tracerProvider: Setting resource from default');
529 : if (defaultResource != null) {
530 532 : defaultResource!.attributes.toList().forEach((attr) {
531 399 : if (attr.key == Service.serviceName.key) {
532 532 : OTelLog.debug(' ${attr.key}: ${attr.value}');
533 : }
534 : });
535 : }
536 : }
537 : }
538 :
539 153 : tracerProvider.sampler ??= _defaultSampler;
540 153 : tracerProvider.spanExceptionOptions ??= _defaultSpanExceptionOptions;
541 : if (_defaultTimeProvider != null) {
542 2 : tracerProvider.timeProvider = _defaultTimeProvider!;
543 : }
544 : return tracerProvider;
545 : }
546 :
547 : /// Gets a MeterProvider for creating Meters.
548 : ///
549 : /// If name is null, this returns the global default MeterProvider, which shares
550 : /// the endpoint, serviceName, serviceVersion and resource set in initialize().
551 : /// If the name is not null, it returns a MeterProvider for the name that was added
552 : /// with addMeterProvider.
553 : ///
554 : /// @param name Optional name of a specific MeterProvider
555 : /// @return The MeterProvider instance
556 133 : static MeterProvider meterProvider({String? name}) {
557 133 : _getAndCacheOtelFactory();
558 132 : final meterProvider = OTelAPI.meterProvider(name) as MeterProvider;
559 132 : meterProvider.resource ??= defaultResource;
560 : return meterProvider;
561 : }
562 :
563 : /// Adds or replaces a named TracerProvider.
564 : ///
565 : /// This allows for creating multiple TracerProviders with different configurations,
566 : /// which can be useful for sending telemetry to different backends or with different
567 : /// settings.
568 : ///
569 : /// @param name The name of the TracerProvider
570 : /// @param endpoint Optional custom endpoint URL
571 : /// @param serviceName Optional custom service name
572 : /// @param serviceVersion Optional custom service version
573 : /// @param resource Optional custom resource
574 : /// @param sampler Optional custom sampler
575 : /// @param spanExceptionOptions Optional default exception handling options;
576 : /// defaults to the options set in initialize()
577 : /// @return The newly created or replaced TracerProvider
578 12 : static TracerProvider addTracerProvider(
579 : String name, {
580 : String? endpoint,
581 : String? serviceName,
582 : String? serviceVersion,
583 : Resource? resource,
584 : Sampler? sampler,
585 : SpanExceptionOptions? spanExceptionOptions,
586 : }) {
587 12 : _getAndCacheOtelFactory();
588 11 : final sdkTracerProvider = OTelAPI.addTracerProvider(name) as TracerProvider;
589 11 : sdkTracerProvider.resource = resource ?? defaultResource;
590 11 : sdkTracerProvider.sampler = sampler ?? _defaultSampler;
591 11 : sdkTracerProvider.spanExceptionOptions =
592 : spanExceptionOptions ?? _defaultSpanExceptionOptions;
593 : if (_defaultTimeProvider != null) {
594 0 : sdkTracerProvider.timeProvider = _defaultTimeProvider!;
595 : }
596 : return sdkTracerProvider;
597 : }
598 :
599 : /// @return the [TracerProvider]s, the global default and named ones.
600 152 : static List<APITracerProvider> tracerProviders() {
601 152 : return OTelAPI.tracerProviders();
602 : }
603 :
604 : /// Gets the default Tracer from the default TracerProvider.
605 : ///
606 : /// This is a convenience method for getting a Tracer with the default configuration.
607 : /// The endpoint, serviceName, serviceVersion, sampler and resource all flow down
608 : /// from the OTel defaults set during initialization.
609 : ///
610 : /// @return The default Tracer instance
611 36 : static Tracer tracer() {
612 72 : return tracerProvider().getTracer(
613 36 : defaultTracerName,
614 : version: defaultTracerVersion,
615 : );
616 : }
617 :
618 : /// Activates [span] for the duration of [fn] (so `Context.current.span`
619 : /// returns it inside `fn`) and records any thrown exception with
620 : /// `SpanStatusCode.Error`. The caller is still responsible for
621 : /// `span.end()` — typically in a `finally` block.
622 : ///
623 : /// Convenience over `OTel.tracer().withSpan(span, fn)` for callers
624 : /// that don't already have a [Tracer] reference.
625 : ///
626 : /// [exceptionOptions] controls how a thrown exception is recorded and
627 : /// whether the span status is set; see [SpanExceptionOptions].
628 6 : static T withSpan<T>(
629 : APISpan span,
630 : T Function() fn, {
631 : SpanExceptionOptions? exceptionOptions,
632 : }) =>
633 12 : tracer().withSpan(span, fn, exceptionOptions: exceptionOptions);
634 :
635 : /// Async variant of [withSpan]. Propagates the active span across
636 : /// `await` boundaries via Zone-based context.
637 : ///
638 : /// [exceptionOptions] controls how a thrown exception is recorded and
639 : /// whether the span status is set; see [SpanExceptionOptions].
640 4 : static Future<T> withSpanAsync<T>(
641 : APISpan span,
642 : Future<T> Function() fn, {
643 : SpanExceptionOptions? exceptionOptions,
644 : }) =>
645 8 : tracer().withSpanAsync(span, fn, exceptionOptions: exceptionOptions);
646 :
647 : /// Adds or replaces a named MeterProvider.
648 : ///
649 : /// This allows for creating multiple MeterProviders with different configurations,
650 : /// which can be useful for sending metrics to different backends or with different
651 : /// settings.
652 : ///
653 : /// @param name The name of the MeterProvider
654 : /// @param endpoint Optional custom endpoint URL
655 : /// @param serviceName Optional custom service name
656 : /// @param serviceVersion Optional custom service version
657 : /// @param resource Optional custom resource
658 : /// @return The newly created or replaced MeterProvider
659 3 : static MeterProvider addMeterProvider(
660 : String name, {
661 : String? endpoint,
662 : String? serviceName,
663 : String? serviceVersion,
664 : Resource? resource,
665 : }) {
666 3 : _getAndCacheOtelFactory();
667 3 : final mp = _otelFactory!.addMeterProvider(
668 : name,
669 : endpoint: endpoint,
670 : serviceName: serviceName,
671 : serviceVersion: serviceVersion,
672 : ) as MeterProvider;
673 3 : mp.resource = resource ?? defaultResource;
674 : return mp;
675 : }
676 :
677 : /// @return the [MeterProvider]s, the global default and named ones.
678 152 : static List<APIMeterProvider> meterProviders() {
679 152 : return OTelAPI.meterProviders();
680 : }
681 :
682 : /// Gets the default Meter from the default MeterProvider.
683 : ///
684 : /// This is a convenience method for getting a Meter with the default configuration.
685 : /// The endpoint, serviceName, serviceVersion and resource all flow down from
686 : /// the OTel defaults set during initialization.
687 : ///
688 : /// @param name Optional custom name for the meter (defaults to defaultTracerName)
689 : /// @return The default Meter instance
690 17 : static Meter meter([String? name]) {
691 34 : return meterProvider().getMeter(
692 3 : name: name ?? defaultTracerName,
693 : version: defaultTracerVersion,
694 : ) as Meter;
695 : }
696 :
697 : /// Gets a LoggerProvider for creating Loggers.
698 : ///
699 : /// If name is null, this returns the global default LoggerProvider, which shares
700 : /// the endpoint, serviceName, serviceVersion and resource set in initialize().
701 : /// If the name is not null, it returns a LoggerProvider for the name that was added
702 : /// with addLoggerProvider.
703 : ///
704 : /// @param name Optional name of a specific LoggerProvider
705 : /// @return The LoggerProvider instance
706 128 : static LoggerProvider loggerProvider({String? name}) {
707 128 : _getAndCacheOtelFactory();
708 128 : final logProvider = OTelAPI.loggerProvider(name) as LoggerProvider;
709 128 : logProvider.resource ??= defaultResource;
710 : return logProvider;
711 : }
712 :
713 : /// Adds or replaces a named LoggerProvider.
714 : ///
715 : /// This allows for creating multiple LoggerProviders with different configurations,
716 : /// which can be useful for sending logs to different backends or with different
717 : /// settings.
718 : ///
719 : /// @param name The name of the LoggerProvider
720 : /// @param endpoint Optional custom endpoint URL
721 : /// @param serviceName Optional custom service name
722 : /// @param serviceVersion Optional custom service version
723 : /// @param resource Optional custom resource
724 : /// @return The newly created or replaced LoggerProvider
725 2 : static LoggerProvider addLoggerProvider(
726 : String name, {
727 : String? endpoint,
728 : String? serviceName,
729 : String? serviceVersion,
730 : Resource? resource,
731 : }) {
732 2 : _getAndCacheOtelFactory();
733 2 : final lp = _otelFactory!.addLogProvider(name,
734 : endpoint: endpoint,
735 : serviceName: serviceName,
736 : serviceVersion: serviceVersion) as LoggerProvider;
737 2 : lp.resource = resource ?? defaultResource;
738 : return lp;
739 : }
740 :
741 : /// Gets the default OTelLogger from the default LoggerProvider.
742 : ///
743 : /// This is a convenience method for getting a OTelLogger with the default configuration.
744 : /// The endpoint, serviceName, serviceVersion and resource all flow down from
745 : /// the OTel defaults set during initialization.
746 : ///
747 : /// @param name Optional custom name for the logger (defaults to defaultTracerName)
748 : /// @return The default OTelLogger instance
749 5 : static OTelLogger logger([String? name]) {
750 10 : return loggerProvider().getLogger(
751 1 : name ?? defaultTracerName,
752 : version: defaultTracerVersion,
753 : );
754 : }
755 :
756 : /// Whether print interception is enabled.
757 : ///
758 : /// Returns true if [initialize] was called with `logPrint: true`.
759 2 : static bool get isLogPrintEnabled => _logPrintEnabled;
760 :
761 : /// Gets the current DartLogBridge instance, if print interception is enabled.
762 : ///
763 : /// Returns null if print interception was not enabled during initialization.
764 2 : static DartLogBridge? get logBridge => _logBridge;
765 :
766 : /// Runs the given callback in a zone that intercepts print() calls.
767 : ///
768 : /// When [initialize] is called with `logPrint: true`, this method runs
769 : /// the callback in a zone where all `print()` calls are captured and
770 : /// routed to OpenTelemetry logs as INFO level messages.
771 : ///
772 : /// If print interception is not enabled, the callback is run directly
773 : /// without any interception.
774 : ///
775 : /// Example usage:
776 : /// ```dart
777 : /// await OTel.initialize(
778 : /// serviceName: 'my-service',
779 : /// logPrint: true,
780 : /// );
781 : ///
782 : /// OTel.runWithPrintInterception(() {
783 : /// print('This will be captured as an OTel log');
784 : /// });
785 : /// ```
786 : ///
787 : /// @param callback The code to run with print interception
788 : /// @return The result of the callback
789 3 : static R runWithPrintInterception<R>(R Function() callback) {
790 : if (!_logPrintEnabled) {
791 3 : return callback();
792 : }
793 2 : _ensurePrintInterceptionInitialized();
794 2 : return runZoned(callback, zoneSpecification: _printInterceptionZoneSpec);
795 : }
796 :
797 : /// Runs the given async callback in a zone that intercepts print() calls.
798 : ///
799 : /// This is the async version of [runWithPrintInterception].
800 : ///
801 : /// @param callback The async code to run with print interception
802 : /// @return A Future containing the result of the callback
803 3 : static Future<R> runWithPrintInterceptionAsync<R>(
804 : Future<R> Function() callback) {
805 : if (!_logPrintEnabled) {
806 2 : return callback();
807 : }
808 2 : _ensurePrintInterceptionInitialized();
809 2 : return runZoned(callback, zoneSpecification: _printInterceptionZoneSpec);
810 : }
811 :
812 : /// Creates a SpanContext with the specified parameters.
813 : ///
814 : /// A SpanContext represents the portion of a span that must be propagated
815 : /// to descendant spans and across process boundaries. It contains the
816 : /// traceId, spanId, traceFlags, and traceState.
817 : ///
818 : /// @param traceId The trace ID (defaults to a new random ID)
819 : /// @param spanId The span ID (defaults to a new random ID)
820 : /// @param parentSpanId The parent span ID (defaults to an invalid span ID)
821 : /// @param traceFlags Trace flags (defaults to NONE_FLAG)
822 : /// @param traceState Trace state
823 : /// @param isRemote Whether this context was received from a remote source
824 : /// @return A new SpanContext instance
825 72 : static SpanContext spanContext({
826 : TraceId? traceId,
827 : SpanId? spanId,
828 : SpanId? parentSpanId,
829 : TraceFlags? traceFlags,
830 : TraceState? traceState,
831 : bool? isRemote,
832 : }) {
833 72 : return OTelAPI.spanContext(
834 2 : traceId: traceId ?? OTel.traceId(),
835 2 : spanId: spanId ?? OTel.spanId(),
836 23 : parentSpanId: parentSpanId ?? spanIdInvalid(),
837 16 : traceFlags: traceFlags ?? OTelAPI.traceFlags(),
838 : traceState: traceState,
839 : isRemote: isRemote,
840 : );
841 : }
842 :
843 : /// Creates a child SpanContext from a parent context.
844 : ///
845 : /// This creates a new SpanContext that shares the same traceId as the parent,
846 : /// but has a new spanId and the parentSpanId set to the parent's spanId.
847 : ///
848 : /// @param parent The parent SpanContext
849 : /// @return A new child SpanContext
850 1 : static SpanContext spanContextFromParent(SpanContext parent) {
851 1 : _getAndCacheOtelFactory();
852 2 : return OTelFactory.otelFactory!.spanContextFromParent(parent);
853 : }
854 :
855 : /// Creates an invalid SpanContext (all zeros).
856 : ///
857 : /// An invalid SpanContext represents the absence of a trace context.
858 : ///
859 : /// @return An invalid SpanContext instance
860 2 : static SpanContext spanContextInvalid() {
861 2 : _getAndCacheOtelFactory();
862 4 : return OTelFactory.otelFactory!.spanContextInvalid();
863 : }
864 :
865 : /// Creates a SpanEvent with the current timestamp.
866 : ///
867 : /// Note: Per [OTEP 0265: Event Vision](https://github.com/open-telemetry/opentelemetry-specification/blob/main/oteps/0265-event-vision.md)
868 : /// and [OTEP 4430: Span Event API deprecation plan](https://github.com/open-telemetry/opentelemetry-specification/blob/main/oteps/4430-span-event-api-deprecation-plan.md),
869 : /// span events are planned for deprecation in favor of log-based events
870 : /// emitted via the Logs API; SDKs will provide options to render log-based
871 : /// events as span events for compatibility.
872 : ///
873 : /// @param name The name of the event
874 : /// @param attributes Attributes to associate with the event
875 : /// @return A new SpanEvent instance with the current timestamp
876 1 : static SpanEvent spanEventNow(String name, Attributes attributes) {
877 1 : _getAndCacheOtelFactory();
878 2 : return spanEvent(name, attributes, DateTime.now());
879 : }
880 :
881 : /// Creates a SpanEvent with the specified parameters.
882 : ///
883 : /// Note: Per [OTEP 0265: Event Vision](https://github.com/open-telemetry/opentelemetry-specification/blob/main/oteps/0265-event-vision.md)
884 : /// and [OTEP 4430: Span Event API deprecation plan](https://github.com/open-telemetry/opentelemetry-specification/blob/main/oteps/4430-span-event-api-deprecation-plan.md),
885 : /// span events are planned for deprecation in favor of log-based events
886 : /// emitted via the Logs API; SDKs will provide options to render log-based
887 : /// events as span events for compatibility.
888 : ///
889 : /// @param name The name of the event
890 : /// @param attributes Optional attributes to associate with the event
891 : /// @param timestamp Optional timestamp for the event (defaults to null)
892 : /// @return A new SpanEvent instance
893 4 : static SpanEvent spanEvent(
894 : String name, [
895 : Attributes? attributes,
896 : DateTime? timestamp,
897 : ]) {
898 4 : _getAndCacheOtelFactory();
899 4 : return _otelFactory!.spanEvent(name, attributes, timestamp);
900 : }
901 :
902 : /// Creates a Baggage with key-value pairs.
903 : ///
904 : /// Baggage is a set of key-value pairs that can be propagated across service boundaries
905 : /// along with the trace context. It can be used to add contextual information to traces.
906 : ///
907 : /// @param keyValuePairs A map of key-value pairs to include in the baggage
908 : /// @return A new Baggage instance
909 1 : static Baggage baggageForMap(Map<String, String> keyValuePairs) {
910 1 : _getAndCacheOtelFactory();
911 1 : return _otelFactory!.baggageForMap(keyValuePairs);
912 : }
913 :
914 : /// Creates a BaggageEntry with the specified value and optional metadata.
915 : ///
916 : /// @param value The value of the baggage entry
917 : /// @param metadata Optional metadata for the baggage entry
918 : /// @return A new BaggageEntry instance
919 5 : static BaggageEntry baggageEntry(String value, [String? metadata]) {
920 5 : _getAndCacheOtelFactory();
921 5 : return _otelFactory!.baggageEntry(value, metadata);
922 : }
923 :
924 : /// Creates a Baggage with the specified entries.
925 : ///
926 : /// @param entries Optional map of baggage entries
927 : /// @return A new Baggage instance
928 6 : static Baggage baggage([Map<String, BaggageEntry>? entries]) {
929 6 : _getAndCacheOtelFactory();
930 6 : return _otelFactory!.baggage(entries);
931 : }
932 :
933 : /// Creates a Baggage instance from a JSON representation.
934 : ///
935 : /// @param json JSON representation of a baggage
936 : /// @return A new Baggage instance
937 1 : static Baggage baggageFromJson(Map<String, dynamic> json) {
938 1 : return OTelAPI.baggageFromJson(json);
939 : }
940 :
941 : /// Creates a string attribute.
942 : ///
943 : /// @param name The name of the attribute
944 : /// @param value The string value of the attribute
945 : /// @return A new Attribute instance
946 14 : static Attribute<String> attributeString(String name, String value) {
947 14 : _getAndCacheOtelFactory();
948 14 : return _otelFactory!.attributeString(name, value);
949 : }
950 :
951 : /// Creates a boolean attribute.
952 : ///
953 : /// @param name The name of the attribute
954 : /// @param value The boolean value of the attribute
955 : /// @return A new Attribute instance
956 2 : static Attribute<bool> attributeBool(String name, bool value) {
957 2 : _getAndCacheOtelFactory();
958 2 : return _otelFactory!.attributeBool(name, value);
959 : }
960 :
961 : /// Creates an integer attribute.
962 : ///
963 : /// @param name The name of the attribute
964 : /// @param value The integer value of the attribute
965 : /// @return A new Attribute instance
966 5 : static Attribute<int> attributeInt(String name, int value) {
967 5 : _getAndCacheOtelFactory();
968 5 : return _otelFactory!.attributeInt(name, value);
969 : }
970 :
971 : /// Creates a double attribute.
972 : ///
973 : /// @param name The name of the attribute
974 : /// @param value The double value of the attribute
975 : /// @return A new Attribute instance
976 3 : static Attribute<double> attributeDouble(String name, double value) {
977 3 : _getAndCacheOtelFactory();
978 3 : return _otelFactory!.attributeDouble(name, value);
979 : }
980 :
981 : /// Creates a string list attribute.
982 : ///
983 : /// @param name The name of the attribute
984 : /// @param value The list of string values
985 : /// @return A new Attribute instance
986 3 : static Attribute<List<String>> attributeStringList(
987 : String name,
988 : List<String> value,
989 : ) {
990 3 : _getAndCacheOtelFactory();
991 3 : return _otelFactory!.attributeStringList(name, value);
992 : }
993 :
994 : /// Creates a boolean list attribute.
995 : ///
996 : /// @param name The name of the attribute
997 : /// @param value The list of boolean values
998 : /// @return A new Attribute instance
999 3 : static Attribute<List<bool>> attributeBoolList(
1000 : String name,
1001 : List<bool> value,
1002 : ) {
1003 3 : _getAndCacheOtelFactory();
1004 3 : return _otelFactory!.attributeBoolList(name, value);
1005 : }
1006 :
1007 : /// Creates an integer list attribute.
1008 : ///
1009 : /// @param name The name of the attribute
1010 : /// @param value The list of integer values
1011 : /// @return A new Attribute instance
1012 3 : static Attribute<List<int>> attributeIntList(String name, List<int> value) {
1013 3 : _getAndCacheOtelFactory();
1014 3 : return _otelFactory!.attributeIntList(name, value);
1015 : }
1016 :
1017 : /// Creates a double list attribute.
1018 : ///
1019 : /// @param name The name of the attribute
1020 : /// @param value The list of double values
1021 : /// @return A new Attribute instance
1022 3 : static Attribute<List<double>> attributeDoubleList(
1023 : String name,
1024 : List<double> value,
1025 : ) {
1026 3 : _getAndCacheOtelFactory();
1027 3 : return _otelFactory!.attributeDoubleList(name, value);
1028 : }
1029 :
1030 : /// Creates an empty Attributes collection.
1031 : ///
1032 : /// @return A new empty Attributes collection
1033 10 : static Attributes createAttributes() {
1034 10 : _getAndCacheOtelFactory();
1035 10 : return _otelFactory!.attributes();
1036 : }
1037 :
1038 : /// Creates an Attributes collection from a list of Attribute objects.
1039 : ///
1040 : /// Often called before initialize; pre-init this routes through the API,
1041 : /// which lazily installs the no-op factory per spec (API ≥ beta.9 — there
1042 : /// is no factory-bypassing cheat path anymore).
1043 : ///
1044 : /// @param entries Optional list of Attribute objects
1045 : /// @return A new Attributes collection
1046 153 : static Attributes attributes([List<Attribute>? entries]) {
1047 : return _otelFactory == null
1048 1 : ? OTelAPI.attributes(entries)
1049 153 : : _otelFactory!.attributes(entries);
1050 : }
1051 :
1052 : /// Creates an Attributes collection from a map of named values.
1053 : ///
1054 : /// String, bool, int, double, or Lists of those types get converted
1055 : /// to the matching typed attribute. DateTime gets converted to a
1056 : /// String attribute with the UTC time string.
1057 : ///
1058 : /// Unlike most methods, this does not create the OTelFactory if
1059 : /// one does not exist, instead it uses the OTelAPI's attributesFromMap.
1060 : ///
1061 : /// Alternatively, consider using the toAttributes()
1062 : /// extension on \<String, Map>{}.
1063 : /// @param namedMap Map of attribute names to values
1064 : /// @return A new Attributes collection
1065 153 : static Attributes attributesFromMap(Map<String, Object> namedMap) {
1066 : if (_otelFactory == null) {
1067 3 : return OTelAPI.attributesFromMap(namedMap);
1068 : } else {
1069 153 : return _otelFactory!.attributesFromMap(namedMap);
1070 : }
1071 : }
1072 :
1073 : /// Creates an [Attributes] from a map keyed by [OTelSemantic] enum values
1074 : /// (e.g. `Http.httpRequestMethod`). Each enum's `.key` is used as the
1075 : /// attribute name. Lets you write
1076 : ///
1077 : /// ```dart
1078 : /// OTel.attributesFromSemanticMap({
1079 : /// Http.httpRequestMethod: 'GET',
1080 : /// Http.httpResponseStatusCode: 200,
1081 : /// })
1082 : /// ```
1083 : ///
1084 : /// instead of `attributesFromMap({Http.httpRequestMethod.key: 'GET', …})`.
1085 : /// Mixing enum types in one map is fine — the param is `Map<OTelSemantic, Object>`,
1086 : /// and every semconv enum implements `OTelSemantic`.
1087 : ///
1088 : /// Passthrough to [OTelAPI.attributesFromSemanticMap] for symmetry with
1089 : /// the [attributesFromMap] convenience.
1090 1 : static Attributes attributesFromSemanticMap(
1091 : Map<OTelSemantic, Object> semanticMap,
1092 : ) {
1093 1 : return OTelAPI.attributesFromSemanticMap(semanticMap);
1094 : }
1095 :
1096 : /// Like [attributesFromSemanticMap], but parameterized on a single
1097 : /// concrete semconv enum [E]. The expected key type is concrete at
1098 : /// the call site, so Dart 3.10 static dot-shorthand can drop the
1099 : /// enum prefix on every entry:
1100 : ///
1101 : /// ```dart
1102 : /// // Today and forever:
1103 : /// OTel.attributesOf<Http>({
1104 : /// Http.httpRequestMethod: 'GET',
1105 : /// Http.httpResponseStatusCode: 200,
1106 : /// });
1107 : ///
1108 : /// // With Dart 3.10+ static dot-shorthand enabled:
1109 : /// OTel.attributesOf<Http>({
1110 : /// .requestMethod: 'GET',
1111 : /// .responseStatusCode: 200,
1112 : /// });
1113 : /// ```
1114 : ///
1115 : /// **Mix and match**: each typed-enum map can be spread into a wider
1116 : /// `Map<OTelSemantic, Object>` literal, which is exactly what
1117 : /// [attributesFromSemanticMap] takes — so combining HTTP + Database
1118 : /// keys with full dot-shorthand looks like:
1119 : ///
1120 : /// ```dart
1121 : /// OTel.attributesFromSemanticMap({
1122 : /// ...<Http, Object>{
1123 : /// Http.httpRequestMethod: 'GET',
1124 : /// Http.httpResponseStatusCode: 200,
1125 : /// },
1126 : /// ...<Database, Object>{
1127 : /// Db.dbSystemName: DbSystem.postgresql.value,
1128 : /// },
1129 : /// });
1130 : /// ```
1131 : ///
1132 : /// Passthrough to [OTelAPI.attributesOf].
1133 1 : static Attributes attributesOf<E extends OTelSemantic>(
1134 : Map<E, Object> typedMap,
1135 : ) =>
1136 1 : OTelAPI.attributesOf<E>(typedMap);
1137 :
1138 : /// Creates an Attributes collection from a list of Attribute objects.
1139 : ///
1140 : /// @param attributeList List of Attribute objects
1141 : /// @return A new Attributes collection
1142 9 : static Attributes attributesFromList(List<Attribute> attributeList) {
1143 9 : _getAndCacheOtelFactory();
1144 9 : return _otelFactory!.attributesFromList(attributeList);
1145 : }
1146 :
1147 : /// Creates a TraceState with the specified entries.
1148 : ///
1149 : /// TraceState carries vendor-specific trace identification data across systems.
1150 : ///
1151 : /// @param entries Optional map of key-value pairs for the trace state
1152 : /// @return A new TraceState instance
1153 9 : static TraceState traceState(Map<String, String>? entries) {
1154 9 : _getAndCacheOtelFactory();
1155 9 : return _otelFactory!.traceState(entries);
1156 : }
1157 :
1158 : /// Creates TraceFlags with the specified flags.
1159 : ///
1160 : /// TraceFlags are used to encode bit field flags in the trace context.
1161 : /// The most commonly used flag is SAMPLED_FLAG, which indicates
1162 : /// that the trace should be sampled.
1163 : ///
1164 : /// @param flags Optional flags value (default: NONE_FLAG)
1165 : /// @return A new TraceFlags instance
1166 64 : static TraceFlags traceFlags([int? flags]) {
1167 64 : _getAndCacheOtelFactory();
1168 64 : return _otelFactory!.traceFlags(flags ?? TraceFlags.NONE_FLAG);
1169 : }
1170 :
1171 : /// Generates a new random TraceId.
1172 : ///
1173 : /// @return A new random TraceId
1174 67 : static TraceId traceId() {
1175 134 : return traceIdOf(IdGenerator.generateTraceId());
1176 : }
1177 :
1178 : /// Creates a TraceId from the specified bytes.
1179 : ///
1180 : /// @param traceId The bytes for the trace ID (must be exactly 16 bytes)
1181 : /// @return A new TraceId instance
1182 : /// @throws ArgumentError if traceId is not exactly 16 bytes
1183 68 : static TraceId traceIdOf(Uint8List traceId) {
1184 68 : _getAndCacheOtelFactory();
1185 136 : if (traceId.length != TraceId.traceIdLength) {
1186 1 : throw ArgumentError(
1187 2 : 'Trace ID must be exactly ${TraceId.traceIdLength} bytes, got ${traceId.length} bytes',
1188 : );
1189 : }
1190 136 : return OTelFactory.otelFactory!.traceId(traceId);
1191 : }
1192 :
1193 : /// Creates a TraceId from a hex string.
1194 : ///
1195 : /// @param hexString Hexadecimal representation of the trace ID
1196 : /// @return A new TraceId instance
1197 15 : static TraceId traceIdFrom(String hexString) {
1198 15 : return OTelAPI.traceIdFrom(hexString);
1199 : }
1200 :
1201 : /// Creates an invalid TraceId (all zeros).
1202 : ///
1203 : /// @return An invalid TraceId instance
1204 2 : static TraceId traceIdInvalid() {
1205 4 : return traceIdOf(TraceId.invalidTraceIdBytes);
1206 : }
1207 :
1208 : /// Generates a new random SpanId.
1209 : ///
1210 : /// @return A new random SpanId
1211 65 : static SpanId spanId() {
1212 130 : return spanIdOf(IdGenerator.generateSpanId());
1213 : }
1214 :
1215 : /// Creates a SpanId from the specified bytes.
1216 : ///
1217 : /// @param spanId The bytes for the span ID (must be exactly 8 bytes)
1218 : /// @return A new SpanId instance
1219 : /// @throws ArgumentError if spanId is not exactly 8 bytes
1220 75 : static SpanId spanIdOf(Uint8List spanId) {
1221 75 : _getAndCacheOtelFactory();
1222 150 : if (spanId.length != 8) {
1223 1 : throw ArgumentError(
1224 2 : 'Span ID must be exactly 8 bytes, got ${spanId.length} bytes',
1225 : );
1226 : }
1227 75 : return _otelFactory!.spanId(spanId);
1228 : }
1229 :
1230 : /// Creates a SpanId from a hex string.
1231 : ///
1232 : /// @param hexString Hexadecimal representation of the span ID
1233 : /// @return A new SpanId instance
1234 15 : static SpanId spanIdFrom(String hexString) {
1235 15 : return OTelAPI.spanIdFrom(hexString);
1236 : }
1237 :
1238 : /// Creates an invalid SpanId (all zeros).
1239 : ///
1240 : /// @return An invalid SpanId instance
1241 73 : static SpanId spanIdInvalid() {
1242 146 : return spanIdOf(SpanId.invalidSpanIdBytes);
1243 : }
1244 :
1245 : /// Creates a SpanLink with the specified SpanContext and optional attributes.
1246 : ///
1247 : /// SpanLinks are used to associate spans that may be causally related
1248 : /// but not via a parent-child relationship.
1249 : ///
1250 : /// @param spanContext The SpanContext to link to
1251 : /// @param attributes Optional attributes to associate with the link
1252 : /// @return A new SpanLink instance
1253 6 : static SpanLink spanLink(SpanContext spanContext, {Attributes? attributes}) {
1254 6 : _getAndCacheOtelFactory();
1255 6 : return _otelFactory!.spanLink(spanContext, attributes: attributes);
1256 : }
1257 :
1258 : /// Retrieves and caches the OTelFactory instance.
1259 : ///
1260 : /// @return The OTelFactory instance
1261 : /// @throws StateError if initialize() has not been called
1262 153 : static OTelFactory _getAndCacheOtelFactory() {
1263 : if (_otelFactory != null) {
1264 : return _otelFactory!;
1265 : }
1266 3 : final installed = OTelFactory.otelFactory;
1267 : // An installed factory that is not SDK-capable is the API's auto-installed
1268 : // no-op (API-only code ran first) — the SDK still isn't initialized, and
1269 : // saying so beats the `as` TypeError the cast would produce (#50).
1270 2 : if (installed == null || installed is! OTelSDKFactory) {
1271 3 : throw StateError('OTel.initialize() must be called first.');
1272 : }
1273 : return _otelFactory = installed;
1274 : }
1275 :
1276 : /// Initializes logging based on environment variables.
1277 : ///
1278 : /// This can be called separately from initialize(), but initialize() will
1279 : /// call it automatically if not already done.
1280 :
1281 : /// Installs the global [TextMapPropagator] per `OTEL_PROPAGATORS`
1282 : /// (default `tracecontext,baggage`). Unsupported names emit an
1283 : /// [OTelLog.warn] and are ignored; `none` — or a list with no supported
1284 : /// names — leaves the API's spec-mandated no-op propagator in place.
1285 : /// Supported: `tracecontext`, `baggage`, `none`.
1286 153 : static void _installGlobalPropagator() {
1287 153 : final names = OTelEnv.getPropagators();
1288 153 : if (names.contains('none')) {
1289 3 : if (names.length > 1 && OTelLog.isWarn()) {
1290 1 : OTelLog.warn('OTEL_PROPAGATORS contains "none" alongside other '
1291 : 'values; using no propagator per spec.');
1292 : }
1293 : return; // The API default is the no-op propagator.
1294 : }
1295 153 : final propagators = <TextMapPropagator<Map<String, String>, String>>[];
1296 : final seen = <String>{};
1297 306 : for (final name in names) {
1298 153 : if (!seen.add(name)) continue;
1299 : switch (name) {
1300 153 : case 'tracecontext':
1301 306 : propagators.add(W3CTraceContextPropagator());
1302 153 : case 'baggage':
1303 306 : propagators.add(W3CBaggagePropagator());
1304 : default:
1305 1 : if (OTelLog.isWarn()) {
1306 2 : OTelLog.warn('OTEL_PROPAGATORS: unsupported propagator '
1307 : '"$name" ignored. Supported: tracecontext, baggage, none.');
1308 : }
1309 : }
1310 : }
1311 153 : if (propagators.isEmpty) {
1312 : return; // Nothing supported was requested; keep the no-op default.
1313 : }
1314 459 : OTelAPI.textMapPropagator = propagators.length == 1
1315 1 : ? propagators.single
1316 153 : : OTelAPI.compositePropagator<Map<String, String>, String>(propagators);
1317 : }
1318 :
1319 153 : static void initializeLogging() {
1320 : // Initialize log settings from environment variables
1321 153 : OTelEnv.initializeLogging();
1322 :
1323 153 : if (OTelLog.isDebug()) {
1324 133 : OTelLog.debug('OTel logging initialized');
1325 : }
1326 : }
1327 :
1328 : /// Flushes and shuts down trace and metric providers,
1329 : /// processors and exporters. Typically called from [OTel.shutdown]
1330 152 : static Future<void> shutdown() async {
1331 : // Shutdown any tracer providers to clean up span processors
1332 : try {
1333 152 : final tracerProviders = OTel.tracerProviders();
1334 303 : for (final tracerProvider in tracerProviders) {
1335 151 : if (OTelLog.isDebug()) {
1336 138 : OTelLog.debug('OTel: Shutting down tracer providers');
1337 : }
1338 151 : if (tracerProvider is TracerProvider) {
1339 : try {
1340 151 : await tracerProvider.forceFlush();
1341 151 : if (OTelLog.isDebug()) {
1342 138 : OTelLog.debug('OTel: Tracer provider flush complete');
1343 : }
1344 : } catch (e) {
1345 0 : if (OTelLog.isDebug()) {
1346 0 : OTelLog.debug('OTel: Error during tracer provider flush: $e');
1347 : }
1348 : }
1349 : }
1350 : try {
1351 151 : await tracerProvider.shutdown();
1352 151 : if (OTelLog.isDebug()) {
1353 138 : OTelLog.debug('OTel: Tracer provider shutdown complete');
1354 : }
1355 : } catch (e) {
1356 0 : if (OTelLog.isDebug()) {
1357 0 : OTelLog.debug('OTel: Error during tracer provider shutdown: $e');
1358 : }
1359 : }
1360 : }
1361 : } catch (e) {
1362 0 : if (OTelLog.isDebug()) {
1363 0 : OTelLog.debug('OTel: Error accessing tracer provider: $e');
1364 : }
1365 : }
1366 :
1367 : // Shutdown meter providers to clean up metric readers and exporters
1368 152 : final meterProviders = OTel.meterProviders();
1369 282 : for (var meterProvider in meterProviders) {
1370 : try {
1371 130 : if (OTelLog.isDebug()) {
1372 123 : OTelLog.debug('OTel: Shutting down meter provider');
1373 : }
1374 130 : await meterProvider.shutdown();
1375 130 : if (OTelLog.isDebug()) {
1376 123 : OTelLog.debug('OTel: Meter provider shutdown complete');
1377 : }
1378 : } catch (e) {
1379 0 : if (OTelLog.isDebug()) {
1380 0 : OTelLog.debug('OTel: Error during meter provider shutdown: $e');
1381 : }
1382 : }
1383 : }
1384 :
1385 : // Shut down all LoggerProviders — default plus any named ones added
1386 : // via `OTel.addLoggerProvider(name)`. Without this, each provider's
1387 : // BatchLogRecordProcessor `Timer.periodic` keeps the Dart isolate
1388 : // alive after `main()` returns, so short-lived CLI binaries hang
1389 : // indefinitely after `await OTel.shutdown()` (issue #33).
1390 : //
1391 : // Note: enumeration relies on `OTelAPI.loggerProviders()`, added in
1392 : // API `1.0.0-beta.4`. Earlier versions only had access to the default
1393 : // provider, which is why beta.1 of this SDK left this as a documented
1394 : // gap — closed here.
1395 : try {
1396 152 : final loggerProviders = OTelAPI.loggerProviders();
1397 278 : for (final loggerProvider in loggerProviders) {
1398 : try {
1399 126 : if (OTelLog.isDebug()) {
1400 122 : OTelLog.debug('OTel: Shutting down logger provider');
1401 : }
1402 126 : await loggerProvider.shutdown();
1403 126 : if (OTelLog.isDebug()) {
1404 122 : OTelLog.debug('OTel: Logger provider shutdown complete');
1405 : }
1406 : } catch (e) {
1407 0 : if (OTelLog.isDebug()) {
1408 0 : OTelLog.debug('OTel: Error during logger provider shutdown: $e');
1409 : }
1410 : }
1411 : }
1412 : } catch (e) {
1413 0 : if (OTelLog.isDebug()) {
1414 0 : OTelLog.debug('OTel: Error accessing logger providers: $e');
1415 : }
1416 : }
1417 : }
1418 :
1419 : /// Resets the OTel state for testing purposes.
1420 : ///
1421 : /// This method should only be used in tests to reset the state between test runs.
1422 : /// It shuts down all tracer and meter providers and resets all static fields.
1423 : ///
1424 : /// @return A Future that completes when the reset is done
1425 152 : @visibleForTesting
1426 : static Future<void> reset() async {
1427 290 : if (OTelLog.isDebug()) OTelLog.debug('OTel: Resetting state');
1428 :
1429 152 : await shutdown();
1430 :
1431 : // Reset all static fields
1432 : _otelFactory = null;
1433 : _defaultSampler = null;
1434 : _defaultSpanExceptionOptions = null;
1435 : _defaultTimeProvider = null;
1436 : defaultResource = null;
1437 :
1438 : // Reset print interception state
1439 : if (_logBridge != null) {
1440 2 : DartLogBridge.uninstall();
1441 : }
1442 : _logBridge = null;
1443 : _printInterceptionZoneSpec = null;
1444 : _logPrintEnabled = false;
1445 : _logPrintLoggerName = 'dart.print';
1446 290 : if (OTelLog.isDebug()) OTelLog.debug('OTel: Reset static fields');
1447 :
1448 : // Reset API state
1449 : try {
1450 : // ignore: invalid_use_of_visible_for_testing_member
1451 152 : OTelAPI.reset();
1452 290 : if (OTelLog.isDebug()) OTelLog.debug('OTel: Reset OTelAPI');
1453 : } catch (e) {
1454 0 : if (OTelLog.isDebug()) OTelLog.debug('OTel: Error resetting OTelAPI: $e');
1455 : }
1456 :
1457 : // Reset OTelFactory
1458 152 : OTelFactory.otelFactory = null;
1459 290 : if (OTelLog.isDebug()) OTelLog.debug('OTel: Reset OTelFactory');
1460 :
1461 290 : if (OTelLog.isDebug()) OTelLog.debug('OTel: Cleared test environment');
1462 :
1463 : // Add a short delay to ensure resources are released
1464 152 : await Future<void>.delayed(const Duration(milliseconds: 250));
1465 290 : if (OTelLog.isDebug()) OTelLog.debug('OTel: Reset complete');
1466 : }
1467 :
1468 : /// Creates a new InstrumentationScope.
1469 : ///
1470 : /// [name] is required and represents the instrumentation scope name (e.g. 'io.opentelemetry.contrib.mongodb')
1471 : /// [version] is optional and specifies the version of the instrumentation scope, defaults to '1.0.0'
1472 : /// [schemaUrl] is optional and specifies the Schema URL
1473 : /// [attributes] is optional and specifies instrumentation scope attributes
1474 25 : static InstrumentationScope instrumentationScope({
1475 : required String name,
1476 : String version = '1.0.0',
1477 : String? schemaUrl,
1478 : Attributes? attributes,
1479 : }) {
1480 25 : return OTelAPI.instrumentationScope(
1481 : name: name,
1482 : version: version,
1483 : schemaUrl: schemaUrl,
1484 : attributes: attributes,
1485 : );
1486 : }
1487 : }
|