Line data Source code
1 : // Licensed under the Apache License, Version 2.0
2 : // Copyright 2025, Michael Bushe, All rights reserved.
3 :
4 : import 'dart:typed_data';
5 :
6 : import 'package:dartastic_opentelemetry/dartastic_opentelemetry.dart';
7 : import 'package:dartastic_opentelemetry_api/dartastic_opentelemetry_api.dart';
8 : import 'package:meta/meta.dart';
9 :
10 : /// Main entry point for the OpenTelemetry SDK.
11 : ///
12 : /// The [OTel] class provides static methods for initializing the SDK and
13 : /// creating OpenTelemetry objects such as Tracers, Spans, Meters, and other
14 : /// components necessary for instrumentation.
15 : ///
16 : /// To use the SDK, you must first call [initialize] to set up the global
17 : /// configuration and install the SDK implementation. After initialization,
18 : /// you can use the various factory methods to create OpenTelemetry objects.
19 : ///
20 : /// Example usage:
21 : /// ```dart
22 : /// await OTel.initialize(
23 : /// serviceName: 'my-service',
24 : /// serviceVersion: '1.0.0',
25 : /// endpoint: 'https://otel-collector.example.com:4317',
26 : /// );
27 : ///
28 : /// final tracer = OTel.tracer();
29 : /// final span = tracer.startSpan('my-operation');
30 : /// // ... perform work ...
31 : /// span.end();
32 : /// ```
33 : ///
34 : /// The tenant_id and the resources from platform resource detection are merged
35 : /// with resource attributes with resource attributes taking priority.
36 : /// The values must be valid Attribute types (String, bool, int, double, or
37 : /// List\<String>, List\<bool>, List\<int> or List\<double>).
38 : class OTel {
39 : static OTelSDKFactory? _otelFactory;
40 : static Sampler? _defaultSampler;
41 :
42 : /// Default resource for the SDK.
43 : ///
44 : /// This is set during initialization and used by tracer and meter providers
45 : /// that don't have a specific resource set.
46 : static Resource? defaultResource;
47 :
48 : /// API key for Dartastic.io backend, if used.
49 : static String? dartasticApiKey;
50 :
51 : /// Default service name used if none is provided.
52 : static const defaultServiceName = "@dart/dartastic_opentelemetry";
53 :
54 : /// Default OTEL endpoint
55 : static const defaultEndpoint = "http://localhost:4317";
56 :
57 : /// Default tracer name used if none is provided.
58 : static const String _defaultTracerName = 'dartastic';
59 :
60 : /// Default tracer name that can be customized.
61 3 : static String defaultTracerName = _defaultTracerName;
62 :
63 : /// Default tracer version.
64 : static String defaultTracerVersion = "1.0.0";
65 :
66 : /// Initializes the OpenTelemetry SDK with the specified configuration.
67 : ///
68 : /// This method must be called before any other OpenTelemetry operations.
69 : /// It sets up the global configuration and installs the SDK implementation.
70 : ///
71 : /// When OTelLog.debug is true or the environmental variable
72 : /// OTEL_CONSOLE_EXPORTER is set to true, a ConsoleExporter is added to the
73 : /// exports to print spans.
74 : ///
75 : /// @param endpoint The endpoint URL for the OpenTelemetry collector (default: http://localhost:4317)
76 : /// @param secure Whether to use TLS for the connection (default: true)
77 : /// @param serviceName Name that uniquely identifies the service (default: "@dart/dartastic_opentelemetry")
78 : /// @param serviceVersion Version of the service (defaults to the OTel spec version)
79 : /// @param tracerName Name of the default tracer (default: "dartastic")
80 : /// @param tracerVersion Version of the default tracer (default: null)
81 : /// @param resourceAttributes Additional attributes for the resource
82 : /// @param spanProcessor Custom span processor (default: BatchSpanProcessor with OtlpGrpcSpanExporter)
83 : /// @param sampler Sampling strategy to use (default: AlwaysOnSampler)
84 : /// @param spanKind Default span kind (default: SpanKind.server)
85 : /// @param metricExporter Custom metric exporter for metrics
86 : /// @param metricReader Custom metric reader for metrics
87 : /// @param enableMetrics Whether to enable metrics collection (default: true)
88 : /// @param dartasticApiKey API key for Dartastic.io backend
89 : /// @param tenantId Tenant ID for multi-tenant backends (required for Dartastic.io)
90 : /// @param detectPlatformResources Whether to detect platform resources (default: true)
91 : /// @param oTelFactoryCreationFunction Factory function for creating OTelSDKFactory instances
92 : /// @return A Future that completes when initialization is done
93 : /// @throws StateError if called more than once
94 : /// @throws ArgumentError if required parameters are invalid
95 73 : static Future<void> initialize({
96 : String? endpoint,
97 : bool? secure,
98 : String? serviceName,
99 : String? serviceVersion,
100 : String? tracerName,
101 : String? tracerVersion,
102 : Attributes? resourceAttributes,
103 : SpanProcessor? spanProcessor,
104 : Sampler sampler = const AlwaysOnSampler(),
105 : SpanKind spanKind = SpanKind.server,
106 : MetricExporter? metricExporter,
107 : MetricReader? metricReader,
108 : bool enableMetrics = true,
109 : String? dartasticApiKey,
110 : String? tenantId,
111 : bool detectPlatformResources = true,
112 : OTelFactoryCreationFunction? oTelFactoryCreationFunction =
113 : otelSDKFactoryFactoryFunction,
114 : }) async {
115 : // Apply environment variables only if parameters are not provided
116 : final envServiceName = serviceName == null
117 26 : ? OTelEnv.getServiceConfig()['serviceName'] as String?
118 : : null;
119 : final envServiceVersion = serviceVersion == null
120 114 : ? OTelEnv.getServiceConfig()['serviceVersion'] as String?
121 : : null;
122 :
123 : serviceName ??= envServiceName;
124 : serviceVersion ??= envServiceVersion;
125 :
126 : final otlpConfig = (endpoint == null || secure == null)
127 73 : ? OTelEnv.getOtlpConfig(signal: 'traces')
128 0 : : <String, dynamic>{};
129 : final envEndpoint =
130 55 : endpoint == null ? otlpConfig['endpoint'] as String? : null;
131 73 : final envInsecure = secure == null ? otlpConfig['insecure'] as bool? : null;
132 :
133 : endpoint ??= envEndpoint;
134 : if (secure == null) {
135 : if (envInsecure != null) {
136 : secure = !envInsecure;
137 : } else {
138 : secure = true;
139 : }
140 : }
141 :
142 : // Apply defaults if still null
143 : serviceName ??= defaultServiceName;
144 : serviceVersion ??= '1.0.0';
145 : endpoint ??= defaultEndpoint;
146 : // secure is guaranteed non-null from above
147 :
148 : // Log environment variable usage
149 73 : if (OTelLog.isDebug()) {
150 : if (envServiceName != null) {
151 0 : OTelLog.debug('Using service name from environment: $serviceName');
152 : }
153 : if (envServiceVersion != null) {
154 0 : OTelLog.debug(
155 0 : 'Using service version from environment: $serviceVersion');
156 : }
157 : if (envEndpoint != null) {
158 0 : OTelLog.debug('Using endpoint from environment: $endpoint');
159 : }
160 : if (envInsecure != null) {
161 0 : OTelLog.debug('Using insecure setting from environment: $envInsecure');
162 : }
163 : }
164 :
165 : // Get otlpConfig for exporter creation later
166 73 : final otlpConfigForExporter = OTelEnv.getOtlpConfig(signal: 'traces');
167 :
168 : // Get resource attributes from environment and merge with provided ones
169 73 : final envResourceAttrs = OTelEnv.getResourceAttributes();
170 73 : if (envResourceAttrs.isNotEmpty) {
171 : if (resourceAttributes != null) {
172 : // Merge with provided attributes - provided ones take precedence
173 0 : final mergedAttrs = Map<String, Object>.from(envResourceAttrs);
174 0 : resourceAttributes.toList().forEach((attr) {
175 0 : mergedAttrs[attr.key] = attr.value;
176 : });
177 0 : resourceAttributes = OTel.attributesFromMap(mergedAttrs);
178 : } else {
179 0 : resourceAttributes = OTel.attributesFromMap(envResourceAttrs);
180 : }
181 : }
182 : if (OTelFactory.otelFactory != null) {
183 1 : throw StateError(
184 : 'OTelAPI can only be initialized once. If you need multiple endpoints or service names or versions create a named TracerProvider');
185 : }
186 :
187 73 : if (endpoint.isEmpty) {
188 1 : throw ArgumentError(
189 : 'endpoint must not be the empty string.'); //TODO validate url
190 : }
191 73 : if (serviceName.isEmpty) {
192 1 : throw ArgumentError('serviceName must not be the empty string.');
193 : }
194 73 : if (serviceVersion.isEmpty) {
195 1 : throw ArgumentError('serviceVersion must not be the empty string.');
196 : }
197 : final factoryFactory =
198 : oTelFactoryCreationFunction ?? otelSDKFactoryFactoryFunction;
199 : // Initialize with default sampler
200 : _defaultSampler = sampler;
201 : OTel.defaultTracerName = tracerName ?? _defaultTracerName;
202 : OTel.defaultTracerVersion = tracerVersion ?? defaultTracerVersion;
203 : OTel.dartasticApiKey = dartasticApiKey;
204 : // Initialize logging from environment variables if needed
205 73 : initializeLogging();
206 :
207 73 : OTelFactory.otelFactory = factoryFactory(
208 : apiEndpoint: endpoint,
209 : apiServiceName: serviceName,
210 : apiServiceVersion: serviceVersion);
211 :
212 73 : if (OTelLog.isDebug()) {
213 73 : OTelLog.debug(
214 73 : 'OTel initialized with endpoint: $endpoint, service: $serviceName');
215 : }
216 :
217 73 : final serviceResourceAttributes = {
218 : 'service.name': serviceName,
219 : 'service.version': serviceVersion,
220 : };
221 : // Create initial resource with service attributes
222 : var baseResource =
223 146 : OTel.resource(OTel.attributesFromMap(serviceResourceAttributes));
224 :
225 : if (tenantId != null) {
226 : // Create a separate tenant_id resource to ensure it's preserved
227 : final tenantResource =
228 0 : OTel.resource(OTel.attributesFromMap({'tenant_id': tenantId}));
229 0 : if (OTelLog.isDebug()) {
230 0 : OTelLog.debug(
231 0 : 'OTel.initialize: Creating tenant_id resource with: $tenantId');
232 : }
233 : // Merge tenant into the base resource
234 0 : baseResource = baseResource.merge(tenantResource);
235 : }
236 :
237 : // Initialize with tenant-aware resource
238 : var mergedResource = baseResource;
239 : if (detectPlatformResources) {
240 45 : final resourceDetector = PlatformResourceDetector.create();
241 45 : final platformResource = await resourceDetector.detect();
242 : // Merge platform resource with our service resource - our service resource takes precedence
243 45 : mergedResource = platformResource.merge(mergedResource);
244 :
245 45 : if (OTelLog.isDebug()) {
246 45 : OTelLog.debug('Resource after platform merge:');
247 180 : mergedResource.attributes.toList().forEach((attr) {
248 180 : if (attr.key == 'tenant_id' || attr.key == 'service.name') {
249 180 : OTelLog.debug(' ${attr.key}: ${attr.value}');
250 : }
251 : });
252 : }
253 : }
254 : if (resourceAttributes != null) {
255 0 : final initResources = OTel.resource(resourceAttributes);
256 : // Merge user-provided attributes - they have highest priority
257 0 : mergedResource = mergedResource.merge(initResources);
258 :
259 0 : if (OTelLog.isDebug()) {
260 0 : OTelLog.debug('Resource after user attributes merge:');
261 0 : mergedResource.attributes.toList().forEach((attr) {
262 0 : if (attr.key == 'tenant_id' || attr.key == 'service.name') {
263 0 : OTelLog.debug(' ${attr.key}: ${attr.value}');
264 : }
265 : });
266 : }
267 : }
268 : // Set the final merged resource as default
269 : OTel.defaultResource = mergedResource;
270 :
271 73 : if (OTelLog.isDebug()) {
272 : // Final check to ensure tenant_id is preserved
273 : if (tenantId != null && OTel.defaultResource != null) {
274 : bool hasTenantId = false;
275 0 : OTel.defaultResource!.attributes.toList().forEach((attr) {
276 0 : if (attr.key == 'tenant_id') {
277 : hasTenantId = true;
278 0 : if (OTelLog.isDebug()) {
279 0 : OTelLog.debug(
280 0 : 'Final resource check - tenant_id is present: ${attr.value}');
281 : }
282 : }
283 : });
284 :
285 : if (!hasTenantId) {
286 : // As a last resort, add the tenant_id directly
287 0 : if (OTelLog.isDebug()) {
288 0 : OTelLog.debug('tenant_id was missing - adding it as fallback');
289 : }
290 : final tenantResource =
291 0 : OTel.resource(OTel.attributesFromMap({'tenant_id': tenantId}));
292 0 : OTel.defaultResource = OTel.defaultResource!.merge(tenantResource);
293 : }
294 : }
295 : }
296 :
297 : if (spanProcessor == null) {
298 : // Determine which exporter to create based on environment or defaults
299 72 : final exporterType = OTelEnv.getExporter(signal: 'traces') ?? 'otlp';
300 :
301 72 : if (exporterType != 'none') {
302 : // Determine protocol - default to http/protobuf if not set
303 : final protocol =
304 72 : otlpConfigForExporter['protocol'] as String? ?? 'http/protobuf';
305 :
306 : SpanExporter exporter;
307 72 : if (exporterType == 'console') {
308 0 : exporter = ConsoleExporter();
309 72 : } else if (exporterType == 'otlp') {
310 : // Create appropriate exporter based on protocol
311 72 : if (protocol == 'grpc') {
312 0 : exporter = OtlpGrpcSpanExporter(
313 0 : OtlpGrpcExporterConfig(
314 : endpoint: endpoint,
315 : insecure: !secure,
316 : headers:
317 0 : otlpConfigForExporter['headers'] as Map<String, String>? ??
318 0 : {},
319 0 : timeout: otlpConfigForExporter['timeout'] as Duration? ??
320 : const Duration(seconds: 10),
321 0 : compression: otlpConfigForExporter['compression'] == 'gzip',
322 0 : certificate: otlpConfigForExporter['certificate'] as String?,
323 0 : clientKey: otlpConfigForExporter['clientKey'] as String?,
324 : clientCertificate:
325 0 : otlpConfigForExporter['clientCertificate'] as String?,
326 : ),
327 : );
328 : } else {
329 : // Default to http/protobuf
330 : // For HTTP, adjust endpoint if it's the gRPC default
331 : String httpEndpoint = endpoint;
332 72 : if (endpoint == defaultEndpoint) {
333 : httpEndpoint = 'http://localhost:4318';
334 : }
335 72 : exporter = OtlpHttpSpanExporter(
336 72 : OtlpHttpExporterConfig(
337 : endpoint: httpEndpoint,
338 : headers:
339 72 : otlpConfigForExporter['headers'] as Map<String, String>? ??
340 72 : {},
341 72 : timeout: otlpConfigForExporter['timeout'] as Duration? ??
342 : const Duration(seconds: 10),
343 144 : compression: otlpConfigForExporter['compression'] == 'gzip',
344 72 : certificate: otlpConfigForExporter['certificate'] as String?,
345 72 : clientKey: otlpConfigForExporter['clientKey'] as String?,
346 : clientCertificate:
347 72 : otlpConfigForExporter['clientCertificate'] as String?,
348 : ),
349 : );
350 : }
351 : } else {
352 : // Fallback to gRPC for backward compatibility
353 0 : exporter = OtlpGrpcSpanExporter(
354 0 : OtlpGrpcExporterConfig(
355 : endpoint: endpoint,
356 : insecure: !secure,
357 : ),
358 : );
359 : }
360 :
361 : // Only add ConsoleExporter in debug mode or if explicitly requested
362 72 : final exporters = <SpanExporter>[exporter];
363 72 : if (OTelLog.isDebug() ||
364 : const bool.fromEnvironment('OTEL_CONSOLE_EXPORTER',
365 : defaultValue: false)) {
366 144 : exporters.add(ConsoleExporter());
367 : }
368 :
369 72 : spanProcessor = BatchSpanProcessor(
370 216 : exporters.length == 1 ? exporter : CompositeExporter(exporters),
371 : const BatchSpanProcessorConfig(
372 : maxQueueSize: 2048,
373 : scheduleDelay: Duration(seconds: 1),
374 : maxExportBatchSize: 512,
375 : ),
376 : );
377 : }
378 : // If exporterType == 'none', spanProcessor remains null and no processor is added
379 : }
380 :
381 : // Create and configure TracerProvider
382 : if (spanProcessor != null) {
383 146 : OTel.tracerProvider().addSpanProcessor(spanProcessor);
384 : }
385 :
386 : // Configure metrics if enabled
387 : if (enableMetrics) {
388 : // If no explicit metric exporter is provided, create one with the same endpoint
389 : if (metricExporter == null && metricReader == null) {
390 58 : MetricsConfiguration.configureMeterProvider(
391 : endpoint: endpoint,
392 : secure: secure,
393 : resource: OTel.defaultResource,
394 : );
395 : } else {
396 : // Use the provided exporter and/or reader
397 12 : MetricsConfiguration.configureMeterProvider(
398 : endpoint: endpoint,
399 : secure: secure,
400 : metricExporter: metricExporter,
401 : metricReader: metricReader,
402 : resource: OTel.defaultResource,
403 : );
404 : }
405 : }
406 : }
407 :
408 : /// Creates a Resource with the specified attributes and schema URL.
409 : ///
410 : /// Resources represent the entity producing telemetry, such as a service,
411 : /// process, or device. They are a collection of attributes that provide
412 : /// identifying information about the entity.
413 : ///
414 : /// @param attributes Attributes describing the resource
415 : /// @param schemaUrl Optional URL of the schema defining the attributes
416 : /// @return A new Resource instance
417 73 : static Resource resource(Attributes? attributes, [String? schemaUrl]) {
418 73 : _getAndCacheOtelFactory();
419 : return (_otelFactory as OTelSDKFactory)
420 73 : .resource(attributes ?? OTel.attributes(), schemaUrl);
421 : }
422 :
423 : /// Creates a new ContextKey with the given name.
424 : ///
425 : /// Context keys are used to store and retrieve values in a Context.
426 : /// Each instance will be unique, even with the same name, per the OTel spec.
427 : /// The name is for debugging purposes only.
428 : ///
429 : /// @param name The name of the context key (for debugging only)
430 : /// @return A new ContextKey instance
431 1 : static ContextKey<T> contextKey<T>(String name) {
432 1 : _getAndCacheOtelFactory();
433 2 : return _otelFactory!.contextKey(name, ContextKey.generateContextKeyId());
434 : }
435 :
436 : /// Creates a new Context with optional Baggage and SpanContext.
437 : ///
438 : /// Contexts are used to propagate information across the execution path,
439 : /// such as trace context, baggage, and other cross-cutting concerns.
440 : ///
441 : /// @param baggage Optional baggage to include in the context
442 : /// @param spanContext Optional span context to include in the context
443 : /// @return A new Context instance
444 11 : static Context context({Baggage? baggage, SpanContext? spanContext}) {
445 11 : _getAndCacheOtelFactory();
446 11 : var context = OTelFactory.otelFactory!.context(baggage: baggage);
447 : if (spanContext != null) {
448 1 : context = context.copyWithSpanContext(spanContext);
449 : }
450 : return context;
451 : }
452 :
453 : /// Gets a TracerProvider for creating Tracers.
454 : ///
455 : /// If name is null, this returns the global default TracerProvider, which shares
456 : /// the endpoint, serviceName, serviceVersion, sampler and resource set in initialize().
457 : /// If the name is not null, it returns a TracerProvider for the name that was added
458 : /// with addTracerProvider.
459 : ///
460 : /// The endpoint, serviceName, serviceVersion, sampler and resource set flow down
461 : /// to the [Tracer]s created by the TracerProvider and the [Span]
462 : /// created by those tracers
463 : /// @param name Optional name of a specific TracerProvider
464 : /// @return The TracerProvider instance
465 73 : static TracerProvider tracerProvider({String? name}) {
466 73 : final tracerProvider = OTelAPI.tracerProvider(name) as TracerProvider;
467 : // Ensure the resource is properly set
468 73 : if (tracerProvider.resource == null && defaultResource != null) {
469 73 : tracerProvider.resource = defaultResource;
470 73 : if (OTelLog.isDebug()) {
471 73 : OTelLog.debug('OTel.tracerProvider: Setting resource from default');
472 : if (defaultResource != null) {
473 292 : defaultResource!.attributes.toList().forEach((attr) {
474 292 : if (attr.key == 'tenant_id' || attr.key == 'service.name') {
475 292 : OTelLog.debug(' ${attr.key}: ${attr.value}');
476 : }
477 : });
478 : }
479 : }
480 : }
481 :
482 73 : tracerProvider.sampler ??= _defaultSampler;
483 : return tracerProvider;
484 : }
485 :
486 : /// Gets a MeterProvider for creating Meters.
487 : ///
488 : /// If name is null, this returns the global default MeterProvider, which shares
489 : /// the endpoint, serviceName, serviceVersion and resource set in initialize().
490 : /// If the name is not null, it returns a MeterProvider for the name that was added
491 : /// with addMeterProvider.
492 : ///
493 : /// @param name Optional name of a specific MeterProvider
494 : /// @return The MeterProvider instance
495 69 : static MeterProvider meterProvider({String? name}) {
496 69 : final meterProvider = OTelAPI.meterProvider(name) as MeterProvider;
497 69 : meterProvider.resource ??= defaultResource;
498 : return meterProvider;
499 : }
500 :
501 : /// Adds or replaces a named TracerProvider.
502 : ///
503 : /// This allows for creating multiple TracerProviders with different configurations,
504 : /// which can be useful for sending telemetry to different backends or with different
505 : /// settings.
506 : ///
507 : /// @param name The name of the TracerProvider
508 : /// @param endpoint Optional custom endpoint URL
509 : /// @param serviceName Optional custom service name
510 : /// @param serviceVersion Optional custom service version
511 : /// @param resource Optional custom resource
512 : /// @param sampler Optional custom sampler
513 : /// @return The newly created or replaced TracerProvider
514 5 : static TracerProvider addTracerProvider(
515 : String name, {
516 : String? endpoint,
517 : String? serviceName,
518 : String? serviceVersion,
519 : Resource? resource,
520 : Sampler? sampler,
521 : }) {
522 5 : final sdkTracerProvider = OTelAPI.addTracerProvider(name) as TracerProvider;
523 5 : sdkTracerProvider.resource = resource ?? defaultResource;
524 5 : sdkTracerProvider.sampler = sampler ?? _defaultSampler;
525 : return sdkTracerProvider;
526 : }
527 :
528 : /// @return the [TracerProvider]s, the global default and named ones.
529 73 : static List<APITracerProvider> tracerProviders() {
530 73 : return OTelAPI.tracerProviders();
531 : }
532 :
533 : /// Gets the default Tracer from the default TracerProvider.
534 : ///
535 : /// This is a convenience method for getting a Tracer with the default configuration.
536 : /// The endpoint, serviceName, serviceVersion, sampler and resource all flow down
537 : /// from the OTel defaults set during initialization.
538 : ///
539 : /// @return The default Tracer instance
540 3 : static Tracer tracer() {
541 6 : return tracerProvider().getTracer(
542 3 : defaultTracerName,
543 : version: defaultTracerVersion,
544 : );
545 : }
546 :
547 : /// Adds or replaces a named MeterProvider.
548 : ///
549 : /// This allows for creating multiple MeterProviders with different configurations,
550 : /// which can be useful for sending metrics to different backends or with different
551 : /// settings.
552 : ///
553 : /// @param name The name of the MeterProvider
554 : /// @param endpoint Optional custom endpoint URL
555 : /// @param serviceName Optional custom service name
556 : /// @param serviceVersion Optional custom service version
557 : /// @param resource Optional custom resource
558 : /// @return The newly created or replaced MeterProvider
559 1 : static MeterProvider addMeterProvider(
560 : String name, {
561 : String? endpoint,
562 : String? serviceName,
563 : String? serviceVersion,
564 : Resource? resource,
565 : }) {
566 1 : _getAndCacheOtelFactory();
567 1 : final mp = _otelFactory!.addMeterProvider(name,
568 : endpoint: endpoint,
569 : serviceName: serviceName,
570 : serviceVersion: serviceVersion) as MeterProvider;
571 1 : mp.resource = resource ?? defaultResource;
572 : return mp;
573 : }
574 :
575 : /// @return the [MeterProvider]s, the global default and named ones.
576 73 : static List<APIMeterProvider> meterProviders() {
577 73 : return OTelAPI.meterProviders();
578 : }
579 :
580 : /// Gets the default Meter from the default MeterProvider.
581 : ///
582 : /// This is a convenience method for getting a Meter with the default configuration.
583 : /// The endpoint, serviceName, serviceVersion and resource all flow down from
584 : /// the OTel defaults set during initialization.
585 : ///
586 : /// @param name Optional custom name for the meter (defaults to defaultTracerName)
587 : /// @return The default Meter instance
588 12 : static Meter meter([String? name]) {
589 24 : return meterProvider().getMeter(
590 1 : name: name ?? defaultTracerName,
591 : version: defaultTracerVersion) as Meter;
592 : }
593 :
594 : /// Creates a SpanContext with the specified parameters.
595 : ///
596 : /// A SpanContext represents the portion of a span that must be propagated
597 : /// to descendant spans and across process boundaries. It contains the
598 : /// traceId, spanId, traceFlags, and traceState.
599 : ///
600 : /// @param traceId The trace ID (defaults to a new random ID)
601 : /// @param spanId The span ID (defaults to a new random ID)
602 : /// @param parentSpanId The parent span ID (defaults to an invalid span ID)
603 : /// @param traceFlags Trace flags (defaults to NONE_FLAG)
604 : /// @param traceState Trace state
605 : /// @param isRemote Whether this context was received from a remote source
606 : /// @return A new SpanContext instance
607 35 : static SpanContext spanContext(
608 : {TraceId? traceId,
609 : SpanId? spanId,
610 : SpanId? parentSpanId,
611 : TraceFlags? traceFlags,
612 : TraceState? traceState,
613 : bool? isRemote}) {
614 35 : return OTelAPI.spanContext(
615 2 : traceId: traceId ?? OTel.traceId(),
616 2 : spanId: spanId ?? OTel.spanId(),
617 13 : parentSpanId: parentSpanId ?? spanIdInvalid(),
618 9 : traceFlags: traceFlags ?? OTelAPI.traceFlags(),
619 : traceState: traceState,
620 : isRemote: isRemote,
621 : );
622 : }
623 :
624 : /// Creates a child SpanContext from a parent context.
625 : ///
626 : /// This creates a new SpanContext that shares the same traceId as the parent,
627 : /// but has a new spanId and the parentSpanId set to the parent's spanId.
628 : ///
629 : /// @param parent The parent SpanContext
630 : /// @return A new child SpanContext
631 1 : static SpanContext spanContextFromParent(SpanContext parent) {
632 1 : _getAndCacheOtelFactory();
633 1 : return OTelFactory.otelFactory!.spanContextFromParent(parent);
634 : }
635 :
636 : /// Creates an invalid SpanContext (all zeros).
637 : ///
638 : /// An invalid SpanContext represents the absence of a trace context.
639 : ///
640 : /// @return An invalid SpanContext instance
641 1 : static SpanContext spanContextInvalid() {
642 1 : _getAndCacheOtelFactory();
643 1 : return OTelFactory.otelFactory!.spanContextInvalid();
644 : }
645 :
646 : /// Creates a SpanEvent with the current timestamp.
647 : ///
648 : /// Note: Per [OTEP 0265](https://opentelemetry.io/docs/specs/semconv/general/events/),
649 : /// span events are being deprecated and will be replaced by the Logging API in future versions.
650 : ///
651 : /// @param name The name of the event
652 : /// @param attributes Attributes to associate with the event
653 : /// @return A new SpanEvent instance with the current timestamp
654 0 : static SpanEvent spanEventNow(String name, Attributes attributes) {
655 0 : _getAndCacheOtelFactory();
656 0 : return spanEvent(name, attributes, DateTime.now());
657 : }
658 :
659 : /// Creates a SpanEvent with the specified parameters.
660 : ///
661 : /// Note: Per [OTEP 0265](https://opentelemetry.io/docs/specs/semconv/general/events/),
662 : /// span events are being deprecated and will be replaced by the Logging API in future versions.
663 : ///
664 : /// @param name The name of the event
665 : /// @param attributes Optional attributes to associate with the event
666 : /// @param timestamp Optional timestamp for the event (defaults to null)
667 : /// @return A new SpanEvent instance
668 2 : static SpanEvent spanEvent(String name,
669 : [Attributes? attributes, DateTime? timestamp]) {
670 2 : _getAndCacheOtelFactory();
671 2 : return _otelFactory!.spanEvent(name, attributes, timestamp);
672 : }
673 :
674 : /// Creates a Baggage with key-value pairs.
675 : ///
676 : /// Baggage is a set of key-value pairs that can be propagated across service boundaries
677 : /// along with the trace context. It can be used to add contextual information to traces.
678 : ///
679 : /// @param keyValuePairs A map of key-value pairs to include in the baggage
680 : /// @return A new Baggage instance
681 0 : static Baggage baggageForMap(Map<String, String> keyValuePairs) {
682 0 : _getAndCacheOtelFactory();
683 0 : return _otelFactory!.baggageForMap(keyValuePairs);
684 : }
685 :
686 : /// Creates a BaggageEntry with the specified value and optional metadata.
687 : ///
688 : /// @param value The value of the baggage entry
689 : /// @param metadata Optional metadata for the baggage entry
690 : /// @return A new BaggageEntry instance
691 2 : static BaggageEntry baggageEntry(String value, [String? metadata]) {
692 2 : _getAndCacheOtelFactory();
693 2 : return _otelFactory!.baggageEntry(value, metadata);
694 : }
695 :
696 : /// Creates a Baggage with the specified entries.
697 : ///
698 : /// @param entries Optional map of baggage entries
699 : /// @return A new Baggage instance
700 2 : static Baggage baggage([Map<String, BaggageEntry>? entries]) {
701 2 : _getAndCacheOtelFactory();
702 2 : return _otelFactory!.baggage(entries);
703 : }
704 :
705 : /// Creates a Baggage instance from a JSON representation.
706 : ///
707 : /// @param json JSON representation of a baggage
708 : /// @return A new Baggage instance
709 0 : static Baggage baggageFromJson(Map<String, dynamic> json) {
710 0 : return OTelAPI.baggageFromJson(json);
711 : }
712 :
713 : /// Creates a string attribute.
714 : ///
715 : /// @param name The name of the attribute
716 : /// @param value The string value of the attribute
717 : /// @return A new Attribute instance
718 2 : static Attribute<String> attributeString(String name, String value) {
719 2 : _getAndCacheOtelFactory();
720 2 : return _otelFactory!.attributeString(name, value);
721 : }
722 :
723 : /// Creates a boolean attribute.
724 : ///
725 : /// @param name The name of the attribute
726 : /// @param value The boolean value of the attribute
727 : /// @return A new Attribute instance
728 1 : static Attribute<bool> attributeBool(String name, bool value) {
729 1 : _getAndCacheOtelFactory();
730 1 : return _otelFactory!.attributeBool(name, value);
731 : }
732 :
733 : /// Creates an integer attribute.
734 : ///
735 : /// @param name The name of the attribute
736 : /// @param value The integer value of the attribute
737 : /// @return A new Attribute instance
738 2 : static Attribute<int> attributeInt(String name, int value) {
739 2 : _getAndCacheOtelFactory();
740 2 : return _otelFactory!.attributeInt(name, value);
741 : }
742 :
743 : /// Creates a double attribute.
744 : ///
745 : /// @param name The name of the attribute
746 : /// @param value The double value of the attribute
747 : /// @return A new Attribute instance
748 1 : static Attribute<double> attributeDouble(String name, double value) {
749 1 : _getAndCacheOtelFactory();
750 1 : return _otelFactory!.attributeDouble(name, value);
751 : }
752 :
753 : /// Creates a string list attribute.
754 : ///
755 : /// @param name The name of the attribute
756 : /// @param value The list of string values
757 : /// @return A new Attribute instance
758 0 : static Attribute<List<String>> attributeStringList(
759 : String name, List<String> value) {
760 0 : _getAndCacheOtelFactory();
761 0 : return _otelFactory!.attributeStringList(name, value);
762 : }
763 :
764 : /// Creates a boolean list attribute.
765 : ///
766 : /// @param name The name of the attribute
767 : /// @param value The list of boolean values
768 : /// @return A new Attribute instance
769 0 : static Attribute<List<bool>> attributeBoolList(
770 : String name, List<bool> value) {
771 0 : _getAndCacheOtelFactory();
772 0 : return _otelFactory!.attributeBoolList(name, value);
773 : }
774 :
775 : /// Creates an integer list attribute.
776 : ///
777 : /// @param name The name of the attribute
778 : /// @param value The list of integer values
779 : /// @return A new Attribute instance
780 0 : static Attribute<List<int>> attributeIntList(String name, List<int> value) {
781 0 : _getAndCacheOtelFactory();
782 0 : return _otelFactory!.attributeIntList(name, value);
783 : }
784 :
785 : /// Creates a double list attribute.
786 : ///
787 : /// @param name The name of the attribute
788 : /// @param value The list of double values
789 : /// @return A new Attribute instance
790 0 : static Attribute<List<double>> attributeDoubleList(
791 : String name, List<double> value) {
792 0 : _getAndCacheOtelFactory();
793 0 : return _otelFactory!.attributeDoubleList(name, value);
794 : }
795 :
796 : /// Creates an empty Attributes collection.
797 : ///
798 : /// @return A new empty Attributes collection
799 0 : static Attributes createAttributes() {
800 0 : _getAndCacheOtelFactory();
801 0 : return _otelFactory!.attributes();
802 : }
803 :
804 : /// Creates an Attributes collection from a list of Attribute objects.
805 : ///
806 : /// @param entries Optional list of Attribute objects
807 : /// @return A new Attributes collection
808 8 : static Attributes attributes([List<Attribute>? entries]) {
809 : // Cheating here since Attributes is unlikely to be overriden in a
810 : // factory and is often called before initialize
811 : return _otelFactory == null
812 0 : ? AttributesCreate.create(entries ?? [])
813 8 : : _otelFactory!.attributes(entries);
814 : }
815 :
816 : /// Creates an Attributes collection from a map of named values.
817 : ///
818 : /// String, bool, int, double, or Lists of those types get converted
819 : /// to the matching typed attribute. DateTime gets converted to a
820 : /// String attribute with the UTC time string.
821 : ///
822 : /// Unlike most methods, this does not create the OTelFactory if
823 : /// one does not exist, instead it uses the OTelAPI's attributesFromMap.
824 : ///
825 : /// Alternatively, consider using the toAttributes()
826 : /// extension on \<String, Map>{}.
827 : /// @param namedMap Map of attribute names to values
828 : /// @return A new Attributes collection
829 73 : static Attributes attributesFromMap(Map<String, Object> namedMap) {
830 : if (_otelFactory == null) {
831 73 : return OTelAPI.attributesFromMap(namedMap);
832 : } else {
833 45 : return _otelFactory!.attributesFromMap(namedMap);
834 : }
835 : }
836 :
837 : /// Creates an Attributes collection from a list of Attribute objects.
838 : ///
839 : /// @param attributeList List of Attribute objects
840 : /// @return A new Attributes collection
841 1 : static Attributes attributesFromList(List<Attribute> attributeList) {
842 1 : _getAndCacheOtelFactory();
843 1 : return _otelFactory!.attributesFromList(attributeList);
844 : }
845 :
846 : /// Creates a TraceState with the specified entries.
847 : ///
848 : /// TraceState carries vendor-specific trace identification data across systems.
849 : ///
850 : /// @param entries Optional map of key-value pairs for the trace state
851 : /// @return A new TraceState instance
852 6 : static TraceState traceState(Map<String, String>? entries) {
853 6 : _getAndCacheOtelFactory();
854 6 : return _otelFactory!.traceState(entries);
855 : }
856 :
857 : /// Creates TraceFlags with the specified flags.
858 : ///
859 : /// TraceFlags are used to encode bit field flags in the trace context.
860 : /// The most commonly used flag is SAMPLED_FLAG, which indicates
861 : /// that the trace should be sampled.
862 : ///
863 : /// @param flags Optional flags value (default: NONE_FLAG)
864 : /// @return A new TraceFlags instance
865 32 : static TraceFlags traceFlags([int? flags]) {
866 32 : _getAndCacheOtelFactory();
867 32 : return _otelFactory!.traceFlags(flags ?? TraceFlags.NONE_FLAG);
868 : }
869 :
870 : /// Generates a new random TraceId.
871 : ///
872 : /// @return A new random TraceId
873 33 : static TraceId traceId() {
874 66 : return traceIdOf(IdGenerator.generateTraceId());
875 : }
876 :
877 : /// Creates a TraceId from the specified bytes.
878 : ///
879 : /// @param traceId The bytes for the trace ID (must be exactly 16 bytes)
880 : /// @return A new TraceId instance
881 : /// @throws ArgumentError if traceId is not exactly 16 bytes
882 34 : static TraceId traceIdOf(Uint8List traceId) {
883 34 : _getAndCacheOtelFactory();
884 68 : if (traceId.length != TraceId.traceIdLength) {
885 0 : throw ArgumentError(
886 0 : 'Trace ID must be exactly ${TraceId.traceIdLength} bytes, got ${traceId.length} bytes');
887 : }
888 34 : return OTelFactory.otelFactory!.traceId(traceId);
889 : }
890 :
891 : /// Creates a TraceId from a hex string.
892 : ///
893 : /// @param hexString Hexadecimal representation of the trace ID
894 : /// @return A new TraceId instance
895 6 : static TraceId traceIdFrom(String hexString) {
896 6 : return OTelAPI.traceIdFrom(hexString);
897 : }
898 :
899 : /// Creates an invalid TraceId (all zeros).
900 : ///
901 : /// @return An invalid TraceId instance
902 1 : static TraceId traceIdInvalid() {
903 2 : return traceIdOf(TraceId.invalidTraceIdBytes);
904 : }
905 :
906 : /// Generates a new random SpanId.
907 : ///
908 : /// @return A new random SpanId
909 32 : static SpanId spanId() {
910 64 : return spanIdOf(IdGenerator.generateSpanId());
911 : }
912 :
913 : /// Creates a SpanId from the specified bytes.
914 : ///
915 : /// @param spanId The bytes for the span ID (must be exactly 8 bytes)
916 : /// @return A new SpanId instance
917 : /// @throws ArgumentError if spanId is not exactly 8 bytes
918 37 : static SpanId spanIdOf(Uint8List spanId) {
919 37 : _getAndCacheOtelFactory();
920 74 : if (spanId.length != 8) {
921 0 : throw ArgumentError(
922 0 : 'Span ID must be exactly 8 bytes, got ${spanId.length} bytes');
923 : }
924 37 : return _otelFactory!.spanId(spanId);
925 : }
926 :
927 : /// Creates a SpanId from a hex string.
928 : ///
929 : /// @param hexString Hexadecimal representation of the span ID
930 : /// @return A new SpanId instance
931 6 : static SpanId spanIdFrom(String hexString) {
932 6 : return OTelAPI.spanIdFrom(hexString);
933 : }
934 :
935 : /// Creates an invalid SpanId (all zeros).
936 : ///
937 : /// @return An invalid SpanId instance
938 35 : static SpanId spanIdInvalid() {
939 70 : return spanIdOf(SpanId.invalidSpanIdBytes);
940 : }
941 :
942 : /// Creates a SpanLink with the specified SpanContext and optional attributes.
943 : ///
944 : /// SpanLinks are used to associate spans that may be causally related
945 : /// but not via a parent-child relationship.
946 : ///
947 : /// @param spanContext The SpanContext to link to
948 : /// @param attributes Optional attributes to associate with the link
949 : /// @return A new SpanLink instance
950 3 : static SpanLink spanLink(SpanContext spanContext, {Attributes? attributes}) {
951 3 : _getAndCacheOtelFactory();
952 3 : return _otelFactory!.spanLink(spanContext, attributes: attributes);
953 : }
954 :
955 : /// Retrieves and caches the OTelFactory instance.
956 : ///
957 : /// @return The OTelFactory instance
958 : /// @throws StateError if initialize() has not been called
959 73 : static OTelFactory _getAndCacheOtelFactory() {
960 : if (_otelFactory != null) {
961 : return _otelFactory!;
962 : }
963 : if (OTelFactory.otelFactory == null) {
964 0 : throw StateError('initialize() must be called first.');
965 : }
966 : return _otelFactory = OTelFactory.otelFactory! as OTelSDKFactory;
967 : }
968 :
969 : /// Initializes logging based on environment variables.
970 : ///
971 : /// This can be called separately from initialize(), but initialize() will
972 : /// call it automatically if not already done.
973 73 : static void initializeLogging() {
974 : // Initialize log settings from environment variables
975 73 : OTelEnv.initializeLogging();
976 :
977 73 : if (OTelLog.isDebug()) {
978 73 : OTelLog.debug('OTel logging initialized');
979 : }
980 : }
981 :
982 : /// Flushes and shuts down trace and metric providers,
983 : /// processors and exporters. Typically called from [OTel.shutdown]
984 73 : static Future<void> shutdown() async {
985 : // Shutdown any tracer providers to clean up span processors
986 : try {
987 73 : final tracerProviders = OTel.tracerProviders();
988 145 : for (final tracerProvider in tracerProviders) {
989 72 : if (OTelLog.isDebug()) {
990 71 : OTelLog.debug('OTel: Shutting down tracer providers');
991 : }
992 72 : if (tracerProvider is TracerProvider) {
993 : try {
994 72 : await tracerProvider.forceFlush();
995 72 : if (OTelLog.isDebug()) {
996 71 : OTelLog.debug('OTel: Tracer provider flush complete');
997 : }
998 : } catch (e) {
999 0 : if (OTelLog.isDebug()) {
1000 0 : OTelLog.debug('OTel: Error during tracer provider flush: $e');
1001 : }
1002 : }
1003 : }
1004 : try {
1005 72 : await tracerProvider.shutdown();
1006 72 : if (OTelLog.isDebug()) {
1007 71 : OTelLog.debug('OTel: Tracer provider shutdown complete');
1008 : }
1009 : } catch (e) {
1010 0 : if (OTelLog.isDebug()) {
1011 0 : OTelLog.debug('OTel: Error during tracer provider shutdown: $e');
1012 : }
1013 : }
1014 : }
1015 : } catch (e) {
1016 0 : if (OTelLog.isDebug()) {
1017 0 : OTelLog.debug('OTel: Error accessing tracer provider: $e');
1018 : }
1019 : }
1020 :
1021 : // Shutdown meter providers to clean up metric readers and exporters
1022 73 : final meterProviders = OTel.meterProviders();
1023 141 : for (var meterProvider in meterProviders) {
1024 : try {
1025 68 : if (OTelLog.isDebug()) {
1026 67 : OTelLog.debug('OTel: Shutting down meter provider');
1027 : }
1028 68 : await meterProvider.shutdown();
1029 68 : if (OTelLog.isDebug()) {
1030 67 : OTelLog.debug('OTel: Meter provider shutdown complete');
1031 : }
1032 : } catch (e) {
1033 0 : if (OTelLog.isDebug()) {
1034 0 : OTelLog.debug('OTel: Error during meter provider shutdown: $e');
1035 : }
1036 : }
1037 : }
1038 : }
1039 :
1040 : /// Resets the OTel state for testing purposes.
1041 : ///
1042 : /// This method should only be used in tests to reset the state between test runs.
1043 : /// It shuts down all tracer and meter providers and resets all static fields.
1044 : ///
1045 : /// @return A Future that completes when the reset is done
1046 73 : @visibleForTesting
1047 : static Future<void> reset() async {
1048 144 : if (OTelLog.isDebug()) OTelLog.debug('OTel: Resetting state');
1049 :
1050 73 : await shutdown();
1051 :
1052 : // Reset all static fields
1053 : _otelFactory = null;
1054 : _defaultSampler = null;
1055 : defaultResource = null;
1056 : dartasticApiKey = null;
1057 144 : if (OTelLog.isDebug()) OTelLog.debug('OTel: Reset static fields');
1058 :
1059 : // Reset API state
1060 : try {
1061 : // ignore: invalid_use_of_visible_for_testing_member
1062 73 : OTelAPI.reset();
1063 144 : if (OTelLog.isDebug()) OTelLog.debug('OTel: Reset OTelAPI');
1064 : } catch (e) {
1065 0 : if (OTelLog.isDebug()) OTelLog.debug('OTel: Error resetting OTelAPI: $e');
1066 : }
1067 :
1068 : // Reset OTelFactory
1069 : OTelFactory.otelFactory = null;
1070 144 : if (OTelLog.isDebug()) OTelLog.debug('OTel: Reset OTelFactory');
1071 :
1072 144 : if (OTelLog.isDebug()) OTelLog.debug('OTel: Cleared test environment');
1073 :
1074 : // Add a short delay to ensure resources are released
1075 73 : await Future<void>.delayed(const Duration(milliseconds: 250));
1076 144 : if (OTelLog.isDebug()) OTelLog.debug('OTel: Reset complete');
1077 : }
1078 :
1079 : /// Creates a new InstrumentationScope.
1080 : ///
1081 : /// [name] is required and represents the instrumentation scope name (e.g. 'io.opentelemetry.contrib.mongodb')
1082 : /// [version] is optional and specifies the version of the instrumentation scope, defaults to '1.0.0'
1083 : /// [schemaUrl] is optional and specifies the Schema URL
1084 : /// [attributes] is optional and specifies instrumentation scope attributes
1085 1 : static InstrumentationScope instrumentationScope(
1086 : {required String name,
1087 : String version = '1.0.0',
1088 : String? schemaUrl,
1089 : Attributes? attributes}) {
1090 1 : return OTelAPI.instrumentationScope(
1091 : name: name,
1092 : version: version,
1093 : schemaUrl: schemaUrl,
1094 : attributes: attributes);
1095 : }
1096 : }
|