Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions packages/opentelemetry/src/asyncLocalStorageContextManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,16 @@
import type { Context, ContextManager } from '@opentelemetry/api';
import { ROOT_CONTEXT } from '@opentelemetry/api';
import { AsyncLocalStorage } from 'node:async_hooks';
import { EventEmitter } from 'node:events';
import type { AsyncLocalStorageLookup } from './contextManager';
import type { EventEmitter } from 'node:events';
import { SENTRY_SCOPES_CONTEXT_KEY } from './constants';
import { buildContextWithSentryScopes } from './utils/buildContextWithSentryScopes';
import { setIsSetup } from './utils/setupCheck';
import { getAsyncContextStrategy, getMainCarrier } from '@sentry/core';

export type AsyncLocalStorageLookup = {
asyncLocalStorage: AsyncLocalStorage<unknown>;
contextSymbol: symbol;
};
type ListenerFn = (...args: unknown[]) => unknown;

/**
Expand All @@ -50,13 +53,14 @@ export class SentryAsyncLocalStorageContextManager implements ContextManager {
private readonly _kOtListeners = Symbol('OtListeners');
private _wrapped = false;

public constructor() {
public constructor(asyncLocalStorage?: AsyncLocalStorage<Context>) {
setIsSetup('SentryContextManager');
// Pick the instance from the async context strategy
// this should normally always be there, but if it is not for whatever reason, we fall back to a new instance
this._asyncLocalStorage =
(getAsyncContextStrategy(getMainCarrier()).getTracingChannelBinding?.()
?.asyncLocalStorage as AsyncLocalStorage<Context>) ?? new AsyncLocalStorage<Context>();
?.asyncLocalStorage as AsyncLocalStorage<Context>) ??
(asyncLocalStorage || new AsyncLocalStorage());
}

public active(): Context {
Expand Down Expand Up @@ -84,7 +88,7 @@ export class SentryAsyncLocalStorageContextManager implements ContextManager {
}

public bind<T>(context: Context, target: T): T {
if (target instanceof EventEmitter) {
if (isEventEmitter(target)) {
return this._bindEventEmitter(context, target);
}
if (typeof target === 'function') {
Expand Down Expand Up @@ -218,3 +222,8 @@ export class SentryAsyncLocalStorageContextManager implements ContextManager {
return (ee as unknown as Record<symbol, PatchMap | undefined>)[this._kOtListeners];
}
}

// We use this instead of instanceof EventEmitter to make sure this also works in non-Node.js environments (e.g. vercel-edge)
function isEventEmitter(target: unknown): target is EventEmitter {
return typeof target === 'object' && target !== null && 'on' in target;
}
72 changes: 0 additions & 72 deletions packages/opentelemetry/src/contextManager.ts

This file was deleted.

3 changes: 0 additions & 3 deletions packages/opentelemetry/src/exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,6 @@ export { suppressTracing } from './utils/suppressTracing';

export { setupEventContextTrace } from './setupEventContextTrace';

// eslint-disable-next-line typescript/no-deprecated

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Public API removed without migration docs

Medium Severity

wrapContextManagerClass is removed from the public @sentry/opentelemetry surface. That is a breaking public API removal. It was marked deprecated, but MIGRATION.md does not list this removal under @sentry/opentelemetry, and the package README still tells users they can use wrapContextManagerClass for a custom base. Flagged because the PR review rules call out public API removals and breaking changes without proper notices.

Fix in Cursor Fix in Web

Triggered by project rule: PR Review Guidelines for Cursor Bot

Reviewed by Cursor Bugbot for commit 3f7d8fa. Configure here.

export { wrapContextManagerClass } from './contextManager';

export { SentryPropagator, shouldPropagateTraceForUrl } from './propagator';
export { SentrySpanProcessor } from './spanProcessor';
export { SentrySampler, wrapSamplingDecision } from './sampler';
Expand Down
3 changes: 1 addition & 2 deletions packages/opentelemetry/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
export * from './exports';

// Node-specific exports
export { SentryAsyncLocalStorageContextManager } from './asyncLocalStorageContextManager';
export type { AsyncLocalStorageLookup } from './contextManager';
export { SentryAsyncLocalStorageContextManager, type AsyncLocalStorageLookup } from './asyncLocalStorageContextManager';

// We export the node-specific variant here that uses async local storage
export { setNodeOpenTelemetryContextAsyncContextStrategy as setOpenTelemetryContextAsyncContextStrategy } from './nodeAsyncContextStrategy';
42 changes: 36 additions & 6 deletions packages/vercel-edge/src/sdk.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { Context } from '@opentelemetry/api';
import { context, diag, DiagLogLevel, propagation, trace } from '@opentelemetry/api';
import { BasicTracerProvider } from '@opentelemetry/sdk-trace-base';
import type { Client, Integration, Options } from '@sentry/core';
Expand All @@ -22,25 +23,30 @@ import {
enhanceDscWithOpenTelemetryRootSpanName,
getSentryResource,
openTelemetrySetupCheck,
SentryAsyncLocalStorageContextManager,
SentryPropagator,
SentrySampler,
SentrySpanProcessor,
setOpenTelemetryContextAsyncContextStrategy,
setupEventContextTrace,
wrapContextManagerClass,
} from '@sentry/opentelemetry';
import { VercelEdgeClient } from './client';
import { DEBUG_BUILD } from './debug-build';
import { winterCGFetchIntegration } from './integrations/wintercg-fetch';
import { makeEdgeTransport } from './transports';
import type { VercelEdgeOptions } from './types';
import { getVercelEnv } from './utils/vercel';
import { AsyncLocalStorageContextManager } from './vendored/async-local-storage-context-manager';

declare const process: {
env: Record<string, string>;
};

interface AsyncLocalStorage<T> {
getStore(): T | undefined;
run<R>(store: T, callback: (...args: unknown[]) => R, ...args: unknown[]): R;
disable(): void;
}

const nodeStackParser = createStackParser(nodeStackLineParser());

/** Get the default integrations for the browser SDK. */
Expand Down Expand Up @@ -166,16 +172,40 @@ export function setupOtel(client: VercelEdgeClient): void {
],
});

// eslint-disable-next-line typescript/no-deprecated
const SentryContextManager = wrapContextManagerClass(AsyncLocalStorageContextManager);

trace.setGlobalTracerProvider(provider);
propagation.setGlobalPropagator(new SentryPropagator());
context.setGlobalContextManager(new SentryContextManager());
context.setGlobalContextManager(new SentryAsyncLocalStorageContextManager(getAsyncLocalStorage()));
Comment thread
cursor[bot] marked this conversation as resolved.

client.traceProvider = provider;
}

function getAsyncLocalStorage(): AsyncLocalStorage<Context> {
const MaybeGlobalAsyncLocalStorageConstructor = (
GLOBAL_OBJ as { AsyncLocalStorage?: new () => AsyncLocalStorage<Context> }
).AsyncLocalStorage;

if (!MaybeGlobalAsyncLocalStorageConstructor) {
DEBUG_BUILD &&
debug.warn(
"Tried to register AsyncLocalStorage async context strategy in a runtime that doesn't support AsyncLocalStorage.",
);

return {
getStore() {
return undefined;
},
run<R>(_store: Context, callback: (...args: unknown[]) => R, ...args: unknown[]): R {
return callback.apply(this, args);
},
disable() {
// noop
},
};
}

return new MaybeGlobalAsyncLocalStorageConstructor();
}

/**
* Setup the OTEL logger to use our own debug logger.
*/
Expand Down
Loading
Loading