-
Notifications
You must be signed in to change notification settings - Fork 33
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
O11Y-988: Record Spans #2
Conversation
Merge Requirements Met ✅Request Rosie to automerge this pull request by including @Workiva/release-management-p in a comment. General InformationTicket(s): Code Review(s): #2 Reviewers: blakeroberts-wk, tylersnavely-wf Additional InformationWatchlist Notifications: None
Note: This is a shortened report. Click here to view Rosie's full evaluation. |
Security InsightsNo security relevant content was detected by automated scans. Action Items
Questions or Comments? Reach out on Slack: #support-infosec. |
48722d1
to
b723b76
Compare
withContext; | ||
export 'src/api/trace/id_generator.dart' | ||
show | ||
IdGenerator; |
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.
does this need to be exposed?
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.
like it is the api, but can the sdk consume this without exporting it publicly? More over, is this truly apart of the specification, or an implementation detail about which the specification dictates that a trace/span id be of a certain form?
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.
Although I don't see anyone needing to, yes, according to the spec.
https://github.com/open-telemetry/opentelemetry-specification/blob/7cef355a15c4430aa323d155206a2945fcd67a05/specification/trace/sdk.md#id-generators
We provide a default ID generator, but to be fully compliant, we need to allow others to configure their own ID generators on their tracers. Exporting the abstract IdGenerator
allows them to do so.
QA +1 |
@Workiva/release-management-p |
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 from RM
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.
Sorry for the post-merge review, I was out on some PTO and just got to this
final ContextKey spanKey = Context.createKey('OpenTelemetry Context Key SPAN'); | ||
|
||
/// Get the [Span] attached to the [context]. | ||
dynamic getSpan(Context context) => context.getValue(spanKey); |
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 this return type be Span
?
|
||
/// Get the SpanContext of the [context]. | ||
dynamic getSpanContext(Context context) { | ||
final span = context.getValue(spanKey); |
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 getSpan()
here?
final span = context.getValue(spanKey); | |
final span = getSpan(context); |
dynamic withContext(Context context, Function fn) { | ||
final scope = Context.attach(context); | ||
final result = fn(); | ||
context.detach(scope); | ||
return result; |
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 readme, it looks like this should support async functions, but currently this isn't awaiting that future at all, which would result in the scope being detached before it's complete. We'd need to check if result is Future
like so:
dynamic withContext(Context context, Function fn) { | |
final scope = Context.attach(context); | |
final result = fn(); | |
context.detach(scope); | |
return result; | |
dynamic withContext(Context context, Function fn) { | |
final scope = Context.attach(context); | |
var result = fn(); | |
if (result is Future) { | |
result.whenComplete(() { | |
context.detach(scope); | |
}); | |
} else { | |
context.detach(scope); | |
} | |
return result; |
/// Get the time when the span was closed, or null if still open. | ||
int get endTime; | ||
|
||
/// Get the time when the span was started. | ||
int get startTime; |
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.
The "Get " part of these doc comments could be omitted.
/// Get the ID of the span. | ||
String get spanId; | ||
|
||
/// Get the ID of the trace the span is a part of. | ||
String get traceId; | ||
|
||
/// Get the state of the entire trace. | ||
TraceState get traceState; |
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.
}); | ||
|
||
test('withContext attaches and detaches with async fn', () async { | ||
expect(await withContext(Context.current, () async => 42), equals(42)); |
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 test could be improved to verify that it is actually awaiting the async function given to it before detaching the scope. One way to do that might be like so:
expect(await withContext(Context.current, () async => 42), equals(42)); | |
final key = Context.createKey('test'); | |
final parentContext = Context.current; | |
final childContext = parentContext.setValue(key, 'foo'); | |
final result = withContext(childContext, expectAsync0(() async { | |
// Verify that our context was attached prior to calling our function | |
expect(Context.current, same(childContext)); | |
await Future.delayed(Duration(milliseconds: 100)); | |
return 42; | |
})); | |
expect(await result, 42); | |
// Now that our function has completed, the child context should no longer be active | |
expect(Context.current, same(parentContext)); |
It might also be worth updating the sync test in a similar manner, such that it verifies that the expected context is active from within the callback.
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 also made me wonder how withContext
is expected to work in the case of multiple, concurrent async usages. Since Context.current
is effectively a global singleton right now.. couldn't two async usages of withCurrent
cause unexpected results where the second invocation could change the current context before the first invocation's async callback has completed?
Makes me wonder if the current scope should be read/written via a Zone
value so that multiple context "branches" could exist concurrently.
|
||
final span = tracer.startSpan('foo'); | ||
|
||
expect(span.startTime, isA<int>()); |
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.
Span.startTime
is already typed as int
, so this assertion may be confusing to those who read it. Maybe we just want to verify that it's non-null?
expect(span.startTime, isA<int>()); | |
expect(span.startTime, isNotNull); |
expect(span.spanContext.traceId, isA<String>()); | ||
expect(span.spanContext.spanId, isA<String>()); |
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.
Same note here - this typing is already enforced by the language, so it's probably more appropriate to use isNotNull
if that's what we're really wanting to verify here.
|
||
final childSpan = tracer.startSpan('bar', context: context); | ||
|
||
expect(childSpan.startTime, isA<int>()); |
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.
expect(childSpan.startTime, isA<int>()); | |
expect(childSpan.startTime, isNotNull); |
expect(childSpan.spanContext.spanId, allOf([ | ||
isA<String>(), | ||
isNot(equals(parentSpan.spanContext.spanId)) | ||
])); |
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.
expect(childSpan.spanContext.spanId, allOf([ | |
isA<String>(), | |
isNot(equals(parentSpan.spanContext.spanId)) | |
])); | |
expect(childSpan.spanContext.spanId, isNot(parentSpan.spanContext.spanId)); |
This PR allows for creation of traces and their spans. A version is not being cut because this should not be consumed; spans are just being held in memory, so this could cause a problem if is was. The next bit of work would be to allow exporters that can properly manage the generated spans.
An example for how this works is as follows: