-
Notifications
You must be signed in to change notification settings - Fork 938
Issue 7869 - Support the new W3C random flag #8012
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,6 @@ | ||
| Comparing source compatibility of opentelemetry-api-1.59.0-SNAPSHOT.jar against opentelemetry-api-1.58.0.jar | ||
| No changes. | ||
| *** MODIFIED INTERFACE: PUBLIC ABSTRACT io.opentelemetry.api.trace.TraceFlags (not serializable) | ||
| === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 | ||
| +++ NEW METHOD: PUBLIC(+) boolean isTraceIdRandom() | ||
| +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.trace.TraceFlags withRandomTraceIdBit() | ||
| +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.trace.TraceFlags withSampledBit() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,4 @@ | ||
| Comparing source compatibility of opentelemetry-sdk-trace-1.59.0-SNAPSHOT.jar against opentelemetry-sdk-trace-1.58.0.jar | ||
| No changes. | ||
| *** MODIFIED INTERFACE: PUBLIC ABSTRACT io.opentelemetry.sdk.trace.IdGenerator (not serializable) | ||
| === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 | ||
| +++ NEW METHOD: PUBLIC(+) boolean generatesRandomTraceIds() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,8 +16,10 @@ | |
| import io.opentelemetry.api.trace.Span; | ||
| import io.opentelemetry.api.trace.SpanBuilder; | ||
| import io.opentelemetry.api.trace.SpanContext; | ||
| import io.opentelemetry.api.trace.SpanId; | ||
| import io.opentelemetry.api.trace.SpanKind; | ||
| import io.opentelemetry.api.trace.TraceFlags; | ||
| import io.opentelemetry.api.trace.TraceId; | ||
| import io.opentelemetry.api.trace.TraceState; | ||
| import io.opentelemetry.context.Context; | ||
| import io.opentelemetry.sdk.common.InstrumentationScopeInfo; | ||
|
|
@@ -39,6 +41,7 @@ class SdkSpanBuilder implements SpanBuilder { | |
| private final InstrumentationScopeInfo instrumentationScopeInfo; | ||
| private final TracerSharedState tracerSharedState; | ||
| private final SpanLimits spanLimits; | ||
| private final Context rootContextWithRandomTraceIdBit; | ||
|
|
||
| @Nullable private Context parent; // null means: Use current context. | ||
| private SpanKind spanKind = SpanKind.INTERNAL; | ||
|
|
@@ -56,6 +59,23 @@ class SdkSpanBuilder implements SpanBuilder { | |
| this.instrumentationScopeInfo = instrumentationScopeInfo; | ||
| this.tracerSharedState = tracerSharedState; | ||
| this.spanLimits = spanLimits; | ||
| this.rootContextWithRandomTraceIdBit = | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I'm reading this right, I've sketched this (and my other suggestions) out in this commit to improve clarity: c77bad6bb
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, it should be static - thanks! |
||
| preparePrimordialContext( | ||
| Context.root(), | ||
| TraceFlags.getDefault().withRandomTraceIdBit(), | ||
| TraceState.getDefault()); | ||
| } | ||
|
|
||
| /* | ||
| * A primordial context can be passed as the parent context for a root span | ||
| * if a non-default TraceFlags or TraceState need to be passed to the sampler | ||
| */ | ||
| private static Context preparePrimordialContext( | ||
| Context parentContext, TraceFlags traceFlags, TraceState traceState) { | ||
|
Comment on lines
+69
to
+74
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see this is private. I'm aware of an underspecified region in the OTel specification. Should there be a specified way to create these "primordial" contexts with control over the root trace state? In the Go SDK, asking for a new root gets you an empty context: https://github.com/open-telemetry/opentelemetry-go/blob/8d3b4cb2501dec9f1c5373123e425f109c43b8d2/sdk/trace/tracer.go#L92 It's not clear whether users are able to setup a context with control over TraceState at the root. We write:
It doesn't actually require SDKs provide a way to set explicit randomness for the root span, it just refers to the potential scenario.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I was thinking something similar. Specifically, its weird that the primordial context always has the random trace bit set to true, even if paired with an IdGenerator which does not generate random trace ids.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In theory, the "primordial" context can have non-empty TraceFlags, and non-empty TraceState (which we do not use yet), while still remaining "invalid", forcing the "child" span to be root. Hence I opted for passing both TraceFlags and TraceState as arguments for this method, just to emphasize this point. It is not clear to me, if the IdGenerator can be changed dynamically, but if it can, I believe this case will be handled properly (i.e. the primordial context to use will have the RandomTraceId on or off, depending on the IdGenerator). If a non-random IdGenerator is use, the RandomTraceId flad will not be set. |
||
| SpanContext spanContext = | ||
| SpanContext.create(TraceId.getInvalid(), SpanId.getInvalid(), traceFlags, traceState); | ||
| Span span = Span.wrap(spanContext); | ||
| return span.storeInContext(parentContext); | ||
| } | ||
|
|
||
| @Override | ||
|
|
@@ -170,14 +190,25 @@ public Span startSpan() { | |
| Span parentSpan = Span.fromContext(parentContext); | ||
| SpanContext parentSpanContext = parentSpan.getSpanContext(); | ||
| String traceId; | ||
| boolean isTraceIdRandom; | ||
| IdGenerator idGenerator = tracerSharedState.getIdGenerator(); | ||
| String spanId = idGenerator.generateSpanId(); | ||
|
|
||
| Context parentContextForSampler = parentContext; | ||
| if (!parentSpanContext.isValid()) { | ||
| // New root span. | ||
| traceId = idGenerator.generateTraceId(); | ||
| if (idGenerator.generatesRandomTraceIds()) { | ||
| isTraceIdRandom = true; | ||
| // Replace parentContext for sampling with one with RANDOM_TRACE_ID bit set | ||
| parentContextForSampler = rootContextWithRandomTraceIdBit; | ||
| } else { | ||
| isTraceIdRandom = false; | ||
| } | ||
| } else { | ||
| // New child span. | ||
| traceId = parentSpanContext.getTraceId(); | ||
| isTraceIdRandom = parentSpanContext.getTraceFlags().isTraceIdRandom(); | ||
| } | ||
| List<LinkData> currentLinks = links; | ||
| List<LinkData> immutableLinks = | ||
|
|
@@ -190,7 +221,12 @@ public Span startSpan() { | |
| tracerSharedState | ||
| .getSampler() | ||
| .shouldSample( | ||
| parentContext, traceId, spanName, spanKind, immutableAttributes, immutableLinks); | ||
| parentContextForSampler, | ||
| traceId, | ||
| spanName, | ||
| spanKind, | ||
| immutableAttributes, | ||
| immutableLinks); | ||
| SamplingDecision samplingDecision = samplingResult.getDecision(); | ||
|
|
||
| TraceState samplingResultTraceState = | ||
|
|
@@ -199,7 +235,7 @@ public Span startSpan() { | |
| ImmutableSpanContext.create( | ||
| traceId, | ||
| spanId, | ||
| isSampled(samplingDecision) ? TraceFlags.getSampled() : TraceFlags.getDefault(), | ||
| newTraceFlags(isTraceIdRandom, isSampled(samplingDecision)), | ||
jack-berg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| samplingResultTraceState, | ||
| /* remote= */ false, | ||
| tracerSharedState.isIdGeneratorSafeToSkipIdValidation()); | ||
|
|
@@ -239,6 +275,17 @@ public Span startSpan() { | |
| recordEndSpanMetrics); | ||
| } | ||
|
|
||
| private static TraceFlags newTraceFlags(boolean randomTraceId, boolean sampled) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This reads as a near copy of the |
||
| TraceFlags traceFlags = TraceFlags.getDefault(); | ||
| if (randomTraceId) { | ||
| traceFlags = traceFlags.withRandomTraceIdBit(); | ||
| } | ||
| if (sampled) { | ||
| traceFlags = traceFlags.withSampledBit(); | ||
| } | ||
| return traceFlags; | ||
| } | ||
|
|
||
| private AttributesMap attributes() { | ||
| AttributesMap attributes = this.attributes; | ||
| if (attributes == 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.
Taking another look at this, I think these need to be static helpers with signatures of the form
static TraceFlags with{Param}(TraceFlags traceFlags, boolean {param}) { ...}.The instance levels make for nice UX, but are overridable, which I believe is never needed and would lead to bad / hard-to-debug behavior.
Uh oh!
There was an error while loading. Please reload this page.
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 do not want them to be overridden. I definitely considered the static methods, and ultimately opted out of them: the usage is less convenient, and even though TraceFlags is an interface, it is really tightly coupled with ImmutableTraceFlags (see fromHex(), and fromByte()), so in practice developing a different implementation would be very inconvenient, if not impossible.
Another thing is about being able to reset some bits. I consciously did not provide this functionality. W3C Trace Context specification effectively says that any set bit from the upstream TraceFlags has to be cleared if the current implementation does not recognize it. I read this as a requirement to always build TraceFlags from scratch. Providing the capability to take (potentially unknown) TraceFlags and clear selected bits could confuse the users into thinking that they can do this with incoming TraceFlags.