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

Generated by: LCOV version 2.0-1