-
Notifications
You must be signed in to change notification settings - Fork 2
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
AF-3505 : ActiveSpan #34
Conversation
Security InsightsNo security relevant content was detected by automated scans. Action Items
Questions or Comments? Reach out on Slack: #support-infosec. |
d2f882a
to
df1160c
Compare
example/default/example.dart
Outdated
@@ -50,15 +50,16 @@ void runFailureCase() { | |||
|
|||
HttpRequest.getString('http://httpstat.us/500').then((String result) { | |||
span.log('data_received', payload: result); | |||
}).catchError((error) { | |||
}).catchError((dynamic error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dart recommends not specifying types in these anonymous functions. Is there a reason we are declaring this as dynamic
? If so, would it be better to inline a type cast to be more explicit?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was getting an analyzer warning to 'Specify type annotations'.
class JsonSerializableSpan { | ||
// ignore: public_member_api_docs |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BTW you can ignore this lint for the whole file (https://www.dartlang.org/guides/language/analysis-options#ignoring-specific-analysis-rules)
// ignore_for_file: public_member_api_docs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ty
lib/noop_tracer.dart
Outdated
export 'src/noop_span.dart'; | ||
export 'src/noop_span_context.dart'; | ||
export 'src/noop_tracer.dart'; | ||
export 'src/reference.dart'; | ||
export 'src/reference.dart'; | ||
export 'src/span_context.dart'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This and the line above it are repeated export statements.
lib/noop_tracer.dart
Outdated
export 'src/noop_span.dart'; | ||
export 'src/noop_span_context.dart'; | ||
export 'src/noop_tracer.dart'; | ||
export 'src/reference.dart'; | ||
export 'src/reference.dart'; | ||
export 'src/span_context.dart'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, do we need to export this stuff through Noop Tracer? It makes more sense to me that they would import the normal library entrypoint to get access to these things, and use this entrypoint for just the Noop-related exports.
lib/src/noop_scope.dart
Outdated
import 'package:opentracing/opentracing.dart'; | ||
|
||
/// The No-op implementation of [Scope] in which all operations are no-op | ||
class NoOpScope implements Scope { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we use Noop
rather than NoOp
for consistency?
Maybe I missed it, or it's not needed. Do we also need to add a method to set the active span? |
@danielharasymiw-wf said:
We can set the active span by calling Tracer.scope.activate(theActiveSpan);
I did indeed see that method here: Thoughts? @danielharasymiw-wf |
test/unit/noop_scope_test.dart
Outdated
import 'package:opentracing/noop_tracer.dart'; | ||
|
||
void main() { | ||
group('NoOpScope: verify', () { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: s/NoOpScope/NoopScope here and in the test descriptions below
import 'package:opentracing/noop_tracer.dart'; | ||
|
||
void main() { | ||
group('NoOpScopeManager: verify', () { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: s/NoOpScope/NoopScope here and in the test descriptions below
test/unit/noop_tracer_test.dart
Outdated
@@ -39,4 +39,10 @@ void main() { | |||
expect(noopSpan.context.sampled, expectedContext.sampled); | |||
expect(noopSpan.context.baggage, expectedContext.baggage); | |||
}); | |||
|
|||
test('Verify startSpan returns NoOpSpan from NoOpScopeManager', () { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: s/NoOp/Noop in NoOpSpan, NoOpScopeManager and noOpTracer below
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just a few small nits otherwise looks good to me
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good besides a few small things (and sorry for all the doc nits).
Have you spiked out a reference implementation of this that consumes these classes? I'm a little leery of committing to a public interface that doesn't have any real-world concrete implementations.
Also, a bigger question: are we trying to go off of this ScopeManager proposal, or just the Java impl minus the builder stuff?
lib/src/abstract_scope.dart
Outdated
/// A [Scope] formalizes the activation and deactivation of a [Span], usually | ||
/// from a CPU standpoint. | ||
/// | ||
/// Many times a [Span] will be extant ([Span].finish() has not been called) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#nit Fix dartdoc reference
/// Many times a [Span] will be extant ([Span].finish() has not been called) | |
/// Many times a [Span] will be extant ([Span.finish] has not been called) |
lib/src/abstract_scope.dart
Outdated
@@ -0,0 +1,21 @@ | |||
import 'package:opentracing/opentracing.dart'; | |||
|
|||
/// A [Scope] formalizes the activation and deactivation of a [Span], usually |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#nit the Dart style guide discourages redundant references to the surrounding context
/// A [Scope] formalizes the activation and deactivation of a [Span], usually | |
/// A Scope formalizes the activation and deactivation of a [Span], usually |
lib/src/abstract_scope.dart
Outdated
/// Many times a [Span] will be extant ([Span].finish() has not been called) | ||
/// despite being in a non-runnable state from a CPU/scheduler standpoint. For | ||
/// instance, a [Span] representing the client side of an RPC will be unfinished | ||
/// but blocked on IO while the RPC is still outstanding. A [Scope] defines when |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#nit
/// but blocked on IO while the RPC is still outstanding. A [Scope] defines when | |
/// but blocked on IO while the RPC is still outstanding. A Scope defines when |
lib/src/abstract_scope.dart
Outdated
/// Mark the end of the active period for the current thread and [Scope], | ||
/// updating the [ScopeManager].active() in the process. | ||
/// | ||
/// NOTE: Calling 'close' more than once on a single {@link Scope} instance |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#nit
/// NOTE: Calling 'close' more than once on a single {@link Scope} instance | |
/// NOTE: Calling 'close' more than once on a single Scope instance |
lib/src/abstract_scope.dart
Outdated
/// but blocked on IO while the RPC is still outstanding. A [Scope] defines when | ||
/// a given [Span] is scheduled and on the path. | ||
abstract class Scope { | ||
/// Mark the end of the active period for the current thread and [Scope], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#nit
/// Mark the end of the active period for the current thread and [Scope], | |
/// Mark the end of the active period for the current thread and Scope, |
lib/src/abstract_scope.dart
Outdated
/// a given [Span] is scheduled and on the path. | ||
abstract class Scope { | ||
/// Mark the end of the active period for the current thread and [Scope], | ||
/// updating the [ScopeManager].active() in the process. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#nit
/// updating the [ScopeManager].active() in the process. | |
/// updating the [ScopeManager.active] in the process. |
lib/src/abstract_scope.dart
Outdated
/// Mark the end of the active period for the current thread and [Scope], | ||
/// updating the [ScopeManager].active() in the process. | ||
/// | ||
/// NOTE: Calling 'close' more than once on a single {@link Scope} instance |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#nit
/// NOTE: Calling 'close' more than once on a single {@link Scope} instance | |
/// NOTE: Calling 'close' more than once on a single Scope instance |
lib/src/abstract_scope_manager.dart
Outdated
/// Returns a [Scope] instance to control the end of the active period for the | ||
/// [Span]. It is a programming error to neglect calling [Scope].close() on | ||
/// the returned instance. | ||
Scope activate(Span span, {bool finishSpanOnClose}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I noticed that the spec lists finishSpanOnClose as required.
A required boolean parameter finish span on close will mark whether...
This should be made a positional parameter or have the @required
annotation added.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with your suggestion, but later came across this: opentracing/opentracing-java#291
I also noticed that your reference isn't the spec, but rather a suggested change to it. I'm inclined to remove this parameter for now, until that spec suggestion is accepted and/or we have an immediate use-case for it. Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After creating an actual implementation in basictracer_dart, I've changed my position on your suggestion and think we should proceed with it.
lib/src/abstract_scope.dart
Outdated
/// leads to undefined behavior. | ||
void close(); | ||
|
||
/// Returns the [Span] that's been scoped by this [Scope] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#nit Getters should be documented using noun phrases.
/// Returns the [Span] that's been scoped by this [Scope] | |
/// The Span that's been scoped by this Scope. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll stop commenting on these; sorry, my OCD is kicking in... 😅
If you wouldn't be offended, I'd be more than happy to make a PR into this branch to update the doc comments to match the Dart style guide.
|
||
/// Returns the activer [Span]. This is a shorthand for | ||
/// `Tracer.scopeManager().active().span()` and null will be returned if | ||
/// [ScopeManager].active()} is null. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Based on the description, can we just provide a default implementation?
Span activeSpan() => scopeManager.active?.span;
lib/src/abstract_tracer.dart
Outdated
/// Returns the activer [Span]. This is a shorthand for | ||
/// `Tracer.scopeManager().active().span()` and null will be returned if | ||
/// [ScopeManager].active()} is null. | ||
Span activeSpan(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these changes missing the Tracer Changes from the spec?
Also, is there supposed to be an activeSpan
on this class? I don't see that in the spec
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these changes missing the Tracer Changes from the spec?
Yes, these changes are missing some portions of the proposed spec changes. The only parts I implemented were those that would specifically help accomplish the targeted use-case:
consolidate the spans occurring from the time the user has started to navigate to Home being loaded and populated.
Also, is there supposed to be an activeSpan on this class? I don't see that in the spec
Yes, this is a convenience method that was borrowed from https://developer.lightbend.com/docs/telemetry/current/extensions/opentracing/api.html#globaltracer-active-span and https://github.com/opentracing-contrib/java-agent#creating-custom-rules .
We could probably just include it in our implementation if we don't think we want it in this abstract class.
Ready for re-review. |
lib/src/abstract_tracer.dart
Outdated
/// Returns the activer [Span]. This is a shorthand for | ||
/// `Tracer.scopeManager().active().span()` and null will be returned if | ||
/// [ScopeManager].active()} is null. | ||
Span activeSpan(); | ||
Span activeSpan() => scopeManager.active?.span; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now that I think about it, this should just be a getter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed!
@@ -54,6 +55,17 @@ abstract class AbstractTracer { | |||
/// var serverSpan = Tracer.startSpan('...', { childOf : wireCtx }); | |||
SpanContext extract(String format, dynamic carrier); | |||
|
|||
/// Returns the current [ScopeManager], which may be a noop but may not be | |||
/// null. | |||
ScopeManager get scopeManager; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding these abstract members is considered a breaking change, since classes that extend this will no longer implement them.
To avoid a major change, could we implement this and return a NoopScopeManager
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea! That being said, I'm debating this and consulting with our Tracing people on it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After consulting and debating the question of whether we should accept this getter, resulting in a breaking change, the following pros and cons were identified:
Pros:
- The AbstractTracer continues to make it obvious what must be implemented by extenders/implementors.
- We maintain the distinction between abstract classes and inert implementations.
Cons:
- Propagation of this breaking change will require no less than 7 pull requests to update consumers. (mitigated via automation by RepoCommander)
Based on these considerations, I believe we should accept the breaking change and propagate it via RepoCommander.
/// null. | ||
ScopeManager get scopeManager; | ||
|
||
set scopeManager(ScopeManager value); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See scopeManager getter comment
@@ -37,6 +37,9 @@ abstract class Span { | |||
/// Each span has a UTC timestamp describing when it started. | |||
DateTime get startTime; | |||
|
|||
/// Each span has a UTC timestamp describing when it started. | |||
set startTime(DateTime value); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding these abstract members is considered a breaking change, since classes that extend this will no longer implement them.
Do we need this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, we need this change. Luckily at Workiva in Dart, we only use BasicTracer as our basis for all Tracing related classes.
Map getFields() { | ||
Map<String, dynamic> fieldMap = {}; | ||
Map<String, dynamic> getFields() { | ||
Map<String, dynamic> fieldMap = <String, dynamic>{}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#nit you only need to specify generic parameters on the LHS or RHS, not both; Dart infers the rest for you!
I think the convention is to use the RHS, since it allows the omission of the full type on the LHS
Map<String, dynamic> fieldMap = <String, dynamic>{}; | |
final fieldMap = <String, dynamic>{}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is from one of the lints opted into in this repo; I think that lint should be disabled unless there was a reason it was enabled.
Related: I agree with the sentiment expressed here by one of the linter package maintainers: dart-lang/linter#1068 (comment)
I do not believe that any project should enable a lint because there isn't a stated reason not to. I think, rather, that a project should only enable those lints for which there is a stated reason for it to be enabled.
test/unit/noop_tracer_test.dart
Outdated
test('Verify startSpan returns NoopSpan from NoopScopeManager', () { | ||
NoopTracer noOpTracer = new NoopTracer(); | ||
expect( | ||
identical(noOpTracer.startSpan(''), noOpTracer.activeSpan()), isTrue); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#nit can use same
matcher for a better error message
identical(noOpTracer.startSpan(''), noOpTracer.activeSpan()), isTrue); | |
same(noOpTracer.startSpan('')), noOpTracer.activeSpan()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ty! I didn't know that!
lib/src/noop_tracer.dart
Outdated
} | ||
|
||
@override | ||
Future<dynamic> flush() async {} | ||
|
||
@override | ||
Span activeSpan() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unnecessary override; this should use the same implementation as the abstract tracer, since this class seems to be the only thing providing activeSpan
coverage.
38de148
to
2b26664
Compare
b2b3f2c
to
2802716
Compare
Ready for review, fyi @greglittlefield-wf |
lib/src/noop_scope.dart
Outdated
/// Returns a new NoopScope, which will use the supplied Span. If no Span is | ||
/// supplied, a static instance of a NoopSpan will be used. | ||
NoopScope({Span span}) { | ||
_span = span ?? _span; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could overwriting the static _span when a new NoopScope is created have the potential to be problematic?
For instance:
final span1 = globalTracer().startSpan('span1');
final span2 = globalTracer().startSpan('span2');
final noopScope1 = noopScopeManager.activate(span1, false);
print(noopScope1.span.operationName); // span1
final noopScope2 = noopScopeManager.activate(span2, false);
print(noopScope1.span.operationName); // span2 - this shouldn't have changed, right?
print(noopScope2.span.operationName); // span2
I would expect the implementation to look more like:
/// The No-op implementation of [Scope] in which all operations are no-op
class NoopScope implements Scope {
static final Span _noopSpan = new NoopSpan();
/// Returns a new NoopScope, which will use the supplied Span. If no Span is
/// supplied, a static instance of a NoopSpan will be used.
NoopScope({Span span}) :
_span = span ?? _noopSpan;
final Span _span;
@override
void close() {}
@override
Span get span => _span;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point
/// Returns the [Scope], or null if none could be found. | ||
Scope get active; | ||
|
||
set active(Scope value); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There shouldn't be a setter here; this value gets set in activate
, and there is no setter in the Java implementation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please see https://github.com/Workiva/basictracer-dart/pull/59/files#diff-7c439d7fe63b0d14ff72fcbc3d094df5R32 for the reason to include this setter.
This allows us to auto-nest scopes:
Span operationASpan = new BasicSpan('OperationA')
scopeManager.activate(operationASpan, finishSpanOnClose);
/// start Operation A
/// make a bunch of spans representing sub-steps of Operation A
Span operationA1 = new BasicSpan('operationA1');
scopeManager.activate(operationA1, finishSpanOnClose);
/// make a bunch of spans representing sub-steps of Operation A1
/// end Operation A1
scopeManager.active.close()
/// end Operation A
scopeManager.active.close()
The resulting span structure would be similar to :
operationA
|- spans for sub-steps
|- operationA1
|-spans for sub-steps
Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense. Could we document that the setter should only be used by Scope/ScopeManager implementations, and discourage using it otherwise?
We would have to add ignore comments wherever used, but I think it'd be worth it to annotate it as @protected
as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1, good stuff, @dustinlessard-wf!
QA +1 [x] CI passes @Workiva/release-management-pp |
Description
For platforms not propagating the call-context, it's inconvenient to pass the active Span from function to function manually, so OpenTracing should provide, for those platforms, that Tracer contains a Scope Manager that grants access to the active Span through a container, called Scope (using some call-context storage, such as thread-local or coroutine-local)
Driving use case: We want to trace what happens from the time a pre-authenticated user navigates to Wdesk, until they have arrived at home and can see the file/folder list populated.
To permit this, we need to support the concept of ScopeManager and ActiveSpan in opentracing_dart.
Changes
Testing/QA
Code Review
@Workiva/app-frameworks