-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
feat(opentelemetry): Fix & align isolation scope usage in node-experimental #10570
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
Conversation
packages/core/src/tracing/trace.ts
Outdated
@@ -399,7 +399,8 @@ type SpanWithScopes = Span & { | |||
[ISOLATION_SCOPE_ON_START_SPAN_FIELD]?: Scope; | |||
}; | |||
|
|||
function setCapturedScopesOnSpan(span: Span | undefined, scope: Scope, isolationScope: Scope): void { | |||
/** Store the scope & isolation scope for a span, which can the be used when it is finished. */ | |||
export function setCapturedScopesOnSpan(span: Span | undefined, scope: Scope, isolationScope: Scope): void { |
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.
we need this in otel!
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.
h: We should just vendor this in the otel package. This function should not be exposed to the user.
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.
not sure, this is subject to break if we do that as that is not typed anywhere properly and relies purely on casting stuff around etc 😬 we need this to be the exact same implementation in node-experimental as here, as we need this to be picked up by getCaputredScopesOnSpan()
.
@@ -72,9 +72,6 @@ export function isInitialized(): boolean { | |||
|
|||
/** A fork of the classic scope with some otel specific stuff. */ | |||
export class Scope extends OpenTelemetryScope implements ScopeInterface { | |||
// Overwrite this if you want to use a specific isolation scope here | |||
public isolationScope: Scope | undefined; |
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.
we don't need this anymore 🚀
// so we store it on the span finish scope for later use | ||
const scope = getSpanFinishScope(span) as Scope | undefined; | ||
if (scope) { | ||
scope.isolationScope = getIsolationScope(); |
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 is simply handled in the base span processor now.
@@ -99,7 +99,7 @@ describe('Integration | Transactions', () => { | |||
environment: 'production', | |||
event_id: expect.any(String), | |||
platform: 'node', | |||
sdkProcessingMetadata: { | |||
sdkProcessingMetadata: expect.objectContaining({ |
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 has some more fields now, but we don't care about that.
@@ -329,6 +329,159 @@ describe('Integration | Transactions', () => { | |||
); | |||
}); | |||
|
|||
it('correctly creates concurrent transaction & spans when using native OTEL tracer', async () => { |
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.
added a test to actually check that it works with native otel tracing too.
@@ -30,7 +31,7 @@ export function setOpenTelemetryContextAsyncContextStrategy(): void { | |||
const ctx = api.context.active(); | |||
|
|||
// We depend on the otelContextManager to handle the context/hub | |||
return api.context.with(ctx, () => { | |||
return api.context.with(ctx.setValue(SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY, true), () => { |
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.
we use this attribute to tell the otel context manager if the isolation scope should be forked or not.
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.
Can we explain this a bit further in comments? I still don't fully get it.
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.
will do!
const carrier: Carrier = {}; | ||
ensureHubOnCarrier(carrier, parent); | ||
return getHubFromCarrier(carrier); | ||
function createNewHub(parent: Hub | undefined, shouldForkIsolationScope: boolean): Hub { |
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.
ensureHubOnCarrier
used to always fork the isolation scope, which is not what we want here. Now, we only fork this if withIsolationScope
is used.
@@ -81,7 +81,7 @@ export function wrapClientClass< | |||
delete hint.captureContext; | |||
} | |||
|
|||
return super._prepareEvent(event, hint, actualScope); | |||
return super._prepareEvent(event, hint, actualScope, isolationScope); |
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 was not correctly passed through here before, oops.
@@ -10,8 +10,8 @@ import { OpenTelemetryScope } from './scope'; | |||
* Exported only for testing | |||
*/ | |||
export class OpenTelemetryHub extends Hub { | |||
public constructor(client?: Client, scope: Scope = new OpenTelemetryScope()) { | |||
super(client, scope); | |||
public constructor(client?: Client, scope: Scope = new OpenTelemetryScope(), isolationScope?: Scope) { |
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.
we accept this in the upstream implementation too.
/** | ||
* This is a fork of the base Transaction with OTEL specific stuff added. | ||
*/ | ||
export class OpenTelemetryTransaction extends Transaction { |
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.
not needed anymore - we just leverage this in the core implementation through getCapturedScopesOnSpan
!
@@ -111,6 +120,7 @@ describe('Integration | Scope', () => { | |||
tag1: 'val1', | |||
tag2: 'val2', | |||
tag3: 'val3', | |||
tag4: 'val4', |
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 aligns the behavior with the core one, as Sentry.setTag()
sets on the isolation scope which will be applied to the transaction too.
size-limit report 📦
|
packages/core/src/tracing/trace.ts
Outdated
@@ -399,7 +399,8 @@ type SpanWithScopes = Span & { | |||
[ISOLATION_SCOPE_ON_START_SPAN_FIELD]?: Scope; | |||
}; | |||
|
|||
function setCapturedScopesOnSpan(span: Span | undefined, scope: Scope, isolationScope: Scope): void { | |||
/** Store the scope & isolation scope for a span, which can the be used when it is finished. */ | |||
export function setCapturedScopesOnSpan(span: Span | undefined, scope: Scope, isolationScope: Scope): void { |
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.
h: We should just vendor this in the otel package. This function should not be exposed to the user.
const SpanFinishScopes = new WeakMap< | ||
AbstractSpan, | ||
{ | ||
scope: Scope; | ||
isolationScope: Scope; | ||
} | ||
>(); |
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.
h: Do we really need this if the span has the scopes attached to it already? Also, how is this different from SpanScope
and setSpanScope
?
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 tricky thing here is that we use this here to "cache" the scopes on the OTEL spans, then we use setCapturedScopes()
to set them on the sentry span when we create it in the span exporter.
Once we have aligned our span types, we can probably use setCapturedScopes
here as well - but we'll still need to pick them up in the span exporter via getCapturedScopes
and set them again on the sentry span before we send it.
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.
we can probably get rid of getSpanScope
actually, I'll check it out!
@@ -30,7 +31,7 @@ export function setOpenTelemetryContextAsyncContextStrategy(): void { | |||
const ctx = api.context.active(); | |||
|
|||
// We depend on the otelContextManager to handle the context/hub | |||
return api.context.with(ctx, () => { | |||
return api.context.with(ctx.setValue(SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY, true), () => { |
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.
Can we explain this a bit further in comments? I still don't fully get it.
c9f8c44
to
09e9f6f
Compare
I took the liberty of vendoring the code to pass the scope and isolation scope for the transaction into the otel package. This turns it into an implementation detail instead of an API. I do respect your opinion though so I implemented it in a way so that it can be revisited any time in the future. Also, I will merge this PR because I need it for #10572 :) |
This PR aligns - and actually fixes - isolation scope usage in both
@sentry/opentelemetry
and@sentry/node-experimental
.Since we've added a lot of this in the main SDK now, we can get rid of a bunch of our special handling in the otel SDKs.
Also, I noticed that in opentelemetry (when not using node-experimental) the isolation scope was not properly used before - this is fixed now.
This allows to remove a bunch of stuff, like the custom transaction fork (scopes can be handled by
setCapturedScopesOnSpan()
natively).