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

add cross-cutting context api functions #176

Merged
merged 13 commits into from
Jul 18, 2024
Merged

Conversation

blakeroberts-wk
Copy link
Contributor

@blakeroberts-wk blakeroberts-wk commented Jun 19, 2024

Which problem is this PR solving?

The context API has deprecated members without appropriate replacements.

Fixes #173, #174

Short description of the change

The following functions were added to the API:

  • contextWithSpan,
  • contextWithSpanContext
  • spanContextFromContext
  • spanFromContext
  • traceContext (experimental)
  • traceSyncContext (experimental)

The following functions from the API were deprecated:

  • trace
  • traceSync

The following changes were made to implementations but not interfaces to avoid breaking changes:

  • api.NoopTracer.start() and sdk.Tracer.start() support an optional argument bool newRoot. The API's Tracer interface will include this option in the 0.19.0 release.
  • api.ZoneContext.run() and api.MapContext.run() methods were added. The API's Context interface will include this method in the 0.19.0 release.

How Has This Been Tested?

The three new examples added produce expected output.

Checklist:

  • Unit tests have been added
  • Documentation has been updated

@aviary-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.

@blakeroberts-wk
Copy link
Contributor Author

QA +1 changes were tested on a dart browser app with both zone and map context propagation. Examples succinctly demonstrate functionality as well

@Deprecated(
'This method will be removed in 0.19.0. Use [contextWithSpanContext(globalContextManager.active, SpanContext.invalid())] instead.')
static Context get root => contextWithSpanContext(
globalContextManager.active, SpanContext.invalid());
Copy link
Contributor

Choose a reason for hiding this comment

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

It took me a while to understand how it works, is that possible to simplify this effort and put it as a mehod for globalContextManager or ContextManager?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The purpose of this api is to allow creating new root spans. Rather than having users reference a root context when creating a span, tracer.startSpan() will include an optional named parameter bool newRoot. Because adding that parameter to the tracer interface is a breaking change, it will need to wait until v0.19.0. For now, folks could cast their api tracer to an sdk tracer and use newRoot. Or, as the deprecated annotation suggests, manually construct a context with an invalid span context. Either way, these are short term work arounds until a more succinct solution can be introduced

lib/src/api/context/map_context.dart Outdated Show resolved Hide resolved
lib/src/api/context/map_context.dart Show resolved Hide resolved
Comment on lines +46 to +48
/// Call [fn] with this [MapContext] and return its result.
@experimental
R run<R>(R Function(Context context) fn) => fn(this);
Copy link
Contributor

Choose a reason for hiding this comment

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

What's the use case for this? Seems to me like it would be simpler for the caller to just run fn(this) themselves since they'd have to have a reference to the function and the context in order to call this method.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes. I'm still not sold on this yet, but right now it's for api completeness because our ZoneContext impl needs a way to execute a function within its private zone reference.

I'd like to come up with a paradigm so the Context api doesn't need a run method. However, this requires the consumer to have and/or use a reference to a zone. Which is almost okay except for libraries. By having consumers need to reference the zone, libraries have to worry about portability of their tracing code vs just calling the run function whether it's needed or not (whether their consumers have registered a zone context manager or are using the no op context manager)

lib/src/api/context/zone_context.dart Outdated Show resolved Hide resolved
lib/src/api/context/zone_context.dart Outdated Show resolved Hide resolved
Comment on lines +62 to +64
/// Call [fn] in this [ZoneContext]'s [Zone] and return its result.
@experimental
R run<R>(R Function(Context context) fn) => _zone.run(() => fn(this));
Copy link
Contributor

Choose a reason for hiding this comment

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

Now that I'm seeing this here as well, should run be a part of the Context interface? Or are we holding off on that because it's experimental?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Holding off because that'd be a breaking change. My current plan is to add the run method to the context api in the 0.19.0 release and still mark it as experimental at least for the time being 😅 I don't like it on the context api but I don't know how else to support zones and non-zones seamlessly

/// Records a span of the given [name] for the given function with a given
/// [api.Tracer] and marks the span as errored if an exception occurs.
@experimental
Future<T> traceContext<T>(String name, Future<T> Function(api.Context) fn,
Copy link
Contributor

Choose a reason for hiding this comment

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

Okay, I think I see now why having run defined on the Context classes is useful now. Which makes me think it should be defined on the parent Context class, too.

try {
return await context.withSpan(span).execute(fn);
// TODO: remove this check once `run` exists on context interface
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this answers my question - we're avoiding adding it to the interface right now because it would be breaking to any implementers?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. Lol I should have read your entire review before responding 😆

@blakeroberts-wk
Copy link
Contributor Author

QA +1

api.traceSync('syncTrace', () {
span = api.Context.current.span as Span;
api.traceContextSync('syncTrace', (context) {
span = api.spanFromContext(context) as Span;
Copy link
Contributor

Choose a reason for hiding this comment

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

Are these as Span casts still necessary? Looks like spanFromContext should be typed to return Span

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. The cast is to an internal Span type from the SDK, not the API. The SDK type is not publicly exposed

@blakeroberts-wk
Copy link
Contributor Author

QA +1 CI passes, examples produce expected output, and consumed the changes into a private project that was able to accomplish context propagation via zones without using non-exported types:

  final tracer = otel.globalTracerProvider.getTracer('my-tracer');
  final span = tracer.startSpan('my-span');
  final ctx = otel.contextWithSpan(otel.globalContextManager.active, span);
  otel.spanFromContext(ctx).end();

@blakeroberts-wk
Copy link
Contributor Author

@Workiva/release-management-p

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

@rmconsole6-wk rmconsole6-wk merged commit 9ecd4e2 into master Jul 18, 2024
4 checks passed
@rmconsole6-wk rmconsole6-wk deleted the add-context-functions branch July 18, 2024 15:44
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.

[Question] What is correct way to call Context.current.span now?
6 participants