Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PPI-203 : deprecate context manager and implement attach/detach APIs #192

Closed
wants to merge 11 commits into from
Closed
22 changes: 11 additions & 11 deletions example/attach_detach_context/attach_detach_context.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,37 +13,37 @@ void main() {
tracer = tp.getTracer('instrumentation-name');

final span = tracer.startSpan('root-zone-attached-span')..end();
final token = attach(contextWithSpan(active, span));
final token = Context.attach(contextWithSpan(Context.current, span));

final completer = Completer();

// zone A
zoneWithContext(active).run(() {
zoneWithContext(Context.current).run(() {
final span = tracer.startSpan('zone-a-attached-span')..end();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would advise against shadowing the variables.

span and token are both shadowed and that certainly makes the example a little harder to read.

One way you could tackle this is to split the zone function. The other is just using more unique names.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair. I can update the variable names. Though I'm wondering if I should scrap this example for something more contrived. Like pretend to do an app "setup" or "load" or something 🤷

final context = contextWithSpan(active, span);
final context = contextWithSpan(Context.current, span);

// zone B
zoneWithContext(context).run(() {
tracer.startSpan('zone-b-span').end();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels like this zone-b-span should be a child of zone-a-attached-span? But the README says it is not. I suggest to use the same span names in the README.

completer.future.then((_) {
tracer.startSpan('zone-b-post-detach-span').end();
tracer.startSpan('zone-b-post-Context.detach-span').end();
});
});

final token = attach(context);
final token = Context.attach(context);
tracer.startSpan('zone-a-attached-child-span').end();
if (!detach(token)) {
throw Exception('Failed to detach context');
if (!Context.detach(token)) {
throw Exception('Failed to Context.detach context');
}

tracer.startSpan('zone-a-post-detach-span').end();
tracer.startSpan('zone-a-post-Context.detach-span').end();
});

if (!detach(token)) {
throw Exception('Failed to detach context');
if (!Context.detach(token)) {
throw Exception('Failed to Context.detach context');
}

completer.complete();

tracer.startSpan('root-zone-post-detach-span').end();
tracer.startSpan('root-zone-post-Context.detach-span').end();
}
4 changes: 2 additions & 2 deletions example/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,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 context is available via the global getter, [active].
var context = 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');
Expand Down
2 changes: 1 addition & 1 deletion example/stream_context/stream_context.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import 'package:opentelemetry/sdk.dart'
show ConsoleExporter, SimpleSpanProcessor, TracerProviderBase;

mixin EventContext {
final Context context = active;
final Context context = Context.current;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like this reads well where I see it througout.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

context context context 😆

}

class MyEvent with EventContext {
Expand Down
6 changes: 3 additions & 3 deletions example/w3c_context_propagation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ void main(List<String> args) async {

final span = tp.getTracer('instrumentation-name').startSpan('test-span-0');
final carrier = <String, String>{};
tmp.inject(contextWithSpan(active, span), carrier, MapSetter());
tmp.inject(contextWithSpan(Context.current, span), carrier, MapSetter());
await test(carrier);
span.end();
}
Expand All @@ -43,7 +43,7 @@ Future test(Map<String, String> carrier) async {
globalTracerProvider
.getTracer('instrumentation-name')
.startSpan('test-span-1',
context:
globalTextMapPropagator.extract(active, carrier, MapGetter()))
context: globalTextMapPropagator.extract(
Context.current, carrier, MapGetter()))
.end();
}
4 changes: 0 additions & 4 deletions lib/api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,11 @@ export 'src/api/common/resource_attributes.dart' show ResourceAttributes;
export 'src/api/common/semantic_attributes.dart' show SemanticAttributes;
export 'src/api/context/context.dart'
show
active,
attach,
Context,
ContextKey,
contextFromZone,
contextWithSpan,
contextWithSpanContext,
detach,
root,
spanContextFromContext,
spanFromContext,
zoneWithContext;
Expand Down
172 changes: 78 additions & 94 deletions lib/src/api/context/context.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ import '../trace/nonrecording_span.dart' show NonRecordingSpan;

final Logger _log = Logger('opentelemetry');

@sealed
/// 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 {}
blakeroberts-wk marked this conversation as resolved.
Show resolved Hide resolved

final ContextKey _contextKey = ContextKey();
Expand Down Expand Up @@ -60,24 +64,6 @@ Context contextFromZone({Zone? zone}) {
return (zone ?? Zone.current)[_contextKey] ?? _rootContext;
}

/// The root context which all other contexts are derived from.
///
/// Generally, the root [Context] should not be used directly. Instead, use
/// [active] to operate on the current [Context].
@experimental
Context get root {
return _rootContext;
}

/// The active context.
///
/// The active context is the latest attached context, if one exists, otherwise
/// the latest zone context, if one exists, otherwise the root context.
@experimental
Context get active {
return _activeAttachedContext ?? _activeZoneContext ?? _rootContext;
}

/// Returns the latest non-empty context stack, or the root stack if no context
/// stack is found.
List<ContextStackEntry> get _activeAttachedContextStack {
Expand All @@ -98,81 +84,15 @@ List<ContextStackEntry> get _activeAttachedContextStack {
return stack ?? _rootStack;
}

Context? get _activeAttachedContext {
Context? get _currentAttachedContext {
final stack = _activeAttachedContextStack;
return stack.isEmpty ? null : stack.last.context;
}

Context? get _activeZoneContext {
Context? get _currentZoneContext {
return Zone.current[_contextKey];
}

/// 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 a [Zone]. For example, if a [Context]
/// is attached while [active] is called within a [Zone] created by
/// [zoneWithContext], [active] will return the attached [Context] and not the
/// [Context] given to [zoneWithContext]. Once the attached [Context] is
/// detached, [active] will return the [Context] given to [zoneWithContext].
@experimental
ContextToken attach(Context context) {
final entry = ContextStackEntry(context);
(Zone.current[_contextStackKey] ?? _rootStack).add(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 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
bool detach(ContextToken token) {
final stack = _activeAttachedContextStack;

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 = Zone.current;
do {
final stack = zone?[_contextStackKey] as List<ContextStackEntry>?;
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
return false;
}

class Context {
final Context? _parent;
final ContextKey _key;
Expand All @@ -186,10 +106,11 @@ class Context {
Context._(this._parent, this._key, this._value);

/// The active context.
@Deprecated(
'This method will be removed in 0.19.0. Use the API global function '
'[active] instead.')
static Context get current => active;
///
/// The active context is the latest attached context, if one exists, otherwise
/// the latest zone context, if one exists, otherwise the root context.
static Context get current =>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is current more standard?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah. javascript is the only implementation that uses active

_currentAttachedContext ?? _currentZoneContext ?? _rootContext;

/// The root context which all other contexts are derived from.
///
Expand All @@ -198,9 +119,6 @@ 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 the API global function '
'[root] instead.')
static Context get root => _rootContext;

/// Returns a key to be used to read and/or write values to a context.
Expand All @@ -212,6 +130,72 @@ class Context {
'This method will be removed in 0.19.0. Use [ContextKey] instead.')
static ContextKey createKey(String name) => ContextKey();

/// 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 a [Zone]. For example, if a [Context]
/// is attached while [current] is called within a [Zone] created by
/// [zoneWithContext], [current] will return the attached [Context] and not the
/// [Context] given to [zoneWithContext]. Once the attached [Context] is
/// detached, [current] will return the [Context] given to [zoneWithContext].
@experimental
static ContextToken attach(Context context) {
final entry = ContextStackEntry(context);
(Zone.current[_contextStackKey] ?? _rootStack).add(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 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) {
final stack = _activeAttachedContextStack;

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 = Zone.current;
do {
final stack = zone?[_contextStackKey] as List<ContextStackEntry>?;
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
return false;
}

/// Returns the value identified by [key], or null if no such value exists.
T? getValue<T>(ContextKey key) =>
_key == key ? _value as T : _parent?.getValue(key);
Expand Down
5 changes: 2 additions & 3 deletions lib/src/api/context/context_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information

import 'context.dart' show Context;
import 'context.dart' as global show active, root;

/// The [ContextManager] is responsible for managing the current [Context].
/// Different implementations of [ContextManager] can be registered to use
Expand All @@ -12,12 +11,12 @@ class ContextManager {
@Deprecated(
'This method will be removed in 0.19.0. Use the API global function '
'[root] instead.')
Context get root => global.root;
Context get root => Context.root;

@Deprecated(
'This method will be removed in 0.19.0. Use the API global function '
'[active] instead.')
Context get active => global.active;
Context get active => Context.current;
}

final ContextManager _noopContextManager = ContextManager();
Expand Down
12 changes: 6 additions & 6 deletions lib/src/api/open_telemetry.dart
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ Future<T> trace<T>(String name, Future<T> Function() fn,
bool newRoot = false,
api.SpanKind spanKind = api.SpanKind.internal,
List<api.SpanLink> spanLinks = const []}) async {
context ??= api.active;
context ??= api.Context.current;
tracer ??= _tracerProvider.getTracer('opentelemetry-dart');

// TODO: use start span option `newRoot` instead
Expand All @@ -96,9 +96,9 @@ Future<T> trace<T>(String name, Future<T> Function() fn,
context = api.contextWithSpan(context, span);
try {
return await api.zoneWithContext(context).run(() async {
final token = api.attach(api.contextFromZone());
final token = api.Context.attach(api.contextFromZone());
final ret = await fn();
api.detach(token);
api.Context.detach(token);
return ret;
});
} catch (e, s) {
Expand All @@ -120,7 +120,7 @@ T traceSync<T>(String name, T Function() fn,
bool newRoot = false,
api.SpanKind spanKind = api.SpanKind.internal,
List<api.SpanLink> spanLinks = const []}) {
context ??= api.active;
context ??= api.Context.current;
tracer ??= _tracerProvider.getTracer('opentelemetry-dart');

// TODO: use start span option `newRoot` instead
Expand All @@ -133,9 +133,9 @@ T traceSync<T>(String name, T Function() fn,
context = api.contextWithSpan(context, span);
try {
final r = api.zoneWithContext(context).run(() {
final token = api.attach(api.contextFromZone());
final token = api.Context.attach(api.contextFromZone());
final ret = fn();
api.detach(token);
api.Context.detach(token);
return ret;
});
if (r is Future) {
Expand Down
2 changes: 1 addition & 1 deletion lib/src/sdk/trace/tracer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class Tracer implements api.Tracer {
List<api.SpanLink> links = const [],
Int64? startTime,
bool newRoot = false}) {
context ??= api.active;
context ??= api.Context.current;
startTime ??= _timeProvider.now;

// If a valid, active Span is present in the context, use it as this Span's
Expand Down
Loading