Skip to content

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

Merged
merged 9 commits into from
Feb 9, 2024

Conversation

mydea
Copy link
Member

@mydea mydea commented Feb 8, 2024

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).

@mydea mydea requested review from lforst, Lms24 and AbhiPrasad February 8, 2024 12:16
@mydea mydea self-assigned this Feb 8, 2024
@@ -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 {
Copy link
Member Author

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!

Copy link
Contributor

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.

Copy link
Member Author

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;
Copy link
Member Author

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

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({
Copy link
Member Author

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 () => {
Copy link
Member Author

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), () => {
Copy link
Member Author

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.

Copy link
Contributor

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.

Copy link
Member Author

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 {
Copy link
Member Author

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

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) {
Copy link
Member Author

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 {
Copy link
Member Author

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',
Copy link
Member Author

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.

Copy link
Contributor

github-actions bot commented Feb 8, 2024

size-limit report 📦

Path Size
@sentry/browser (incl. Tracing, Replay, Feedback) - Webpack (gzipped) 78.16 KB (-0.22% 🔽)
@sentry/browser (incl. Tracing, Replay) - Webpack (gzipped) 69.37 KB (-0.29% 🔽)
@sentry/browser (incl. Tracing, Replay with Canvas) - Webpack (gzipped) 73.31 KB (-0.25% 🔽)
@sentry/browser (incl. Tracing, Replay) - Webpack with treeshaking flags (gzipped) 63.01 KB (-0.26% 🔽)
@sentry/browser (incl. Tracing) - Webpack (gzipped) 33.35 KB (-0.5% 🔽)
@sentry/browser (incl. browserTracingIntegration) - Webpack (gzipped) 33.22 KB (-0.51% 🔽)
@sentry/browser (incl. Feedback) - Webpack (gzipped) 31.2 KB (-0.54% 🔽)
@sentry/browser (incl. sendFeedback) - Webpack (gzipped) 31.21 KB (-0.53% 🔽)
@sentry/browser - Webpack (gzipped) 22.48 KB (-0.69% 🔽)
@sentry/browser (incl. Tracing, Replay, Feedback) - ES6 CDN Bundle (gzipped) 76.19 KB (-0.23% 🔽)
@sentry/browser (incl. Tracing, Replay) - ES6 CDN Bundle (gzipped) 67.71 KB (-0.28% 🔽)
@sentry/browser (incl. Tracing) - ES6 CDN Bundle (gzipped) 33.47 KB (-0.56% 🔽)
@sentry/browser - ES6 CDN Bundle (gzipped) 24.56 KB (-0.7% 🔽)
@sentry/browser (incl. Tracing, Replay) - ES6 CDN Bundle (minified & uncompressed) 213.68 KB (-0.19% 🔽)
@sentry/browser (incl. Tracing) - ES6 CDN Bundle (minified & uncompressed) 101.48 KB (-0.39% 🔽)
@sentry/browser - ES6 CDN Bundle (minified & uncompressed) 73.86 KB (-0.54% 🔽)
@sentry/browser (incl. Tracing) - ES5 CDN Bundle (gzipped) 36.64 KB (-0.44% 🔽)
@sentry/react (incl. Tracing, Replay) - Webpack (gzipped) 69.76 KB (-0.24% 🔽)
@sentry/react - Webpack (gzipped) 22.52 KB (-0.66% 🔽)
@sentry/nextjs Client (incl. Tracing, Replay) - Webpack (gzipped) 87.33 KB (-0.26% 🔽)
@sentry/nextjs Client - Webpack (gzipped) 51.52 KB (-0.41% 🔽)
@sentry-internal/feedback - Webpack (gzipped) 17.2 KB (-0.1% 🔽)

@@ -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 {
Copy link
Contributor

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.

Comment on lines 10 to 15
const SpanFinishScopes = new WeakMap<
AbstractSpan,
{
scope: Scope;
isolationScope: Scope;
}
>();
Copy link
Contributor

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?

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

Copy link
Member Author

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), () => {
Copy link
Contributor

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.

@mydea mydea force-pushed the fn/node-experimental-isolation-scope branch from c9f8c44 to 09e9f6f Compare February 8, 2024 14:57
@lforst
Copy link
Contributor

lforst commented Feb 9, 2024

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 :)

@lforst lforst merged commit 5a2b676 into develop Feb 9, 2024
@lforst lforst deleted the fn/node-experimental-isolation-scope branch February 9, 2024 10:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants