Skip to content

Commit 8abfdad

Browse files
Merge pull request #167 from Workiva/O11Y-4010
O11Y-4010: Add ContextManager for Context
2 parents da51a1b + eb4d7ee commit 8abfdad

17 files changed

+304
-71
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
.dart_tool
22
.packages
33
pubspec.lock
4+
.tool-versions

lib/api.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
export 'src/api/common/attribute.dart' show Attribute;
55
export 'src/api/common/resource_attributes.dart' show ResourceAttributes;
66
export 'src/api/common/semantic_attributes.dart' show SemanticAttributes;
7-
export 'src/api/context/context.dart' show Context;
7+
export 'src/api/context/context.dart' show Context, ContextKey;
88
export 'src/api/exporters/span_exporter.dart' show SpanExporter;
99
export 'src/api/instrumentation_library.dart' show InstrumentationLibrary;
1010
export 'src/api/open_telemetry.dart'

lib/src/api/context/context.dart

Lines changed: 22 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,14 @@
11
// Copyright 2021-2022 Workiva.
22
// Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information
3-
4-
/// The OpenTelemetry SDKs require a mechanism for propagating context and the
5-
/// OpenTelemetry specification outlines the requirements for this context
6-
/// implementation: https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/context/context.md
7-
///
8-
/// The spec notes that "languages are expected to use the single, widely used
9-
/// Context implementation if one exists for them." Fortunately, the Dart SDK
10-
/// provides just that with [Zone] - a representation of "an environment that
11-
/// remains stable across asynchronous calls." [Zone] also meets the core
12-
/// requirements of immutability and being able to read and write values:
13-
///
14-
/// - Immutable: a Zone's values are set when the Zone is created and cannot be
15-
/// changed afterwards.
16-
/// - Reading and writing values: a Zone implements the `[]` operator, allowing
17-
/// values to be read directly from it like a [Map], and writing values is
18-
/// possible only by forking another Zone and providing values to add/override
19-
/// (the rest of the values will be inherited from the forked Zone).
20-
///
21-
/// This library provides a simple abstraction over [Zone] for the purpose of
22-
/// implementing the rest of the Context specification. OpenTelemetry SDKs and
23-
/// instrumentation libraries should use this [Context] API instead of a [Zone]
24-
/// directly. Other users should usually not interact with Context at all and
25-
/// should instead manipulate it through cross-cutting concerns APIs provided by
26-
/// OpenTelemetry SDKs.
27-
import 'dart:async';
28-
293
import '../../../api.dart' as api;
30-
import '../trace/nonrecording_span.dart';
31-
32-
/// [ContextKey] used to store spans in a [Context].
33-
final ContextKey spanKey = Context.createKey('OpenTelemetry Context Key SPAN');
34-
35-
class Context {
36-
final Zone _zone;
4+
import 'context_manager.dart';
375

38-
Context._(this._zone);
6+
class ContextKey {}
397

8+
abstract class Context {
409
/// The active context.
41-
static Context get current => Context._(Zone.current);
10+
@Deprecated('This method will be removed in the future.')
11+
static Context get current => globalContextManager.active;
4212

4313
/// The root context which all other contexts are derived from.
4414
///
@@ -47,50 +17,47 @@ class Context {
4717
/// Only use this context if you are certain you need to disregard the
4818
/// current [Context]. For example, when instrumenting an asynchronous
4919
/// event handler which may fire while an unrelated [Context] is "current".
50-
static Context get root => Context._(Zone.root);
20+
@Deprecated('We are planning to remove this in the future.')
21+
static Context get root => globalContextManager.root;
5122

5223
/// Returns a key to be used to read and/or write values to a context.
5324
///
5425
/// [name] is for debug purposes only and does not uniquely identify the key.
5526
/// Multiple calls to this function with the same [name] will not return
5627
/// identical keys.
57-
static ContextKey createKey(String name) => ContextKey(name);
28+
@Deprecated(
29+
'This method will be removed in the future. Please use ContextKey() directly.')
30+
static ContextKey createKey(String name) => ContextKey();
5831

5932
/// Returns the value from this context identified by [key], or null if no
6033
/// such value is set.
61-
T? getValue<T>(ContextKey key) => _zone[key];
34+
T? getValue<T>(ContextKey key);
6235

6336
/// Returns a new context created from this one with the given key/value pair
6437
/// set.
6538
///
6639
/// If [key] was already set in this context, it will be overridden. The rest
6740
/// of the context values will be inherited.
68-
Context setValue(ContextKey key, Object value) =>
69-
Context._(_zone.fork(zoneValues: {key: value}));
41+
Context setValue(ContextKey key, Object value);
7042

7143
/// Returns a new [Context] created from this one with the given [api.Span]
7244
/// set.
73-
Context withSpan(api.Span span) => setValue(spanKey, span);
45+
@Deprecated(
46+
'This method will be removed in the future, new method will be added.')
47+
Context withSpan(api.Span span);
7448

7549
/// Execute a function [fn] within this [Context] and return its result.
76-
R execute<R>(R Function() fn) => _zone.run(fn);
50+
@Deprecated('This method will be removed in the future.')
51+
R execute<R>(R Function() fn);
7752

7853
/// Get the [api.Span] attached to this [Context], or an invalid, [api.Span] if no such
7954
/// [api.Span] exists.
80-
api.Span get span =>
81-
getValue(spanKey) ?? NonRecordingSpan(api.SpanContext.invalid());
55+
@Deprecated(
56+
'This method will be removed in the future, new method will be added.')
57+
api.Span get span;
8258

8359
/// Get the [api.SpanContext] from this [Context], or an invalid [api.SpanContext] if no such
8460
/// [api.SpanContext] exists.
85-
api.SpanContext get spanContext =>
86-
(getValue(spanKey) ?? NonRecordingSpan(api.SpanContext.invalid()))
87-
.spanContext;
88-
}
89-
90-
class ContextKey {
91-
/// Name of the context key.
92-
final String name;
93-
94-
/// Construct a [ContextKey] with a given [name].
95-
ContextKey(this.name);
61+
@Deprecated('This method will be removed in the future.')
62+
api.SpanContext get spanContext;
9663
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright 2021-2022 Workiva.
2+
// Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information
3+
4+
import 'package:opentelemetry/api.dart';
5+
6+
import 'zone_context_manager.dart';
7+
8+
/// The [ContextManager] is responsible for managing the current [Context].
9+
/// Different implementations of [ContextManager] can be registered to use different underlying storage mechanisms.
10+
abstract class ContextManager {
11+
@Deprecated(
12+
'We are planning to remove this in the future, please use globalContextManager.active instead.')
13+
Context get root;
14+
15+
Context get active;
16+
}
17+
18+
/// The default implementation is [ZoneContextManager], which uses Dart zones to store the current [Context].
19+
/// in the future, this will be replaced with noop context manager which maintains map context.
20+
final ContextManager _noopcontextManager = ZoneContextManager();
21+
ContextManager _contextManager = _noopcontextManager;
22+
23+
ContextManager get globalContextManager => _contextManager;
24+
25+
void registerGlobalContextManager(ContextManager contextManager) {
26+
if (_contextManager != _noopcontextManager) {
27+
throw StateError(
28+
'Global context manager is already registered, registerContextManager must be called only once.');
29+
}
30+
31+
_contextManager = contextManager;
32+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright 2021-2022 Workiva.
2+
// Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information
3+
4+
import '../../../api.dart';
5+
import '../../experimental_api.dart';
6+
7+
final ContextKey spanKey = ContextKey();
8+
9+
class MapContext implements Context {
10+
final Map<ContextKey, Object> _contextMap = {};
11+
12+
/// Returns the value from this context identified by [key], or null if no
13+
/// such value is set.
14+
@override
15+
T? getValue<T>(ContextKey key) => _contextMap[key] as T?;
16+
17+
/// Returns a new context created from this one with the given key/value pair
18+
/// set.
19+
///
20+
/// If [key] was already set in this context, it will be overridden. The rest
21+
/// of the context values will be inherited.
22+
@override
23+
MapContext setValue(ContextKey key, Object value) {
24+
final newContext = MapContext();
25+
newContext._contextMap.addAll(_contextMap);
26+
newContext._contextMap[key] = value;
27+
return newContext;
28+
}
29+
30+
/// Returns a new [MapContext] created from this one with the given [Span]
31+
/// set.
32+
@override
33+
MapContext withSpan(Span span) => setValue(spanKey, span);
34+
35+
/// Execute a function, this is a no-op for [MapContext].
36+
@override
37+
R execute<R>(R Function() fn) => fn();
38+
39+
/// Get the [Span] attached to this [MapContext], or an invalid, [Span] if no such
40+
/// [Span] exists.
41+
@override
42+
Span get span => getValue(spanKey) ?? NonRecordingSpan(SpanContext.invalid());
43+
44+
/// Get the [SpanContext] from this [MapContext], or an invalid [SpanContext] if no such
45+
/// [SpanContext] exists.
46+
@override
47+
SpanContext get spanContext => span.spanContext;
48+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright 2021-2022 Workiva.
2+
// Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information
3+
4+
import 'context.dart';
5+
import 'context_manager.dart';
6+
import 'map_context.dart';
7+
8+
final MapContext _rootContext = MapContext();
9+
10+
class NoopContextManager implements ContextManager {
11+
@override
12+
Context get root => _rootContext;
13+
14+
@override
15+
Context get active => _rootContext;
16+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// Copyright 2021-2022 Workiva.
2+
// Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information
3+
4+
/// The OpenTelemetry SDKs require a mechanism for propagating context and the
5+
/// OpenTelemetry specification outlines the requirements for this context
6+
/// implementation: https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/context/context.md
7+
///
8+
/// The spec notes that "languages are expected to use the single, widely used
9+
/// Context implementation if one exists for them." Fortunately, the Dart SDK
10+
/// provides just that with [Zone] - a representation of "an environment that
11+
/// remains stable across asynchronous calls." [Zone] also meets the core
12+
/// requirements of immutability and being able to read and write values:
13+
///
14+
/// - Immutable: a Zone's values are set when the Zone is created and cannot be
15+
/// changed afterwards.
16+
/// - Reading and writing values: a Zone implements the `[]` operator, allowing
17+
/// values to be read directly from it like a [Map], and writing values is
18+
/// possible only by forking another Zone and providing values to add/override
19+
/// (the rest of the values will be inherited from the forked Zone).
20+
///
21+
/// This library provides a simple abstraction over [Zone] for the purpose of
22+
/// implementing the rest of the Context specification. OpenTelemetry SDKs and
23+
/// instrumentation libraries should use this [ZoneContext] API instead of a [Zone]
24+
/// directly. Other users should usually not interact with Context at all and
25+
/// should instead manipulate it through cross-cutting concerns APIs provided by
26+
/// OpenTelemetry SDKs.
27+
import 'dart:async';
28+
29+
import '../../../api.dart';
30+
import '../../experimental_api.dart';
31+
32+
/// [ContextKey] used to store spans in a [ZoneContext].
33+
final ContextKey spanKey = ContextKey();
34+
35+
class ZoneContext implements Context {
36+
final Zone _zone;
37+
38+
ZoneContext._(this._zone);
39+
40+
/// The active context.
41+
static ZoneContext get current => ZoneContext._(Zone.current);
42+
43+
/// The root context which all other contexts are derived from.
44+
///
45+
/// It should generally not be required to use the root [ZoneContext] directly -
46+
/// instead, use [ZoneContext.current] to operate on the current [ZoneContext].
47+
/// Only use this context if you are certain you need to disregard the
48+
/// current [ZoneContext]. For example, when instrumenting an asynchronous
49+
/// event handler which may fire while an unrelated [ZoneContext] is "current".
50+
static ZoneContext get root => ZoneContext._(Zone.root);
51+
52+
/// Returns the value from this context identified by [key], or null if no
53+
/// such value is set.
54+
@override
55+
T? getValue<T>(ContextKey key) => _zone[key];
56+
57+
/// Returns a new context created from this one with the given key/value pair
58+
/// set.
59+
///
60+
/// If [key] was already set in this context, it will be overridden. The rest
61+
/// of the context values will be inherited.
62+
@override
63+
ZoneContext setValue(ContextKey key, Object value) =>
64+
ZoneContext._(_zone.fork(zoneValues: {key: value}));
65+
66+
/// Returns a new [ZoneContext] created from this one with the given [Span]
67+
/// set.
68+
@override
69+
ZoneContext withSpan(Span span) => setValue(spanKey, span);
70+
71+
/// Execute a function [fn] within this [ZoneContext] and return its result.
72+
@override
73+
R execute<R>(R Function() fn) => _zone.run(() => fn());
74+
75+
/// Get the [Span] attached to this [ZoneContext], or an invalid, [Span] if no such
76+
/// [Span] exists.
77+
@override
78+
Span get span => getValue(spanKey) ?? NonRecordingSpan(SpanContext.invalid());
79+
80+
/// Get the [SpanContext] from this [ZoneContext], or an invalid [SpanContext] if no such
81+
/// [SpanContext] exists.
82+
@override
83+
SpanContext get spanContext => span.spanContext;
84+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright 2021-2022 Workiva.
2+
// Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information
3+
4+
import '../../../api.dart';
5+
import '../../experimental_api.dart';
6+
import 'zone_context.dart';
7+
8+
class ZoneContextManager implements ContextManager {
9+
@override
10+
Context get active => ZoneContext.current;
11+
12+
@override
13+
Context get root => ZoneContext.root;
14+
}

lib/src/api/propagation/w3c_trace_context_propagator.dart

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
// Copyright 2021-2022 Workiva.
22
// Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information
3-
4-
import '../trace/nonrecording_span.dart';
5-
63
import '../../../api.dart' as api;
4+
import '../../experimental_api.dart';
75

86
class W3CTraceContextPropagator implements api.TextMapPropagator {
97
static const String _traceVersion = '00';

lib/src/api/trace/nonrecording_span.dart

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ import '../../../api.dart' as api;
1010
/// See https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md#wrapping-a-spancontext-in-a-span
1111
/// for more information.
1212
///
13-
/// This class should not be exposed to consumers and is used internally to wrap
14-
/// [api.SpanContext] being injected or extracted for external calls.
1513
class NonRecordingSpan implements api.Span {
1614
final api.SpanId _parentSpanId = api.SpanId.invalid();
1715
final api.SpanContext _spanContext;

0 commit comments

Comments
 (0)