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

O11Y-988: Record Spans #2

Merged
merged 1 commit into from
Aug 10, 2021
Merged

O11Y-988: Record Spans #2

merged 1 commit into from
Aug 10, 2021

Conversation

tylersnavely-wf
Copy link
Contributor

@tylersnavely-wf tylersnavely-wf commented Aug 3, 2021

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:

import 'package:opentelemetry/api.dart';
import 'package:opentelemetry/sdk.dart' as otel_sdk;

final Tracer tracer = otel_sdk.TracerProvider().getTracer('appName', version: '1.0.0');

doWork() {
  Span parent = getSpan(Context.current);

  withContext(setSpan(Context.current, parent), () {
    Span span = tracer.startSpan('doWork');
    ...
    span.end();
  });
}

doMoreWork() async {
  Span parent = getSpan(Context.current);

  await withContext(setSpan(Context.current, parent), () async {
    Span span = tracer.startSpan('doMoreWork');
    ...
    span.end();
  });
}

main() async {
  // parent span
  Span span = tracer.startSpan('work-setup');

  // force all enclosed new spans will be a child of the parent span.
  withContext(setSpan(Context.current, span), () {
    doWork();
  });

  await withContext(setSpan(Context.current, span), () async {
    await doMoreWork();
  });

  span.end();
}

@rmconsole-wf
Copy link

rmconsole-wf commented Aug 3, 2021

Merge Requirements Met ✅

Request Rosie to automerge this pull request by including @Workiva/release-management-p in a comment.

General Information

Ticket(s):

Code Review(s): #2
Release Image Tags:

Reviewers: blakeroberts-wk, tylersnavely-wf

Additional Information

Watchlist Notifications: None

	When this pull is merged I will add it to the following release:
	Version: opentelemetry-dart 0.1.0
	Release Ticket(s): None


Note: This is a shortened report. Click here to view Rosie's full evaluation.
Last updated on Tuesday, August 10 11:58 AM CST

@aviary2-wf
Copy link

Security Insights

No security relevant content was detected by automated scans.

Action Items

  • Review PR for security impact; comment "security review required" if needed or unsure
  • Verify aviary.yaml coverage of security relevant code

Questions or Comments? Reach out on Slack: #support-infosec.

lib/api.dart Outdated Show resolved Hide resolved
lib/src/api/context/context.dart Outdated Show resolved Hide resolved
lib/src/api/trace/context_utils.dart Outdated Show resolved Hide resolved
lib/src/api/trace/tracer.dart Outdated Show resolved Hide resolved
pubspec.yaml Outdated Show resolved Hide resolved
pubspec.yaml Show resolved Hide resolved
@tylersnavely-wf tylersnavely-wf changed the title create spans wip O11Y-988: Record Spans Aug 5, 2021
@tylersnavely-wf tylersnavely-wf marked this pull request as ready for review August 5, 2021 16:41
@tylersnavely-wf tylersnavely-wf requested a review from a team as a code owner August 5, 2021 16:41
withContext;
export 'src/api/trace/id_generator.dart'
show
IdGenerator;
Copy link
Contributor

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?

Copy link
Contributor

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?

Copy link
Contributor Author

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.

@tylersnavely-wf
Copy link
Contributor Author

QA +1

@tylersnavely-wf
Copy link
Contributor Author

@Workiva/release-management-p

@rmconsole7-wk rmconsole7-wk merged commit e51f4eb into master Aug 10, 2021
Copy link

@rmconsole-wf rmconsole-wf left a comment

Choose a reason for hiding this comment

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

+1 from RM

@rmconsole7-wk rmconsole7-wk deleted the create-spans branch August 10, 2021 16:58
Copy link
Contributor

@evanweible-wf evanweible-wf left a 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);
Copy link
Contributor

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);
Copy link
Contributor

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?

Suggested change
final span = context.getValue(spanKey);
final span = getSpan(context);

Comment on lines +22 to +26
dynamic withContext(Context context, Function fn) {
final scope = Context.attach(context);
final result = fn();
context.detach(scope);
return result;
Copy link
Contributor

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:

Suggested change
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;

Comment on lines +17 to +21
/// 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;
Copy link
Contributor

Choose a reason for hiding this comment

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

Comment on lines +5 to +12
/// 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;
Copy link
Contributor

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));
Copy link
Contributor

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:

Suggested change
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.

Copy link
Contributor

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>());
Copy link
Contributor

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?

Suggested change
expect(span.startTime, isA<int>());
expect(span.startTime, isNotNull);

Comment on lines +14 to +15
expect(span.spanContext.traceId, isA<String>());
expect(span.spanContext.spanId, isA<String>());
Copy link
Contributor

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>());
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
expect(childSpan.startTime, isA<int>());
expect(childSpan.startTime, isNotNull);

Comment on lines +30 to +33
expect(childSpan.spanContext.spanId, allOf([
isA<String>(),
isNot(equals(parentSpan.spanContext.spanId))
]));
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
expect(childSpan.spanContext.spanId, allOf([
isA<String>(),
isNot(equals(parentSpan.spanContext.spanId))
]));
expect(childSpan.spanContext.spanId, isNot(parentSpan.spanContext.spanId));

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants