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

4.x Add baggage API and allow access to baggage via SpanContext (as well as Span) #8320

Merged
merged 3 commits into from
Mar 4, 2024

Conversation

tjquinno
Copy link
Member

@tjquinno tjquinno commented Jan 31, 2024

Description

Resolves #8319

Users have requested a way to retrieve baggage from a SpanContext directly rather than having to use the SpanContext to create (and start and end) a Span. Currently only the Span type allows access to baggage.

New types and implementations:

  1. The new Baggage interface provides read-only access to baggage with get, keys, andcontainsKey operations.
  2. The new WritableBaggage interface extends Baggage and adds a set method.
  3. The OpenTracing and OpenTelemetry providers (and the implicit no-op implementation in tracing/tracing) add implementations of these interfaces and add tests.

Changes to existing code:

  1. The SpanContext interface adds the Baggage baggage() method.
  2. The Span interface adds the WritableBaggage baggage() method.
  3. The existing Span interface methods for directly manipulating baggage are marked as deprecated to encourage users to use the more powerful Baggage interfaces.

Baggage is read-only, typically based on the baggage data from an underlying SpanContext because we don't necessarily know how a SpanContext is derived--for example, it can be from incoming request headers--and so we should not allow callers to update baggage derived from span contexts.

In the Helidon neutral tracing API, though, the Span allows updates to the baggage we associate with the span, hence the WritableBaggage type.

Very brief guided tour of the changes

For other reasons we already had in the OpenTelemetry provider the MutableOpenTelemetryBaggage class which implements an Otel baggage-related interface. This PR expands that class to also implement the new Helidon WritableBaggage interface.

The new OpenTracingBaggage class implements the read-only Baggage interface and it has a small inner subclass which implements WritableBaggage. We use an inner class here because in OpenTracing when span-related baggage is updated we need to propagate the update to the OpenTracing span itself as well as keeping track of it ourselves.

Both providers add tests to exercise their implementations of the new baggage API.

The OpenTracing provider component pom.xml now includes several Otel dependencies in test scope. This allows the tests to rely on the actual Otel extractor and propagator, for example, rather than no-ops that would cause tests to fail.

Documentation

The PR includes a brief addition to the tracing doc page discussing baggage with links to the JavaDoc.

@tjquinno tjquinno self-assigned this Jan 31, 2024
@oracle-contributor-agreement oracle-contributor-agreement bot added the OCA Verified All contributors have signed the Oracle Contributor Agreement. label Jan 31, 2024

@Override
public Set<String> keys() {
return Collections.unmodifiableSet(values.keySet());
Copy link
Member

Choose a reason for hiding this comment

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

Depending on the frequency of the call, consider storing an unmodifiable Set as an instance variable and returning it directly.

Copy link
Member Author

Choose a reason for hiding this comment

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

The values map can grow at any time, so the key set will not be stable. A Set instance field would need to be updated whenever baggage was added or a dirty bit set and the Set reconstituted lazily if dirty upon access. My gut feel is that keys will be infrequently invoked, at least seldom enough for optimizing this to be premature given the map's changeability.

Copy link
Member

Choose a reason for hiding this comment

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

Fair. (Do note that Collections#unmodifiable... methods account for changing values "underneath"; that's why they're called unmodifiable and not, say, immutable.)

Copy link
Member Author

Choose a reason for hiding this comment

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

Exactly. We want the caller to see the accurate key set without being able to mess with its contents.


@Override
public Set<String> keys() {
return Collections.unmodifiableSet(baggage.keySet());
Copy link
Member

Choose a reason for hiding this comment

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

Same as earlier comment

Copy link
Member Author

Choose a reason for hiding this comment

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

Ditto.

@@ -106,6 +106,9 @@ public <T> T unwrap(Class<T> tracerClass) {
if (tracerClass.isAssignableFrom(delegate.getClass())) {
return tracerClass.cast(delegate);
}
if (tracerClass.isInstance(this)) {
Copy link
Member

Choose a reason for hiding this comment

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

Minor nit: depending on how "deep" you want/need the unwrapping to go you could do this whole general thing in a while loop

Copy link
Member Author

Choose a reason for hiding this comment

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

The pattern in tracing previously established has been to check only the delegate and this against the requested type and return whichever one matches (and reject the request if neither matches). There's no real "depth" here to be concerned with except for the one level to the delegate.

}

@Override
public WritableBaggage set(String key, String value) {
Copy link
Member

Choose a reason for hiding this comment

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

(Naïve question: must it be a WriteableBaggage implementation if it's just going to ignore this call anyway?)

Copy link
Member Author

Choose a reason for hiding this comment

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

In this NoOpTracer the inner classes Span and SpanContext have a baggage() method declared by the interfaces they implement to return a WriteableBaggage. Each of the baggage() methods returns theEMPTY_BAGGAGE constant so it needs to implement WriteableBaggage even though, as you point out, it doesn't really write anything.

Copy link
Member

@ljnelson ljnelson left a comment

Choose a reason for hiding this comment

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

Minor comments

@tjquinno tjquinno merged commit 0b16c29 into helidon-io:main Mar 4, 2024
12 checks passed
hrstoyanov pushed a commit to hrstoyanov/helidon that referenced this pull request Mar 12, 2024
… well as `Span`) (helidon-io#8320)

* Rebase with concurrent changes

* Remove an unneeded class

* Copyrights
hrstoyanov pushed a commit to hrstoyanov/helidon that referenced this pull request Mar 12, 2024
… well as `Span`) (helidon-io#8320)

* Rebase with concurrent changes

* Remove an unneeded class

* Copyrights
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
OCA Verified All contributors have signed the Oracle Contributor Agreement.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4.x Provide a way to retrieve telemetry baggage from a SpanContext without having to start a Span
2 participants