LCOV - code coverage report
Current view: top level - lib/src - otel.dart (source / functions) Coverage Total Hit
Test: lcov.info Lines: 86.0 % 358 308
Test Date: 2026-07-11 13:35:19 Functions: - 0 0

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

Generated by: LCOV version 2.0-1