diff --git a/CHANGELOG.md b/CHANGELOG.md index aea5ccd7..fc586d28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## [0.18.6](https://github.com/Workiva/opentelemetry-dart/tree/0.18.6) (2024-08-15) + +[Full Changelog](https://github.com/Workiva/opentelemetry-dart/compare/0.18.5...0.18.6) + +**Merged pull requests:** + +- Make registerGlobalContextManager public API [\#183](https://github.com/Workiva/opentelemetry-dart/pull/183) ([jonathancampbell-wk](https://github.com/jonathancampbell-wk)) +- O11Y-4831: Use test URL in test [\#181](https://github.com/Workiva/opentelemetry-dart/pull/181) ([kennytrytek-wf](https://github.com/kennytrytek-wf)) + ## [0.18.5](https://github.com/Workiva/opentelemetry-dart/tree/0.18.5) (2024-08-01) [Full Changelog](https://github.com/Workiva/opentelemetry-dart/compare/0.18.4...0.18.5) diff --git a/README.md b/README.md index 0368cfd4..3bb7cb6a 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ void main(List args) { In order to parent spans, context must be propagated. Propagation can be achieved by manually passing an instance of `Context` or by using Dart [`Zones`](https://dart.dev/libraries/async/zones). -See the [noop context manager example](./example/noop_context_manager.dart) and [zone context manager example](./example/zone_context_manager.dart) for more information. +See the [attach detach context example](./example/attach_detach_context)for more information. ### Inter-process diff --git a/example/attach_detach_context/README.md b/example/attach_detach_context/README.md new file mode 100644 index 00000000..98e363e2 --- /dev/null +++ b/example/attach_detach_context/README.md @@ -0,0 +1,13 @@ +# Attach Detach Context Example + +This example demonstrates context propagation using the attach/detach context APIs. + +The example produces two traces represented by the following diagram: + +```mermaid +flowchart LR + r1[Root 1 Span] --> c[Child Span] + c --> g1[Grandchild 1 Span] + c --> g2[Grandchild 2 Span] + r2[Root 2 Span] +``` diff --git a/example/attach_detach_context/attach_detach_context.dart b/example/attach_detach_context/attach_detach_context.dart new file mode 100644 index 00000000..35528ff3 --- /dev/null +++ b/example/attach_detach_context/attach_detach_context.dart @@ -0,0 +1,42 @@ +// Copyright 2021-2022 Workiva. +// Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information + +import 'package:opentelemetry/api.dart'; +import 'package:opentelemetry/sdk.dart' + show ConsoleExporter, SimpleSpanProcessor, TracerProviderBase; + +void main() { + final tp = TracerProviderBase( + processors: [SimpleSpanProcessor(ConsoleExporter())]), + tracer = tp.getTracer('instrumentation-name'); + + // Attach the root span to the current context (the root context) making the + // span the current span until it is detached. + final rootToken = Context.attach( + contextWithSpan(Context.current, tracer.startSpan('root-1')..end())); + + // Starting a child span will automatically parent the span to the span held + // by the attached context. + final child1 = tracer.startSpan('child')..end(); + final context = contextWithSpan(Context.current, child1); + + // Starting a span doesn't automatically attach the span. So to make the + // parent span actually parent a span, its context needs to be attached. + final childToken = Context.attach(context); + tracer.startSpan('grandchild-1').end(); + if (!Context.detach(childToken)) { + throw Exception('Failed to detach context'); + } + + // Alternatively, manually specifying the desired parent context avoids the + // need to attach and detach the context. + tracer.startSpan('grandchild-2', context: context).end(); + + if (!Context.detach(rootToken)) { + throw Exception('Failed to detach context'); + } + + // Since the previous root span context was detached, spans will no longer be + // automatically parented. + tracer.startSpan('root-2').end(); +} diff --git a/example/main.dart b/example/main.dart index fe19c053..38ee49e9 100644 --- a/example/main.dart +++ b/example/main.dart @@ -1,6 +1,8 @@ // Copyright 2021-2022 Workiva. // Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information +import 'dart:async'; + import 'package:opentelemetry/api.dart'; import 'package:opentelemetry/sdk.dart'; @@ -28,8 +30,8 @@ final tracer = provider.getTracer('instrumentation-name'); /// Demonstrates creating a trace with a parent and child span. void main() async { - // The current active span is available via the global context manager. - var context = globalContextManager.active; + // The current active context is available via a static getter. + var context = Context.current; // A trace starts with a root span which has no parent. final parentSpan = tracer.startSpan('parent-span'); @@ -37,11 +39,12 @@ void main() async { // A new context can be created in order to propagate context manually. context = contextWithSpan(context, parentSpan); - // The traceContext and traceContextSync functions will automatically + // The [traceContext] and [traceContextSync] functions will automatically // propagate context, capture errors, and end the span. - await traceContext( - 'child-span', (_context) => Future.delayed(Duration(milliseconds: 100)), - context: context); + await traceContext('child-span', (_) { + tracer.startSpan('grandchild-span').end(); + return Future.delayed(Duration(milliseconds: 100)); + }, context: context, tracer: tracer); // Spans must be ended or they will not be exported. parentSpan.end(); diff --git a/example/noop_context_manager.dart b/example/noop_context_manager.dart deleted file mode 100644 index f1d3fe16..00000000 --- a/example/noop_context_manager.dart +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2021-2022 Workiva. -// Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information - -import 'package:opentelemetry/api.dart'; -import 'package:opentelemetry/sdk.dart' - show ConsoleExporter, SimpleSpanProcessor, TracerProviderBase; -import 'package:opentelemetry/src/experimental_api.dart' show NoopContextManager; - -void main(List args) async { - final tp = - TracerProviderBase(processors: [SimpleSpanProcessor(ConsoleExporter())]); - registerGlobalTracerProvider(tp); - - final cm = NoopContextManager(); - registerGlobalContextManager(cm); - - final span = tp.getTracer('instrumentation-name').startSpan('test-span-0'); - await test(contextWithSpan(cm.active, span)); - span.end(); -} - -Future test(Context context) async { - spanFromContext(context).setStatus(StatusCode.error, 'test error'); - globalTracerProvider - .getTracer('instrumentation-name') - .startSpan('test-span-1', context: context) - .end(); -} diff --git a/example/stream_context/README.md b/example/stream_context/README.md new file mode 100644 index 00000000..92ccc2d8 --- /dev/null +++ b/example/stream_context/README.md @@ -0,0 +1,18 @@ +# Stream Context Propagation Example + +This example demonstrates context propagation over a `Stream` or `StreamController`. + +The example produces three traces represented by the following diagram: + +```mermaid +flowchart LR + subgraph a[Zone A] + direction LR + ap[Zone A Parent Span] --> ac[Zone A Child Span] + r[New Root Span] + ec[Event Child Span] + end + ep[Event Parent Span] --> ec +``` + +Note: When registering a stream listener, A stream listener callback is executed in the Zone where the callback was registered. This can lead to long running traces if the stream gets an event long after it was registered. To avoid this issue, the `trace` and `traceSync` functions will attach and detach the span they create before and after invoking the callback given to them. diff --git a/example/stream_context/stream_context.dart b/example/stream_context/stream_context.dart new file mode 100644 index 00000000..4c691192 --- /dev/null +++ b/example/stream_context/stream_context.dart @@ -0,0 +1,37 @@ +// Copyright 2021-2022 Workiva. +// Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information + +import 'dart:async'; + +import 'package:opentelemetry/api.dart'; +import 'package:opentelemetry/sdk.dart' + show ConsoleExporter, SimpleSpanProcessor, TracerProviderBase; + +mixin EventContext { + final Context context = Context.current; +} + +class MyEvent with EventContext { + MyEvent(); +} + +void main() async { + final tp = TracerProviderBase( + processors: [SimpleSpanProcessor(ConsoleExporter())]), + tracer = tp.getTracer('instrumentation-name'); + + final controller = StreamController(); + + traceSync('zone-a-parent', () { + tracer.startSpan('zone-a-child').end(); + + controller.stream.listen((e) { + tracer.startSpan('new-root').end(); + tracer.startSpan('event-child', context: e.context).end(); + }); + }, tracer: tracer); + + traceSync('event-parent', () => controller.add(MyEvent()), tracer: tracer); + + await controller.close(); +} diff --git a/example/w3c_context_propagation.dart b/example/w3c_context_propagation.dart index 31255870..00bbc712 100644 --- a/example/w3c_context_propagation.dart +++ b/example/w3c_context_propagation.dart @@ -4,7 +4,6 @@ import 'package:opentelemetry/api.dart'; import 'package:opentelemetry/sdk.dart' show ConsoleExporter, SimpleSpanProcessor, TracerProviderBase; -import 'package:opentelemetry/src/experimental_api.dart' show NoopContextManager; class MapSetter implements TextMapSetter { @override @@ -30,15 +29,12 @@ void main(List args) async { TracerProviderBase(processors: [SimpleSpanProcessor(ConsoleExporter())]); registerGlobalTracerProvider(tp); - final cm = NoopContextManager(); - registerGlobalContextManager(cm); - final tmp = W3CTraceContextPropagator(); registerGlobalTextMapPropagator(tmp); final span = tp.getTracer('instrumentation-name').startSpan('test-span-0'); final carrier = {}; - tmp.inject(contextWithSpan(cm.active, span), carrier, MapSetter()); + tmp.inject(contextWithSpan(Context.current, span), carrier, MapSetter()); await test(carrier); span.end(); } @@ -48,6 +44,6 @@ Future test(Map carrier) async { .getTracer('instrumentation-name') .startSpan('test-span-1', context: globalTextMapPropagator.extract( - globalContextManager.active, carrier, MapGetter())) + Context.current, carrier, MapGetter())) .end(); } diff --git a/example/zone_context_manager.dart b/example/zone_context_manager.dart deleted file mode 100644 index 2e72189f..00000000 --- a/example/zone_context_manager.dart +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2021-2022 Workiva. -// Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information - -import 'dart:async'; - -import 'package:opentelemetry/api.dart'; -import 'package:opentelemetry/sdk.dart' - show ConsoleExporter, SimpleSpanProcessor, TracerProviderBase; -import 'package:opentelemetry/src/experimental_api.dart' - show ZoneContext, ZoneContextManager; - -void main(List args) async { - final tp = - TracerProviderBase(processors: [SimpleSpanProcessor(ConsoleExporter())]); - registerGlobalTracerProvider(tp); - - final cm = ZoneContextManager(); - registerGlobalContextManager(cm); - - final span = tp.getTracer('instrumentation-name').startSpan('test-span-0'); - await (contextWithSpan(cm.active, span) as ZoneContext).run((_) => test()); - span.end(); -} - -Future test() async { - spanFromContext(globalContextManager.active) - .setStatus(StatusCode.error, 'test error'); - globalTracerProvider - .getTracer('instrumentation-name') - .startSpan('test-span-1') - .end(); -} diff --git a/lib/api.dart b/lib/api.dart index ff7cdc65..cdca4627 100644 --- a/lib/api.dart +++ b/lib/api.dart @@ -11,7 +11,8 @@ export 'src/api/context/context.dart' contextWithSpan, contextWithSpanContext, spanContextFromContext, - spanFromContext; + spanFromContext, + zone; export 'src/api/context/context_manager.dart' show globalContextManager, registerGlobalContextManager; export 'src/api/exporters/span_exporter.dart' show SpanExporter; diff --git a/lib/src/api/context/context.dart b/lib/src/api/context/context.dart index ae66c20f..ac3499f8 100644 --- a/lib/src/api/context/context.dart +++ b/lib/src/api/context/context.dart @@ -1,15 +1,44 @@ // Copyright 2021-2022 Workiva. // Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information +import 'dart:async'; + +import 'package:collection/collection.dart'; +import 'package:logging/logging.dart'; +import 'package:meta/meta.dart'; + import '../../../api.dart'; import '../trace/nonrecording_span.dart' show NonRecordingSpan; +final Logger _log = Logger('opentelemetry'); + +/// A key used to set and get values from a [Context]. +/// +/// Note: This class is not intended to be extended or implemented. The class +/// will be marked as sealed in 0.19.0. +// TODO: @sealed class ContextKey {} -final ContextKey spanKey = ContextKey(); +final ContextKey _contextStackKey = ContextKey(); +final ContextKey _spanKey = ContextKey(); + +@sealed +class ContextToken {} + +class ContextStackEntry { + final Context context; + final ContextToken token; + + ContextStackEntry(this.context) : token = ContextToken(); +} + +final _rootContext = Context._empty(); +final _stacks = >{ + Zone.root: [], +}; Context contextWithSpan(Context parent, Span span) { - return parent.setValue(spanKey, span); + return parent.setValue(_spanKey, span); } Context contextWithSpanContext(Context parent, SpanContext spanContext) { @@ -17,18 +46,73 @@ Context contextWithSpanContext(Context parent, SpanContext spanContext) { } Span spanFromContext(Context context) { - return context.getValue(spanKey) ?? NonRecordingSpan(SpanContext.invalid()); + return context.getValue(_spanKey) ?? NonRecordingSpan(SpanContext.invalid()); } SpanContext spanContextFromContext(Context context) { return spanFromContext(context).spanContext; } -abstract class Context { +/// Returns a new [Zone] such that the given context will be automatically +/// attached and detached for any function that runs within the zone. +@experimental +Zone zone([Context? context]) => Zone.current.fork( + specification: ZoneSpecification(run: (self, parent, zone, fn) { + // Only attach the context when delegating this zone's run, not any + // potential child. Otherwise, the child zone's current context would + // end up being the outermost zone attached context. + if (self == zone) { + final token = Context.attach(context ?? _currentContext(zone), zone); + final result = parent.run(zone, fn); + if (result is Future) { + result.whenComplete(() { + Context.detach(token, zone); + }); + } else { + Context.detach(token, zone); + } + return result; + } + return parent.run(zone, fn); + })); + +/// Returns the latest non-empty context stack, or the root stack if no context +/// stack is found. +List _currentContextStack(Zone zone) { + var stack = _stacks[zone]; + while (stack == null || stack.isEmpty) { + // walk up the zone tree to find the first non-null, non-empty context stack + final parent = zone.parent; + if (parent == null) { + return _stacks[Zone.root] ?? []; + } + zone = parent; + stack = _stacks[zone]; + } + return stack; +} + +Context _currentContext([Zone? zone]) => + _currentContextStack(zone ?? Zone.current).lastOrNull?.context ?? + _rootContext; + +class Context { + final Context? _parent; + final ContextKey _key; + final Object _value; + + Context._empty() + : _parent = null, + _key = ContextKey(), + _value = Object(); + + Context._(this._parent, this._key, this._value); + /// The active context. - @Deprecated( - 'This method will be removed in 0.19.0. Use [globalContextManager.active] instead.') - static Context get current => globalContextManager.active; + /// + /// The current context is the latest attached context, if one exists. + /// Otherwise, it is the root context. + static Context get current => _currentContext(); /// The root context which all other contexts are derived from. /// @@ -37,10 +121,7 @@ abstract class Context { /// Only use this context if you are certain you need to disregard the /// current [Context]. For example, when instrumenting an asynchronous /// event handler which may fire while an unrelated [Context] is "current". - @Deprecated( - 'This method will be removed in 0.19.0. Use [contextWithSpanContext(globalContextManager.active, SpanContext.invalid())] instead.') - static Context get root => contextWithSpanContext( - globalContextManager.active, SpanContext.invalid()); + static Context get root => _rootContext; /// Returns a key to be used to read and/or write values to a context. /// @@ -51,39 +132,123 @@ abstract class Context { 'This method will be removed in 0.19.0. Use [ContextKey] instead.') static ContextKey createKey(String name) => ContextKey(); - /// Returns the value from this context identified by [key], or null if no - /// such value is set. - T? getValue(ContextKey key); - - /// Returns a new context created from this one with the given key/value pair - /// set. + /// Attaches the given [Context] making it the active [Context] for the current + /// [Zone] and all child [Zone]s and returns a [ContextToken] that must be used + /// to detach the [Context]. + /// + /// When a [Context] is attached, it becomes active and overrides any [Context] + /// that may otherwise be visible within that [Zone]. The [Context] will not + /// be visible to any parent or sibling [Zone]. The [Context] will only be + /// visible for the current [Zone] and any child [Zone]. + @experimental + static ContextToken attach(Context context, [Zone? zone]) { + final entry = ContextStackEntry(context); + _stacks.update(zone ?? Zone.current, (stack) => stack..add(entry), + ifAbsent: () => [entry]); + return entry.token; + } + + /// Detaches the [Context] associated with the given [ContextToken] from their + /// associated [Zone]. + /// + /// Returns `true` if the given [ContextToken] is associated with the latest, + /// expected, attached [Context], `false` otherwise. /// - /// If [key] was already set in this context, it will be overridden. The rest - /// of the context values will be inherited. - Context setValue(ContextKey key, Object value); + /// If the [ContextToken] is not found in the latest stack, detach will walk up + /// the [Zone] tree attempting to find and detach the associated [Context]. + /// + /// Regardless of whether the [Context] is found, if the given [ContextToken] is + /// not expected, a warning will be logged. + @experimental + static bool detach(ContextToken token, [Zone? zone]) { + final stack = _currentContextStack(zone ?? Zone.current); + + final index = stack.indexWhere((c) => c.token == token); + + // the expected context to detach is the latest entry of the latest stack + final match = index != -1 && index == stack.length - 1; + if (!match) { + _log.warning('unexpected (mismatched) token given to detach'); + } + + if (index != -1) { + // context found in the latest stack, possibly the latest entry + stack.removeAt(index); + return match; + } + + // at this point, the token was not in the latest stack, but it might be in a + // stack held by a parent zone + + // walk up the zone tree checking for the token in each zone's context stack + zone ??= Zone.current; + do { + final stack = _stacks[zone!]; + final index = stack?.indexWhere((c) => c.token == token); + if (index != null && index != -1) { + // token found, remove it, but return false since it wasn't expected + stack!.removeAt(index); + return false; + } + zone = zone.parent; + } while (zone != null); + + // the token was nowhere to be found, a context was not detached + _log.warning('failed to detach context'); + return false; + } + + /// Returns the value identified by [key], or null if no such value exists. + T? getValue(ContextKey key) { + // check this context version or its previous versions + final value = _key == key ? _value : _parent?._getValue(key); + if (value != null) { + return value as T; + } + + // check other contexts within the current zone or a parent zone + Zone? zone = Zone.current; + while (zone != null) { + final stack = _stacks[zone]; + if (stack != null && stack.isNotEmpty) { + for (final entry in stack.reversed) { + final value = entry.context._getValue(key); + if (value != null) { + return value as T; + } + } + } + zone = zone.parent; + } + return null; + } + + T? _getValue(ContextKey key) => + _key == key ? _value as T : _parent?._getValue(key); + + /// Returns a new child context containing the given key/value. + Context setValue(ContextKey key, Object value) => Context._(this, key, value); /// Returns a new [Context] created from this one with the given [Span] /// set. @Deprecated( 'This method will be removed in 0.19.0. Use [contextWithSpan] instead.') - Context withSpan(Span span); + Context withSpan(Span span) => contextWithSpan(this, span); /// Execute a function [fn] within this [Context] and return its result. - @Deprecated( - 'This method will be removed in 0.19.0. Propagate [Context] as an ' - 'argument to [fn] and call [fn] directly or use ' - '[(context as ZoneContext).run((_) => fn(this))] instead.') - R execute(R Function() fn); + @Deprecated('This method will be removed in 0.19.0. Use [zone] instead.') + R execute(R Function() fn) => zone(this).run(fn); - /// Get the [Span] attached to this [Context], or an invalid, [Span] if no such - /// [Span] exists. + /// Get the [Span] attached to this [Context], or a [NonRecordingSpan] if no + /// such [Span] exists. @Deprecated( 'This method will be removed in 0.19.0. Use [spanFromContext] instead.') - Span get span; + Span get span => spanFromContext(this); - /// Get the [SpanContext] from this [Context], or an invalid [SpanContext] if no such - /// [SpanContext] exists. + /// Get the [SpanContext] from this [Context], or an invalid [SpanContext] if + /// no such [SpanContext] exists. @Deprecated( - 'This method will be removed in 0.19.0. Use [spanContextFromContext] instead.') - SpanContext get spanContext; + 'This method will be removed in 0.19.0. Use [spanContextFromContext] ' + 'instead.') + SpanContext get spanContext => spanContextFromContext(this); } diff --git a/lib/src/api/context/context_manager.dart b/lib/src/api/context/context_manager.dart index 91fc7801..51324db0 100644 --- a/lib/src/api/context/context_manager.dart +++ b/lib/src/api/context/context_manager.dart @@ -1,30 +1,37 @@ // Copyright 2021-2022 Workiva. // Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information -import '../../../api.dart'; -import '../../experimental_api.dart' show ZoneContextManager; +import 'context.dart' show Context; /// The [ContextManager] is responsible for managing the current [Context]. -/// Different implementations of [ContextManager] can be registered to use different underlying storage mechanisms. -abstract class ContextManager { +/// Different implementations of [ContextManager] can be registered to use +/// different underlying storage mechanisms. +@Deprecated('This class will be removed in 0.19.0 without replacement.') +class ContextManager { @Deprecated( - 'This method will be removed in 0.19.0. Use [globalContextManager.active] instead.') - Context get root; + 'This method will be removed in 0.19.0. Use the API global function ' + '[root] instead.') + Context get root => Context.root; - Context get active; + @Deprecated( + 'This method will be removed in 0.19.0. Use the API global function ' + '[active] instead.') + Context get active => Context.current; } -/// The default implementation is [ZoneContextManager], which uses Dart zones to store the current [Context]. -/// in the future, this will be replaced with noop context manager which maintains map context. -final ContextManager _noopContextManager = ZoneContextManager(); +final ContextManager _noopContextManager = ContextManager(); ContextManager _contextManager = _noopContextManager; +@Deprecated('This method will be removed in 0.19.0. Use the API global ' + 'function [active] instead.') ContextManager get globalContextManager => _contextManager; +@Deprecated('This method will be removed in 0.19.0 without replacement.') void registerGlobalContextManager(ContextManager contextManager) { if (_contextManager != _noopContextManager) { throw StateError( - 'Global context manager is already registered, registerContextManager must be called only once.'); + 'Global context manager is already registered, registerContextManager ' + 'must be called only once.'); } _contextManager = contextManager; diff --git a/lib/src/api/context/noop_context_manager.dart b/lib/src/api/context/noop_context_manager.dart index de04b450..9683f6f0 100644 --- a/lib/src/api/context/noop_context_manager.dart +++ b/lib/src/api/context/noop_context_manager.dart @@ -7,6 +7,7 @@ import 'map_context.dart' show createMapContext; final _root = createMapContext(); +@Deprecated('This class will be removed in 0.19.0 without replacement.') class NoopContextManager implements ContextManager { @override Context get root => _root; diff --git a/lib/src/api/context/zone_context_manager.dart b/lib/src/api/context/zone_context_manager.dart index 3cee5bce..b5e5d12a 100644 --- a/lib/src/api/context/zone_context_manager.dart +++ b/lib/src/api/context/zone_context_manager.dart @@ -3,10 +3,11 @@ import 'dart:async'; -import '../../../api.dart' show Context; -import '../../experimental_api.dart' show ContextManager; +import 'context.dart' show Context; +import 'context_manager.dart' show ContextManager; import 'zone_context.dart' show createZoneContext; +@Deprecated('This class will be removed in 0.19.0 without replacement.') class ZoneContextManager implements ContextManager { @override Context get root => createZoneContext(Zone.root); diff --git a/lib/src/api/open_telemetry.dart b/lib/src/api/open_telemetry.dart index dfbd608a..a99d75d1 100644 --- a/lib/src/api/open_telemetry.dart +++ b/lib/src/api/open_telemetry.dart @@ -8,7 +8,6 @@ import 'package:meta/meta.dart'; import '../../api.dart' as api; import 'propagation/noop_text_map_propagator.dart'; import 'trace/noop_tracer_provider.dart'; -import 'context/zone_context.dart'; final api.TracerProvider _noopTracerProvider = NoopTracerProvider(); final api.TextMapPropagator _noopTextMapPropagator = NoopTextMapPropagator(); @@ -41,52 +40,66 @@ void registerGlobalTextMapPropagator(api.TextMapPropagator textMapPropagator) { /// Records a span of the given [name] for the given function with a given /// [api.Tracer] and marks the span as errored if an exception occurs. -@Deprecated( - 'This method will be removed in 0.19.0. Use [traceContext] instead.') -Future trace(String name, Future Function() fn, - {api.Context? context, api.Tracer? tracer}) async { - context ??= api.globalContextManager.active; - tracer ??= _tracerProvider.getTracer('opentelemetry-dart'); +@Deprecated('Will be removed in v0.19.0. Use [trace] instead') +@experimental +Future traceContext(String name, Future Function(api.Context) fn, + {api.Context? context, + api.Tracer? tracer, + bool newRoot = false, + api.SpanKind spanKind = api.SpanKind.internal, + List spanLinks = const []}) async { + return trace(name, () => fn(api.Context.current), + context: context, + tracer: tracer, + newRoot: newRoot, + spanKind: spanKind, + spanLinks: spanLinks); +} - final span = tracer.startSpan(name, context: context); - try { - return await api.contextWithSpan(context, span).execute(fn); - } catch (e, s) { - span - ..setStatus(api.StatusCode.error, e.toString()) - ..recordException(e, stackTrace: s); - rethrow; - } finally { - span.end(); - } +/// Use [traceContextSync] instead of [traceContext] when [fn] is not an async +/// function. +@Deprecated('Will be removed in v0.19.0. Use [traceSync] instead') +@experimental +T traceContextSync(String name, T Function(api.Context) fn, + {api.Context? context, + api.Tracer? tracer, + bool newRoot = false, + api.SpanKind spanKind = api.SpanKind.internal, + List spanLinks = const []}) { + return traceSync(name, () => fn(api.Context.current), + context: context, + tracer: tracer, + newRoot: newRoot, + spanKind: spanKind, + spanLinks: spanLinks); } /// Records a span of the given [name] for the given function with a given /// [api.Tracer] and marks the span as errored if an exception occurs. @experimental -Future traceContext(String name, Future Function(api.Context) fn, +Future trace(String name, Future Function() fn, {api.Context? context, api.Tracer? tracer, bool newRoot = false, api.SpanKind spanKind = api.SpanKind.internal, List spanLinks = const []}) async { - context ??= api.globalContextManager.active; + context ??= api.Context.current; tracer ??= _tracerProvider.getTracer('opentelemetry-dart'); - // TODO: use start span option `newRoot` instead - if (newRoot) { - context = api.contextWithSpanContext(context, api.SpanContext.invalid()); - } - final span = tracer.startSpan(name, - context: context, kind: spanKind, links: spanLinks); - context = api.contextWithSpan(context, span); + // TODO: use start span option `newRoot` instead + context: newRoot ? api.Context.root : context, + kind: spanKind, + links: spanLinks); try { - // TODO: remove this check once `run` exists on context interface - if (context is ZoneContext) { - return await context.run((context) => fn(context)); - } - return await fn(context); + return await Zone.current.fork().run(() { + final token = api.Context.attach(api.contextWithSpan(context!, span)); + return fn().whenComplete(() { + if (!api.Context.detach(token)) { + span.addEvent('unexpected (mismatched) token given to detach'); + } + }); + }); } catch (e, s) { span ..setStatus(api.StatusCode.error, e.toString()) @@ -98,68 +111,37 @@ Future traceContext(String name, Future Function(api.Context) fn, } /// Use [traceSync] instead of [trace] when [fn] is not an async function. -@Deprecated( - 'This method will be removed in 0.19.0. Use [traceContextSync] instead.') -R traceSync(String name, R Function() fn, - {api.Context? context, api.Tracer? tracer}) { - context ??= api.globalContextManager.active; - tracer ??= _tracerProvider.getTracer('opentelemetry-dart'); - - final span = tracer.startSpan(name, context: context); - - try { - final r = api.contextWithSpan(context, span).execute(fn); - - if (r is Future) { - throw ArgumentError.value(fn, 'fn', - 'Use traceSync to trace functions that do not return a [Future].'); - } - - return r; - } catch (e, s) { - span - ..setStatus(api.StatusCode.error, e.toString()) - ..recordException(e, stackTrace: s); - rethrow; - } finally { - span.end(); - } -} - -/// Use [traceContextSync] instead of [traceContext] when [fn] is not an async function. @experimental -R traceContextSync(String name, R Function(api.Context) fn, +T traceSync(String name, T Function() fn, {api.Context? context, api.Tracer? tracer, bool newRoot = false, api.SpanKind spanKind = api.SpanKind.internal, List spanLinks = const []}) { - context ??= api.globalContextManager.active; + context ??= api.Context.current; tracer ??= _tracerProvider.getTracer('opentelemetry-dart'); - // TODO: use start span option `newRoot` instead - if (newRoot) { - context = api.contextWithSpanContext(context, api.SpanContext.invalid()); - } - final span = tracer.startSpan(name, - context: context, kind: spanKind, links: spanLinks); - context = api.contextWithSpan(context, span); + // TODO: use start span option `newRoot` instead + context: newRoot ? api.Context.root : context, + kind: spanKind, + links: spanLinks); try { - var r; - // TODO: remove this check once `run` exists on context interface - if (context is ZoneContext) { - r = context.run((context) => fn(context)); - } else { - r = fn(context); - } - - if (r is Future) { - throw ArgumentError.value(fn, 'fn', - 'Use traceContextSync to trace functions that do not return a [Future].'); - } - - return r; + return Zone.current.fork().run(() { + final token = api.Context.attach(api.contextWithSpan(context!, span)); + + final r = fn(); + + if (!api.Context.detach(token)) { + span.addEvent('unexpected (mismatched) token given to detach'); + } + + if (r is Future) { + throw ArgumentError.value(fn, 'fn', + 'Use traceSync to trace functions that do not return a [Future].'); + } + return r; + }); } catch (e, s) { span ..setStatus(api.StatusCode.error, e.toString()) diff --git a/lib/src/sdk/trace/tracer.dart b/lib/src/sdk/trace/tracer.dart index ab933269..5ed76c2f 100644 --- a/lib/src/sdk/trace/tracer.dart +++ b/lib/src/sdk/trace/tracer.dart @@ -37,7 +37,7 @@ class Tracer implements api.Tracer { List links = const [], Int64? startTime, bool newRoot = false}) { - context ??= api.globalContextManager.active; + context ??= api.Context.current; startTime ??= _timeProvider.now; // If a valid, active Span is present in the context, use it as this Span's diff --git a/test/api/context/context_test.dart b/test/api/context/context_test.dart new file mode 100644 index 00000000..8b7f42d4 --- /dev/null +++ b/test/api/context/context_test.dart @@ -0,0 +1,123 @@ +// Copyright 2021-2022 Workiva. +// Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information + +@TestOn('vm') + +import 'dart:async'; + +import 'package:opentelemetry/api.dart' as api; +import 'package:opentelemetry/sdk.dart' as sdk; +import 'package:opentelemetry/src/experimental_api.dart'; +import 'package:opentelemetry/src/sdk/trace/span.dart'; +import 'package:test/test.dart'; + +void main() { + final testSpanContext = api.SpanContext(api.TraceId([1, 2, 3]), + api.SpanId([7, 8, 9]), api.TraceFlags.none, api.TraceState.empty()); + final testSpan = Span( + 'foo', + testSpanContext, + api.SpanId([4, 5, 6]), + [], + sdk.DateTimeTimeProvider(), + sdk.Resource([]), + sdk.InstrumentationScope( + 'library_name', 'library_version', 'url://schema', []), + api.SpanKind.client, + [], + sdk.SpanLimits(), + sdk.DateTimeTimeProvider().now); + + group('contextWithSpan', () { + test('returns a new Context with the Span', () { + final context = api.contextWithSpan(api.Context.current, testSpan); + expect(api.Context.current, isNot(same(context))); + expect(api.spanFromContext(context), same(testSpan)); + }); + }); + + group('contextWithSpanContext', () { + test('returns a new Context with the SpanContext', () { + final context = + api.contextWithSpanContext(api.Context.current, testSpanContext); + expect(api.Context.current, isNot(same(context))); + expect(api.spanContextFromContext(context), same(testSpanContext)); + }); + }); + + group('spanFromContext', () { + test('returns Span when exists', () { + final context = api.contextWithSpan(api.Context.current, testSpan); + expect(api.spanFromContext(context), same(testSpan)); + }); + + test('returns an invalid Span when Span doesn\'t exist', () { + final context = api.Context.current; + expect(api.spanFromContext(context), isA()); + expect(api.spanContextFromContext(context).isValid, isFalse); + }); + }); + + group('spanContextFromContext', () { + test('returns SpanContext when Span exists', () { + final testContext = api.contextWithSpan(api.Context.current, testSpan); + expect(api.spanContextFromContext(testContext), same(testSpanContext)); + }); + + test('returns an invalid SpanContext when Span doesn\'t exist', () { + expect(api.spanContextFromContext(api.Context.current).isValid, isFalse); + }); + }); + + group('Context.current', () { + test('returns root context', () { + expect(api.Context.current, same(api.Context.root)); + }); + + test('returns attached context', () { + final context = api.Context.root.setValue(api.ContextKey(), 'foo'); + final token = api.Context.attach(context); + expect(api.Context.current, same(context)); + expect(api.Context.detach(token), isTrue); + expect(api.Context.current, same(api.Context.root)); + }); + + test('returns non-root stack attached context', () { + final context1 = api.Context.root.setValue(api.ContextKey(), 'foo'); + final token = api.Context.attach(context1); + Zone.current.run(() { + final context2 = api.Context.root.setValue(api.ContextKey(), 'bar'); + final token = api.Context.attach(context2); + expect(api.Context.current, same(context2)); + expect(api.Context.detach(token), isTrue); + expect(api.Context.current, same(context1)); + }); + expect(api.Context.detach(token), isTrue); + }); + }); + + group('Context.detach', () { + test('returns true on match', () { + final token = api.Context.attach(api.Context.current); + expect(api.Context.detach(token), isTrue); + }); + + test('returns false on mismatch', () { + final token1 = api.Context.attach(api.Context.current); + final token2 = api.Context.attach(api.Context.current); + expect(api.Context.detach(token1), isFalse); + expect(api.Context.detach(token2), isTrue); + }); + + test('returns true on match in nested zone', () { + final token1 = api.Context.attach(api.Context.current); + Zone.current.fork().run(() { + final token2 = api.Context.attach(api.Context.current); + Zone.current.fork().run(() { + expect(api.Context.detach(token2), isTrue); + expect(api.Context.detach(token1), isTrue); + }); + }); + }); + }); +} diff --git a/test/api/open_telemetry_test.dart b/test/api/open_telemetry_test.dart new file mode 100644 index 00000000..7653d4ef --- /dev/null +++ b/test/api/open_telemetry_test.dart @@ -0,0 +1,121 @@ +// Copyright 2021-2022 Workiva. +// Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information + +@TestOn('vm') + +import 'package:opentelemetry/api.dart'; +import 'package:opentelemetry/sdk.dart' as sdk; +import 'package:opentelemetry/src/sdk/trace/span.dart' as sdk show Span; +import 'package:opentelemetry/src/sdk/trace/tracer.dart' as sdk show Tracer; +import 'package:test/test.dart'; + +void main() { + final tracer = sdk.Tracer([], + sdk.Resource([]), + sdk.AlwaysOnSampler(), + sdk.DateTimeTimeProvider(), + sdk.IdGenerator(), + sdk.InstrumentationScope('name', 'version', 'url://schema', []), + sdk.SpanLimits()); + + test('trace starts and ends span', () async { + final span = await trace('span', () async { + return spanFromContext(Context.current); + }, tracer: tracer) as sdk.Span; + + expect(span.startTime, isNotNull); + expect(span.endTime, isNotNull); + }); + + test('traceSync starts and ends span', () { + final span = traceSync('span', () { + return spanFromContext(Context.current); + }, tracer: tracer) as sdk.Span; + + expect(span.startTime, isNotNull); + expect(span.endTime, isNotNull); + }); + + test('trace propagates context', () { + final parent = tracer.startSpan('parent')..end(); + + trace('child', () async { + final child = spanFromContext(Context.current); + + expect(child.parentSpanId.toString(), + equals(parent.spanContext.spanId.toString())); + }, tracer: tracer, context: contextWithSpan(Context.current, parent)); + }); + + test('traceSync propagates context', () { + final parent = tracer.startSpan('parent')..end(); + + traceSync('span', () { + final child = spanFromContext(Context.current); + + expect(child.parentSpanId.toString(), + equals(parent.spanContext.spanId.toString())); + }, tracer: tracer, context: contextWithSpan(Context.current, parent)); + }); + + test('trace catches, records, and rethrows exception', () async { + late sdk.Span span; + var caught = false; + try { + await trace('span', () async { + span = spanFromContext(Context.current) as sdk.Span; + throw Exception('Bang!'); + }, tracer: tracer); + } catch (e) { + caught = true; + } finally { + expect(caught, isTrue); + expect(span.endTime, isNotNull); + expect(span.status.code, equals(StatusCode.error)); + expect(span.status.description, equals('Exception: Bang!')); + expect(span.events, [ + hasExceptionEvent({ + SemanticAttributes.exceptionType: '_Exception', + SemanticAttributes.exceptionMessage: 'Exception: Bang!', + SemanticAttributes.exceptionStacktrace: anything, + SemanticAttributes.exceptionEscaped: true, + }) + ]); + } + }); + + test('traceSync catches, records, and rethrows exception', () async { + late sdk.Span span; + var caught = false; + try { + traceSync('span', () { + span = spanFromContext(Context.current) as sdk.Span; + throw Exception('Bang!'); + }, tracer: tracer); + } catch (e) { + caught = true; + } finally { + expect(caught, isTrue); + expect(span.endTime, isNotNull); + expect(span.status.code, equals(StatusCode.error)); + expect(span.status.description, equals('Exception: Bang!')); + expect(span.events, [ + hasExceptionEvent({ + SemanticAttributes.exceptionType: '_Exception', + SemanticAttributes.exceptionMessage: 'Exception: Bang!', + SemanticAttributes.exceptionStacktrace: anything, + SemanticAttributes.exceptionEscaped: true, + }) + ]); + } + }); +} + +Matcher hasExceptionEvent(Map attributes) => + isA().having( + (e) => e.attributes, + 'attributes', + isA>().having( + (a) => a.map((e) => [e.key, e.value]), + 'attributes', + containsAll(attributes.entries.map((e) => [e.key, e.value])))); diff --git a/test/integration/api/propagation/w3c_trace_context_propagator_test.dart b/test/integration/api/propagation/w3c_trace_context_propagator_test.dart index 89bec97e..93fa3fb6 100644 --- a/test/integration/api/propagation/w3c_trace_context_propagator_test.dart +++ b/test/integration/api/propagation/w3c_trace_context_propagator_test.dart @@ -48,8 +48,7 @@ void main() { sdk.DateTimeTimeProvider().now); final testPropagator = api.W3CTraceContextPropagator(); final testCarrier = {}; - final testContext = - api.contextWithSpan(api.globalContextManager.active, testSpan); + final testContext = api.contextWithSpan(api.Context.current, testSpan); testPropagator.inject(testContext, testCarrier, TestingInjector()); final resultSpan = api.spanFromContext( @@ -87,8 +86,7 @@ void main() { sdk.DateTimeTimeProvider().now); final testPropagator = api.W3CTraceContextPropagator(); final testCarrier = {}; - final testContext = - api.contextWithSpan(api.globalContextManager.active, testSpan); + final testContext = api.contextWithSpan(api.Context.current, testSpan); testPropagator.inject(testContext, testCarrier, TestingInjector()); final resultSpan = api.spanFromContext( @@ -131,8 +129,7 @@ void main() { // Inject and extract a test Span from a Context, as when an outbound // call is made and received by another service. - final testContext = - api.contextWithSpan(api.globalContextManager.active, testSpan); + final testContext = api.contextWithSpan(api.Context.current, testSpan); testPropagator.inject(testContext, testCarrier, TestingInjector()); final parentSpan = api.spanFromContext( testPropagator.extract(testContext, testCarrier, TestingExtractor())); @@ -141,7 +138,7 @@ void main() { // Use the transmitted Span as a receiver. final resultSpan = tracer.startSpan('doWork', - context: api.contextWithSpan(api.globalContextManager.active, testSpan)) + context: api.contextWithSpan(api.Context.current, testSpan)) ..end(); // Verify that data from the original Span propagates to the child. diff --git a/test/integration/open_telemetry_test.dart b/test/integration/open_telemetry_test.dart deleted file mode 100644 index 3eb5f322..00000000 --- a/test/integration/open_telemetry_test.dart +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright 2021-2022 Workiva. -// Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information - -@TestOn('vm') -import 'dart:async'; - -import 'package:opentelemetry/api.dart' as api; -import 'package:opentelemetry/sdk.dart' as sdk; -import 'package:opentelemetry/src/api/trace/span_event.dart'; -import 'package:opentelemetry/src/sdk/trace/span.dart'; -import 'package:opentelemetry/src/sdk/trace/tracer.dart'; -import 'package:test/test.dart'; - -void main() { - test('trace synchronous execution', () { - final tracer = Tracer([], - sdk.Resource([]), - sdk.AlwaysOnSampler(), - sdk.DateTimeTimeProvider(), - sdk.IdGenerator(), - sdk.InstrumentationScope('name', 'version', 'url://schema', []), - sdk.SpanLimits()); - late Span span; - - api.traceContextSync('syncTrace', (context) { - span = api.spanFromContext(context) as Span; - }, tracer: tracer); - - expect(span, isNotNull); - expect( - span.endTime, - lessThan(DateTime.now().microsecondsSinceEpoch * - sdk.TimeProvider.nanosecondsPerMicrosecond)); - }); - - test('trace synchronous looped execution timing', () { - final tracer = Tracer([], - sdk.Resource([]), - sdk.AlwaysOnSampler(), - sdk.DateTimeTimeProvider(), - sdk.IdGenerator(), - sdk.InstrumentationScope('name', 'version', 'url://schema', []), - sdk.SpanLimits()); - final spans = []; - - for (var i = 0; i < 5; i++) { - api.traceContextSync('syncTrace', (context) { - spans.add(api.spanFromContext(context) as Span); - }, tracer: tracer); - } - - for (var i = 1; i < spans.length; i++) { - expect(spans[i].endTime, isNotNull); - expect(spans[i].startTime, greaterThan(spans[i - 1].startTime)); - expect(spans[i].endTime, greaterThan(spans[i - 1].endTime!)); - } - }); - - test('trace synchronous execution with error', () { - final tracer = Tracer([], - sdk.Resource([]), - sdk.AlwaysOnSampler(), - sdk.DateTimeTimeProvider(), - sdk.IdGenerator(), - sdk.InstrumentationScope('name', 'version', 'url://schema', []), - sdk.SpanLimits()); - late Span span; - - expect( - () => api.traceContextSync('syncTrace', (context) { - span = api.spanFromContext(context) as Span; - throw Exception('Oh noes!'); - }, tracer: tracer), - throwsException); - expect(span.endTime, isNotNull); - expect(span.status.code, equals(api.StatusCode.error)); - expect(span.status.description, equals('Exception: Oh noes!')); - expect(span.events, [ - hasExceptionEvent({ - api.SemanticAttributes.exceptionType: '_Exception', - api.SemanticAttributes.exceptionMessage: 'Exception: Oh noes!', - api.SemanticAttributes.exceptionStacktrace: anything, - api.SemanticAttributes.exceptionEscaped: true, - }) - ]); - }); - - test('trace asynchronous execution', () async { - final tracer = Tracer([], - sdk.Resource([]), - sdk.AlwaysOnSampler(), - sdk.DateTimeTimeProvider(), - sdk.IdGenerator(), - sdk.InstrumentationScope('name', 'version', 'url://schema', []), - sdk.SpanLimits()); - late Span span; - - await api.traceContext('asyncTrace', (context) async { - span = api.spanFromContext(context) as Span; - }, tracer: tracer); - - expect( - span.endTime, - lessThan(DateTime.now().microsecondsSinceEpoch * - sdk.TimeProvider.nanosecondsPerMicrosecond)); - }); - - test('trace asynchronous looped execution timing', () async { - final tracer = Tracer([], - sdk.Resource([]), - sdk.AlwaysOnSampler(), - sdk.DateTimeTimeProvider(), - sdk.IdGenerator(), - sdk.InstrumentationScope('name', 'version', 'url://schema', []), - sdk.SpanLimits()); - final spans = []; - - for (var i = 0; i < 5; i++) { - await api.traceContext('asyncTrace', (context) async { - spans.add(api.spanFromContext(context) as Span); - }, tracer: tracer); - } - - for (var i = 1; i < spans.length; i++) { - expect(spans[1].endTime, isNotNull); - expect(spans[i].startTime, greaterThan(spans[i - 1].startTime)); - expect(spans[i].endTime, greaterThan(spans[i - 1].endTime!)); - } - }); - - test('trace asynchronous execution with thrown error', () async { - final tracer = Tracer([], - sdk.Resource([]), - sdk.AlwaysOnSampler(), - sdk.DateTimeTimeProvider(), - sdk.IdGenerator(), - sdk.InstrumentationScope('name', 'version', 'url://schema', []), - sdk.SpanLimits()); - late Span span; - - try { - await api.traceContext('asyncTrace', (context) async { - span = api.spanFromContext(context) as Span; - throw Exception('Oh noes!'); - }, tracer: tracer); - } catch (e) { - expect(e.toString(), equals('Exception: Oh noes!')); - } - expect(span, isNotNull); - expect(span.endTime, isNotNull); - expect(span.status.code, equals(api.StatusCode.error)); - expect(span.status.description, equals('Exception: Oh noes!')); - expect(span.events, [ - hasExceptionEvent({ - api.SemanticAttributes.exceptionType: '_Exception', - api.SemanticAttributes.exceptionMessage: 'Exception: Oh noes!', - api.SemanticAttributes.exceptionStacktrace: anything, - api.SemanticAttributes.exceptionEscaped: true, - }) - ]); - }); - - test('trace asynchronous execution completes with error', () async { - final tracer = Tracer([], - sdk.Resource([]), - sdk.AlwaysOnSampler(), - sdk.DateTimeTimeProvider(), - sdk.IdGenerator(), - sdk.InstrumentationScope('name', 'version', 'url://schema', []), - sdk.SpanLimits()); - late Span span; - - try { - await api.traceContext('asyncTrace', (context) async { - span = api.spanFromContext(context) as Span; - return Future.error(Exception('Oh noes!')); - }, tracer: tracer); - } catch (e) { - expect(e.toString(), equals('Exception: Oh noes!')); - } - expect(span, isNotNull); - expect(span.endTime, isNotNull); - expect(span.status.code, equals(api.StatusCode.error)); - expect(span.status.description, equals('Exception: Oh noes!')); - expect(span.events, [ - hasExceptionEvent({ - api.SemanticAttributes.exceptionType: '_Exception', - api.SemanticAttributes.exceptionMessage: 'Exception: Oh noes!', - api.SemanticAttributes.exceptionStacktrace: anything, - api.SemanticAttributes.exceptionEscaped: true, - }) - ]); - }); -} - -Matcher hasExceptionEvent(Map attributes) => - isA().having( - (e) => e.attributes, - 'attributes', - isA>().having( - (a) => a.map((e) => [e.key, e.value]), - 'attributes', - containsAll(attributes.entries.map((e) => [e.key, e.value])))); diff --git a/test/integration/sdk/tracer_test.dart b/test/integration/sdk/tracer_test.dart index 672198be..a5a45b74 100644 --- a/test/integration/sdk/tracer_test.dart +++ b/test/integration/sdk/tracer_test.dart @@ -40,8 +40,7 @@ void main() { sdk.SpanLimits()); final parentSpan = tracer.startSpan('foo'); - final context = - api.contextWithSpan(api.globalContextManager.active, parentSpan); + final context = api.contextWithSpan(api.Context.current, parentSpan); final childSpan = tracer.startSpan('bar', context: context) as Span; diff --git a/test/unit/api/context/context_test.dart b/test/unit/api/context/context_test.dart deleted file mode 100644 index c68047ed..00000000 --- a/test/unit/api/context/context_test.dart +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2021-2022 Workiva. -// Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information - -@TestOn('vm') -import 'package:opentelemetry/api.dart' as api; -import 'package:opentelemetry/sdk.dart' as sdk; -import 'package:opentelemetry/src/experimental_api.dart'; -import 'package:opentelemetry/src/sdk/trace/span.dart'; -import 'package:test/test.dart'; - -void main() { - final testSpanContext = api.SpanContext(api.TraceId([1, 2, 3]), - api.SpanId([7, 8, 9]), api.TraceFlags.none, api.TraceState.empty()); - final testSpan = Span( - 'foo', - testSpanContext, - api.SpanId([4, 5, 6]), - [], - sdk.DateTimeTimeProvider(), - sdk.Resource([]), - sdk.InstrumentationScope( - 'library_name', 'library_version', 'url://schema', []), - api.SpanKind.client, - [], - sdk.SpanLimits(), - sdk.DateTimeTimeProvider().now); - - group('spanFromContext', () { - test('returns Span when exists', () { - final context = - api.contextWithSpan(api.globalContextManager.active, testSpan); - - expect(api.spanFromContext(context), same(testSpan)); - }); - - test('returns an invalid Span when a Span does not exist in the Context', - () { - final context = api.globalContextManager.active; - - expect(api.spanFromContext(context), isA()); - expect(api.spanContextFromContext(context).isValid, isFalse); - }); - }); - - group('spanContextFromContext', () { - test('returns SpanContext when Span exists', () { - final testContext = - api.contextWithSpan(api.globalContextManager.active, testSpan); - - expect(api.spanContextFromContext(testContext), same(testSpanContext)); - }); - - test( - 'returns an invalid SpanContext when a Span does not exist in the Context', - () { - expect( - api.spanContextFromContext(api.globalContextManager.active).isValid, - isFalse); - }); - }); -} diff --git a/test/unit/api/context/zone_context_test.dart b/test/unit/api/context/zone_context_test.dart index 72bbab59..c4751aaf 100644 --- a/test/unit/api/context/zone_context_test.dart +++ b/test/unit/api/context/zone_context_test.dart @@ -2,14 +2,17 @@ // Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information @TestOn('vm') +import 'dart:async'; + import 'package:opentelemetry/api.dart'; +import 'package:opentelemetry/src/api/context/zone_context.dart'; import 'package:test/test.dart'; void main() { test('setValue and getValue', () { final testKey = ContextKey(); - final parentContext = globalContextManager.active; + final parentContext = createZoneContext(Zone.current); final childContext = parentContext.setValue(testKey, 'bar'); expect(parentContext.getValue(testKey), isNull); expect(childContext.getValue(testKey), equals('bar')); diff --git a/test/unit/api/propagation/w3c_trace_context_propagator_test.dart b/test/unit/api/propagation/w3c_trace_context_propagator_test.dart index fbc84d6d..54008e12 100644 --- a/test/unit/api/propagation/w3c_trace_context_propagator_test.dart +++ b/test/unit/api/propagation/w3c_trace_context_propagator_test.dart @@ -29,8 +29,6 @@ class TestingExtractor implements api.TextMapGetter> { } void main() { - final cm = NoopContextManager(); - test('extract trace context', () { final testPropagator = api.W3CTraceContextPropagator(); final testCarrier = {}; @@ -40,8 +38,8 @@ void main() { '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01') ..set( testCarrier, 'tracestate', 'rojo=00f067aa0ba902b7,congo=t61rcWkgMzE'); - final resultContext = - testPropagator.extract(cm.active, testCarrier, TestingExtractor()); + final resultContext = testPropagator.extract( + api.Context.current, testCarrier, TestingExtractor()); final resultSpan = api.spanFromContext(resultContext); expect(resultSpan.parentSpanId.toString(), equals('0000000000000000')); @@ -65,8 +63,8 @@ void main() { '00-00000000000000000000000000000000-0000000000000000-ff') ..set( testCarrier, 'tracestate', 'rojo=00f067aa0ba902b7,congo=t61rcWkgMzE'); - final resultContext = - testPropagator.extract(cm.active, testCarrier, TestingExtractor()); + final resultContext = testPropagator.extract( + api.Context.current, testCarrier, TestingExtractor()); final resultSpan = api.spanFromContext(resultContext); expect(resultSpan.parentSpanId.toString(), equals('0000000000000000')); @@ -85,8 +83,8 @@ void main() { final testPropagator = api.W3CTraceContextPropagator(); final testCarrier = {}; - final resultContext = - testPropagator.extract(cm.active, testCarrier, TestingExtractor()); + final resultContext = testPropagator.extract( + api.Context.current, testCarrier, TestingExtractor()); final resultSpan = api.spanFromContext(resultContext); expect(resultSpan, isA()); @@ -102,8 +100,8 @@ void main() { '00-4bf92^3577b34da6q3ce929d0e0e4736-00f@67aa0bak02b7-01') ..set( testCarrier, 'tracestate', 'rojo=00f067aa0ba902b7,congo=t61rcWkgMzE'); - final resultContext = - testPropagator.extract(cm.active, testCarrier, TestingExtractor()); + final resultContext = testPropagator.extract( + api.Context.current, testCarrier, TestingExtractor()); final resultSpan = api.spanFromContext(resultContext); // Extract should not allow a Span with malformed IDs to be attached to @@ -121,8 +119,8 @@ void main() { '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01') ..set(testCarrier, 'tracestate', 'rojo=00f067aa,0ba902b7,con@go=t61rcWk=gMzE'); - final resultSpan = api.spanFromContext( - testPropagator.extract(cm.active, testCarrier, TestingExtractor())); + final resultSpan = api.spanFromContext(testPropagator.extract( + api.Context.current, testCarrier, TestingExtractor())); expect(resultSpan.parentSpanId.toString(), equals('0000000000000000')); expect(resultSpan.spanContext.isValid, isTrue); @@ -156,7 +154,7 @@ void main() { sdk.SpanLimits(), sdk.DateTimeTimeProvider().now); final testCarrier = {}; - final testContext = api.contextWithSpan(cm.active, testSpan); + final testContext = api.contextWithSpan(api.Context.current, testSpan); api.W3CTraceContextPropagator() .inject(testContext, testCarrier, TestingInjector()); @@ -187,7 +185,7 @@ void main() { sdk.SpanLimits(), sdk.DateTimeTimeProvider().now); final testCarrier = {}; - final testContext = api.contextWithSpan(cm.active, testSpan); + final testContext = api.contextWithSpan(api.Context.current, testSpan); api.W3CTraceContextPropagator() .inject(testContext, testCarrier, TestingInjector()); diff --git a/test/unit/sdk/sampling/always_off_sampler_test.dart b/test/unit/sdk/sampling/always_off_sampler_test.dart index 23bbb40e..5dee17c2 100644 --- a/test/unit/sdk/sampling/always_off_sampler_test.dart +++ b/test/unit/sdk/sampling/always_off_sampler_test.dart @@ -4,13 +4,10 @@ @TestOn('vm') import 'package:opentelemetry/api.dart' as api; import 'package:opentelemetry/sdk.dart' as sdk; -import 'package:opentelemetry/src/experimental_api.dart'; import 'package:opentelemetry/src/sdk/trace/span.dart'; import 'package:test/test.dart'; void main() { - final cm = NoopContextManager(); - test('Context contains a Span', () { final traceId = api.TraceId([1, 2, 3]); final traceState = api.TraceState.fromString('test=one,two'); @@ -28,7 +25,7 @@ void main() { [], sdk.SpanLimits(), sdk.DateTimeTimeProvider().now); - final testContext = api.contextWithSpan(cm.active, testSpan); + final testContext = api.contextWithSpan(api.Context.current, testSpan); final result = sdk.AlwaysOffSampler().shouldSample( testContext, traceId, testSpan.name, api.SpanKind.internal, [], []); @@ -59,8 +56,8 @@ void main() { sdk.DateTimeTimeProvider().now) ..setAttributes(attributesList); - final result = sdk.AlwaysOffSampler().shouldSample(cm.active, traceId, - testSpan.name, api.SpanKind.internal, attributesList, []); + final result = sdk.AlwaysOffSampler().shouldSample(api.Context.current, + traceId, testSpan.name, api.SpanKind.internal, attributesList, []); expect(result.decision, equals(sdk.Decision.drop)); expect(result.spanAttributes, attributesList); diff --git a/test/unit/sdk/sampling/always_on_sampler_test.dart b/test/unit/sdk/sampling/always_on_sampler_test.dart index 63054b9c..3a242dae 100644 --- a/test/unit/sdk/sampling/always_on_sampler_test.dart +++ b/test/unit/sdk/sampling/always_on_sampler_test.dart @@ -4,13 +4,10 @@ @TestOn('vm') import 'package:opentelemetry/api.dart' as api; import 'package:opentelemetry/sdk.dart' as sdk; -import 'package:opentelemetry/src/experimental_api.dart'; import 'package:opentelemetry/src/sdk/trace/span.dart'; import 'package:test/test.dart'; void main() { - final cm = NoopContextManager(); - test('Context contains a Span', () { final traceId = api.TraceId([1, 2, 3]); final traceState = api.TraceState.fromString('test=one,two'); @@ -28,7 +25,7 @@ void main() { [], sdk.SpanLimits(), sdk.DateTimeTimeProvider().now); - final testContext = api.contextWithSpan(cm.active, testSpan); + final testContext = api.contextWithSpan(api.Context.current, testSpan); final result = sdk.AlwaysOnSampler().shouldSample( testContext, traceId, testSpan.name, api.SpanKind.internal, [], []); @@ -54,8 +51,8 @@ void main() { sdk.SpanLimits(), sdk.DateTimeTimeProvider().now); - final result = sdk.AlwaysOnSampler().shouldSample( - cm.active, traceId, testSpan.name, api.SpanKind.internal, [], []); + final result = sdk.AlwaysOnSampler().shouldSample(api.Context.current, + traceId, testSpan.name, api.SpanKind.internal, [], []); expect(result.decision, equals(sdk.Decision.recordAndSample)); expect(result.spanAttributes, equals([])); diff --git a/test/unit/sdk/sampling/parent_based_sampler_test.dart b/test/unit/sdk/sampling/parent_based_sampler_test.dart index 04ff10ba..f50a4463 100644 --- a/test/unit/sdk/sampling/parent_based_sampler_test.dart +++ b/test/unit/sdk/sampling/parent_based_sampler_test.dart @@ -4,12 +4,10 @@ @TestOn('vm') import 'package:opentelemetry/api.dart' as api; import 'package:opentelemetry/sdk.dart' as sdk; -import 'package:opentelemetry/src/experimental_api.dart'; import 'package:opentelemetry/src/sdk/trace/span.dart'; import 'package:test/test.dart'; void main() { - final cm = NoopContextManager(); final onSampler = sdk.AlwaysOnSampler(); final offSampler = sdk.AlwaysOffSampler(); final testSampler = sdk.ParentBasedSampler(onSampler, @@ -34,7 +32,7 @@ void main() { sdk.SpanLimits(), sdk.DateTimeTimeProvider().now); - final testContext = api.contextWithSpan(cm.active, testSpan); + final testContext = api.contextWithSpan(api.Context.current, testSpan); final result = testSampler.shouldSample( testContext, traceId, testSpan.name, api.SpanKind.internal, [], []); @@ -59,8 +57,8 @@ void main() { sdk.SpanLimits(), sdk.DateTimeTimeProvider().now); - final result = testSampler.shouldSample( - cm.active, traceId, testSpan.name, api.SpanKind.internal, [], []); + final result = testSampler.shouldSample(api.Context.current, traceId, + testSpan.name, api.SpanKind.internal, [], []); expect(result.decision, equals(sdk.Decision.recordAndSample)); expect(result.spanAttributes, equals([])); @@ -84,7 +82,7 @@ void main() { [], sdk.SpanLimits(), sdk.DateTimeTimeProvider().now); - final testContext = api.contextWithSpan(cm.active, testSpan); + final testContext = api.contextWithSpan(api.Context.current, testSpan); final result = testSampler.shouldSample( testContext, traceId, testSpan.name, api.SpanKind.internal, [], []); @@ -111,7 +109,7 @@ void main() { [], sdk.SpanLimits(), sdk.DateTimeTimeProvider().now); - final testContext = api.contextWithSpan(cm.active, testSpan); + final testContext = api.contextWithSpan(api.Context.current, testSpan); final result = testSampler.shouldSample( testContext, traceId, testSpan.name, api.SpanKind.internal, [], []); @@ -138,7 +136,7 @@ void main() { [], sdk.SpanLimits(), sdk.DateTimeTimeProvider().now); - final testContext = api.contextWithSpan(cm.active, testSpan); + final testContext = api.contextWithSpan(api.Context.current, testSpan); final result = testSampler.shouldSample( testContext, traceId, testSpan.name, api.SpanKind.internal, [], []); @@ -165,7 +163,7 @@ void main() { [], sdk.SpanLimits(), sdk.DateTimeTimeProvider().now); - final testContext = api.contextWithSpan(cm.active, testSpan); + final testContext = api.contextWithSpan(api.Context.current, testSpan); final result = testSampler.shouldSample( testContext, traceId, testSpan.name, api.SpanKind.internal, [], []); diff --git a/test/unit/sdk/trace/tracer_test.dart b/test/unit/sdk/trace/tracer_test.dart index c7a718a2..4a94bf73 100644 --- a/test/unit/sdk/trace/tracer_test.dart +++ b/test/unit/sdk/trace/tracer_test.dart @@ -14,8 +14,7 @@ void main() { test('with newRoot true', () { final parent = tracer.startSpan('parent'); - final context = - api.contextWithSpan(api.globalContextManager.active, parent); + final context = api.contextWithSpan(api.Context.current, parent); final span = (tracer as Tracer).startSpan('', newRoot: true, context: context); expect(span.parentSpanId.isValid, isFalse); @@ -26,8 +25,7 @@ void main() { test('with newRoot false', () { final parent = tracer.startSpan('parent'); - final context = - api.contextWithSpan(api.globalContextManager.active, parent); + final context = api.contextWithSpan(api.Context.current, parent); final span = (tracer as Tracer).startSpan('', newRoot: false, context: context); expect(span.parentSpanId.isValid, isTrue); diff --git a/test/unit/sdk/trace_test.dart b/test/unit/sdk/trace_test.dart deleted file mode 100644 index 1372a871..00000000 --- a/test/unit/sdk/trace_test.dart +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2021-2022 Workiva. -// Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information - -@TestOn('vm') -import 'dart:async'; -import 'package:opentelemetry/src/api/open_telemetry.dart'; -import 'package:test/test.dart'; - -void main() { - test('traceContextSync returns value', () { - final value = traceContextSync('foo', (_) => 'bar'); - expect(value, 'bar'); - }); - - test('traceContextSync throws exception', () { - final bang = Exception('bang!'); - expect(() => traceContextSync('foo', (_) => throw bang), throwsA(bang)); - }); - - test('traceContextSync wants you to use trace instead', () { - expect(() => traceContextSync('foo', (_) async => ''), throwsArgumentError); - expect(() => traceContextSync('foo', Future.value), throwsArgumentError); - }); - - test('trace returns future value', () async { - await expectLater( - traceContext('foo', (_) async => 'bar'), completion('bar')); - await expectLater( - traceContext('foo', (_) => Future.value('baz')), completion('baz')); - }); - - test('trace throws future error', () async { - // Exception thrown from synchronous code in async function. - final bang = Exception('bang!'); - await expectLater( - traceContext('foo', (_) async => throw bang), throwsA(bang)); - - // Exception thrown from asynchronous code in async function. - final buzz = Exception('buzz!!'); - await expectLater( - traceContext('foo', (_) async => Future.error(buzz)), throwsA(buzz)); - - // Exception thrown from asynchronous code in async function. - final baz = Exception('baz!!'); - await expectLater( - traceContext('foo', (_) => Future.error(baz)), throwsA(baz)); - }); -}