@sentry/server-utils
Advanced tools
| Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); | ||
| const core = require('@sentry/core'); | ||
| const debugBuild = require('./debug-build.js'); | ||
| const NOOP = () => { | ||
| }; | ||
| function bindTracingChannelToSpan(channel, getSpan, opts) { | ||
| const handle = bindSpanToChannelStore(channel, getSpan); | ||
| const beforeSpanEnd = opts?.beforeSpanEnd; | ||
| const getErrorHint = (e) => { | ||
| if (typeof opts?.captureError === "function") { | ||
| return opts.captureError(e); | ||
| } | ||
| return { | ||
| mechanism: { | ||
| type: "auto.diagnostic_channels.bind_span", | ||
| handled: false | ||
| } | ||
| }; | ||
| }; | ||
| const subscribers = { | ||
| start: NOOP, | ||
| asyncStart: NOOP, | ||
| end(data) { | ||
| if ("error" in data || "result" in data) { | ||
| endBoundSpan(data, beforeSpanEnd); | ||
| } | ||
| }, | ||
| error(data) { | ||
| const span = data._sentrySpan; | ||
| if (!span) { | ||
| return; | ||
| } | ||
| if (opts?.captureError) { | ||
| core.captureException(data.error, getErrorHint(data.error)); | ||
| } | ||
| span.setStatus({ code: core.SPAN_STATUS_ERROR, message: getErrorMessage(data.error) }); | ||
| }, | ||
| asyncEnd(data) { | ||
| endBoundSpan(data, beforeSpanEnd); | ||
| } | ||
| }; | ||
| handle.channel.subscribe(subscribers); | ||
| return { | ||
| channel: handle.channel, | ||
| unbind: () => { | ||
| handle.channel.unsubscribe(subscribers); | ||
| handle.unbind(); | ||
| } | ||
| }; | ||
| } | ||
| function bindSpanToChannelStore(channel, getSpan) { | ||
| const binding = core._INTERNAL_getTracingChannelBinding(); | ||
| if (!binding) { | ||
| debugBuild.DEBUG_BUILD && core.debug.log("[TracingChannel] Could not access async context binding."); | ||
| return { | ||
| channel, | ||
| unbind: NOOP | ||
| }; | ||
| } | ||
| const asyncLocalStorage = binding.asyncLocalStorage; | ||
| channel.start.bindStore(asyncLocalStorage, (data) => { | ||
| const span = getSpan(data); | ||
| if (!span) { | ||
| return asyncLocalStorage.getStore(); | ||
| } | ||
| data._sentrySpan = span; | ||
| return binding.getStoreWithActiveSpan(span); | ||
| }); | ||
| return { | ||
| channel, | ||
| unbind: () => { | ||
| channel.start.unbindStore(asyncLocalStorage); | ||
| } | ||
| }; | ||
| } | ||
| function endBoundSpan(data, beforeSpanEnd) { | ||
| const span = data._sentrySpan; | ||
| if (!span) { | ||
| return; | ||
| } | ||
| beforeSpanEnd?.(span, data); | ||
| span.end(); | ||
| } | ||
| function getErrorMessage(error) { | ||
| if (error && typeof error === "object" && "message" in error && typeof error.message === "string") { | ||
| return error.message; | ||
| } | ||
| return String(error); | ||
| } | ||
| exports.bindTracingChannelToSpan = bindTracingChannelToSpan; | ||
| //# sourceMappingURL=tracing-channel.js.map |
| {"version":3,"file":"tracing-channel.js","sources":["../../src/tracing-channel.ts"],"sourcesContent":["import type { TracingChannel, TracingChannelSubscribers } from 'node:diagnostics_channel';\nimport type { AsyncLocalStorage } from 'node:async_hooks';\nimport type { ExclusiveEventHintOrCaptureContext, Span } from '@sentry/core';\nimport { _INTERNAL_getTracingChannelBinding, debug, captureException, SPAN_STATUS_ERROR } from '@sentry/core';\nimport { DEBUG_BUILD } from './debug-build';\n\nexport type TracingChannelPayloadWithSpan<TData extends object> = TData & {\n _sentrySpan?: Span;\n};\n\n/*\n * A type patch so that we don't have to handle all subscription types.\n */\nexport interface SentryTracingChannel<TData extends object = object> extends Omit<\n TracingChannel<TData, TracingChannelPayloadWithSpan<TData>>,\n 'subscribe' | 'unsubscribe'\n> {\n subscribe(subscribers: Partial<TracingChannelSubscribers<TracingChannelPayloadWithSpan<TData>>>): void;\n unsubscribe(subscribers: Partial<TracingChannelSubscribers<TracingChannelPayloadWithSpan<TData>>>): void;\n}\n\nexport interface TracingChannelLifeCycleOptions<TData extends object = object> {\n /**\n * Invoked with the span and the channel context object once the traced operation completes\n * Use it to enrich the span from the result/error (branch on `'error' in data` / `'result' in data`) or to run cleanup.\n */\n beforeSpanEnd?: (span: Span, data: TracingChannelPayloadWithSpan<TData>) => void;\n\n /**\n * Whether a thrown error is captured as a Sentry event. The span is always marked with error status regardless. Defaults to `false`.\n * You can alternatively pass a function that sets the ExclusiveEventHintOrCaptureContext on the captured error.\n * Set `true` for instrumentations that own the error boundary, (e.g: route handlers)\n * For database drivers, it is not recommended to set this at all.\n */\n captureError?: boolean | ((e: unknown) => ExclusiveEventHintOrCaptureContext);\n}\n\n/** Returned by {@link bindTracingChannelToSpan}: the bound channel plus a teardown handle. */\nexport interface TracingChannelBindingHandle<TData extends object = object> {\n /**\n * The tracing channel with the span bound into async context.\n */\n channel: SentryTracingChannel<TData>;\n\n /**\n * Tears down the binding: unsubscribes lifecycle handlers, when present, and unbinds the start store.\n * Idempotent, and a no-op when no async context binding was available.\n */\n unbind: () => void;\n}\n\nconst NOOP = (): void => {};\n\n/**\n * Bind a span and its lifecycle to a tracing channel so the span becomes the active async context\n * for the traced operation and is ended when the operation completes.\n *\n * `getSpan` may return `undefined` to opt a payload out entirely: nothing is bound, no span is\n * tracked, and the active context is left untouched. Use it for events that ride the same channel\n * but should reuse the enclosing span instead of opening (and ending) their own — e.g. an agent\n * loop's per-step events, where ending a freshly opened span would close the parent prematurely.\n */\nexport function bindTracingChannelToSpan<TData extends object>(\n channel: TracingChannel<TData, TData>,\n getSpan: (data: TracingChannelPayloadWithSpan<TData>) => Span | undefined,\n opts?: TracingChannelLifeCycleOptions<TData>,\n): TracingChannelBindingHandle<TData> {\n const handle = bindSpanToChannelStore(channel, getSpan);\n\n const beforeSpanEnd = opts?.beforeSpanEnd;\n const getErrorHint = (e: unknown): ExclusiveEventHintOrCaptureContext => {\n if (typeof opts?.captureError === 'function') {\n return opts.captureError(e);\n }\n\n return {\n mechanism: {\n type: 'auto.diagnostic_channels.bind_span',\n handled: false,\n },\n };\n };\n\n const subscribers: Partial<TracingChannelSubscribers<TracingChannelPayloadWithSpan<TData>>> = {\n start: NOOP,\n asyncStart: NOOP,\n end(data) {\n // The operation settled synchronously (returned or threw)\n // Presence checks because caller can return `undefined` result or throw a falsy value.\n if ('error' in data || 'result' in data) {\n endBoundSpan(data, beforeSpanEnd);\n }\n },\n error(data) {\n // No span was bound for this payload (`getSpan` returned undefined), so there is nothing to\n // annotate and no instrumentation that owns capturing this error.\n const span = data._sentrySpan;\n if (!span) {\n return;\n }\n\n if (opts?.captureError) {\n captureException(data.error, getErrorHint(data.error));\n }\n\n span.setStatus({ code: SPAN_STATUS_ERROR, message: getErrorMessage(data.error) });\n },\n asyncEnd(data) {\n endBoundSpan(data, beforeSpanEnd);\n },\n };\n\n handle.channel.subscribe(subscribers);\n\n return {\n channel: handle.channel,\n unbind: () => {\n handle.channel.unsubscribe(subscribers);\n handle.unbind();\n },\n };\n}\n\n/**\n * Bind a span into the channel's async context so it becomes active for the traced operation,\n * without managing its lifecycle. The primitive behind {@link bindTracingChannelToSpan}, which\n * layers span-ending and error handling on top.\n *\n * `getSpan` may return `undefined` to leave the active context untouched for that payload.\n */\nfunction bindSpanToChannelStore<TData extends object>(\n channel: TracingChannel<TData, TData>,\n getSpan: (data: TracingChannelPayloadWithSpan<TData>) => Span | undefined,\n): TracingChannelBindingHandle<TData> {\n // Grabs the tracing channel binding defined by the AsyncContext strategy implementation\n const binding = _INTERNAL_getTracingChannelBinding();\n\n // If no binding, then either the implementer doesn't support tracing channels or there is no active strategy\n // Failure mode here means we would still access the channel and potentially subscribe to it, but parenting will be off.\n if (!binding) {\n DEBUG_BUILD && debug.log('[TracingChannel] Could not access async context binding.');\n\n return {\n channel,\n unbind: NOOP,\n };\n }\n\n // Grab the ALS instance, we don't really care what is in it as long as the AsyncContext strategy can use its value to figure out parenting.\n const asyncLocalStorage = binding.asyncLocalStorage as AsyncLocalStorage<TData>;\n\n // bindStore activates the ALS for the traced call; any getStore() inside it returns the value bound for that context.\n // 1. Produce: getStoreWithActiveSpan(span) clones the current scope, plants the span via _INTERNAL_setSpanForScope, and returns { scope, isolationScope }, the active context carrying our span.\n // 2. Bind: the courier hands that opaque value to channel.start.bindStore(asyncLocalStorage, producer), which runs the traced op inside asyncLocalStorage.run(value, …); it never inspects the value.\n // 3. Read: inside the op, Sentry's scope machinery calls getScopes() → asyncStorage.getStore() on that same ALS, so getCurrentScope/getIsolationScope/getActiveSpan resolve to the scope carrying our span.\n // 4. Nest: any child span started in the traced op parents to that active span.\n channel.start.bindStore(asyncLocalStorage, (data: TracingChannelPayloadWithSpan<TData>) => {\n const span = getSpan(data);\n if (!span) {\n // Leave the active context untouched so nested operations keep parenting to the enclosing span.\n return asyncLocalStorage.getStore() as TData;\n }\n data._sentrySpan = span;\n\n return binding.getStoreWithActiveSpan(span) as TData;\n });\n\n return {\n channel,\n unbind: () => {\n // Removes the store\n channel.start.unbindStore(asyncLocalStorage);\n },\n };\n}\n\nfunction endBoundSpan<TData extends object>(\n data: TracingChannelPayloadWithSpan<TData>,\n beforeSpanEnd: TracingChannelLifeCycleOptions<TData>['beforeSpanEnd'],\n): void {\n const span = data._sentrySpan;\n if (!span) {\n return;\n }\n beforeSpanEnd?.(span, data);\n span.end();\n}\n\n/** Best-effort short message for a span status: an error-like's `message`, otherwise its string form. */\nfunction getErrorMessage(error: unknown): string {\n if (error && typeof error === 'object' && 'message' in error && typeof error.message === 'string') {\n return error.message;\n }\n return String(error);\n}\n"],"names":["captureException","SPAN_STATUS_ERROR","_INTERNAL_getTracingChannelBinding","DEBUG_BUILD","debug"],"mappings":"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"} |
| import { _INTERNAL_getTracingChannelBinding, debug, captureException, SPAN_STATUS_ERROR } from '@sentry/core'; | ||
| import { DEBUG_BUILD } from './debug-build.js'; | ||
| const NOOP = () => { | ||
| }; | ||
| function bindTracingChannelToSpan(channel, getSpan, opts) { | ||
| const handle = bindSpanToChannelStore(channel, getSpan); | ||
| const beforeSpanEnd = opts?.beforeSpanEnd; | ||
| const getErrorHint = (e) => { | ||
| if (typeof opts?.captureError === "function") { | ||
| return opts.captureError(e); | ||
| } | ||
| return { | ||
| mechanism: { | ||
| type: "auto.diagnostic_channels.bind_span", | ||
| handled: false | ||
| } | ||
| }; | ||
| }; | ||
| const subscribers = { | ||
| start: NOOP, | ||
| asyncStart: NOOP, | ||
| end(data) { | ||
| if ("error" in data || "result" in data) { | ||
| endBoundSpan(data, beforeSpanEnd); | ||
| } | ||
| }, | ||
| error(data) { | ||
| const span = data._sentrySpan; | ||
| if (!span) { | ||
| return; | ||
| } | ||
| if (opts?.captureError) { | ||
| captureException(data.error, getErrorHint(data.error)); | ||
| } | ||
| span.setStatus({ code: SPAN_STATUS_ERROR, message: getErrorMessage(data.error) }); | ||
| }, | ||
| asyncEnd(data) { | ||
| endBoundSpan(data, beforeSpanEnd); | ||
| } | ||
| }; | ||
| handle.channel.subscribe(subscribers); | ||
| return { | ||
| channel: handle.channel, | ||
| unbind: () => { | ||
| handle.channel.unsubscribe(subscribers); | ||
| handle.unbind(); | ||
| } | ||
| }; | ||
| } | ||
| function bindSpanToChannelStore(channel, getSpan) { | ||
| const binding = _INTERNAL_getTracingChannelBinding(); | ||
| if (!binding) { | ||
| DEBUG_BUILD && debug.log("[TracingChannel] Could not access async context binding."); | ||
| return { | ||
| channel, | ||
| unbind: NOOP | ||
| }; | ||
| } | ||
| const asyncLocalStorage = binding.asyncLocalStorage; | ||
| channel.start.bindStore(asyncLocalStorage, (data) => { | ||
| const span = getSpan(data); | ||
| if (!span) { | ||
| return asyncLocalStorage.getStore(); | ||
| } | ||
| data._sentrySpan = span; | ||
| return binding.getStoreWithActiveSpan(span); | ||
| }); | ||
| return { | ||
| channel, | ||
| unbind: () => { | ||
| channel.start.unbindStore(asyncLocalStorage); | ||
| } | ||
| }; | ||
| } | ||
| function endBoundSpan(data, beforeSpanEnd) { | ||
| const span = data._sentrySpan; | ||
| if (!span) { | ||
| return; | ||
| } | ||
| beforeSpanEnd?.(span, data); | ||
| span.end(); | ||
| } | ||
| function getErrorMessage(error) { | ||
| if (error && typeof error === "object" && "message" in error && typeof error.message === "string") { | ||
| return error.message; | ||
| } | ||
| return String(error); | ||
| } | ||
| export { bindTracingChannelToSpan }; | ||
| //# sourceMappingURL=tracing-channel.js.map |
| {"version":3,"file":"tracing-channel.js","sources":["../../src/tracing-channel.ts"],"sourcesContent":["import type { TracingChannel, TracingChannelSubscribers } from 'node:diagnostics_channel';\nimport type { AsyncLocalStorage } from 'node:async_hooks';\nimport type { ExclusiveEventHintOrCaptureContext, Span } from '@sentry/core';\nimport { _INTERNAL_getTracingChannelBinding, debug, captureException, SPAN_STATUS_ERROR } from '@sentry/core';\nimport { DEBUG_BUILD } from './debug-build';\n\nexport type TracingChannelPayloadWithSpan<TData extends object> = TData & {\n _sentrySpan?: Span;\n};\n\n/*\n * A type patch so that we don't have to handle all subscription types.\n */\nexport interface SentryTracingChannel<TData extends object = object> extends Omit<\n TracingChannel<TData, TracingChannelPayloadWithSpan<TData>>,\n 'subscribe' | 'unsubscribe'\n> {\n subscribe(subscribers: Partial<TracingChannelSubscribers<TracingChannelPayloadWithSpan<TData>>>): void;\n unsubscribe(subscribers: Partial<TracingChannelSubscribers<TracingChannelPayloadWithSpan<TData>>>): void;\n}\n\nexport interface TracingChannelLifeCycleOptions<TData extends object = object> {\n /**\n * Invoked with the span and the channel context object once the traced operation completes\n * Use it to enrich the span from the result/error (branch on `'error' in data` / `'result' in data`) or to run cleanup.\n */\n beforeSpanEnd?: (span: Span, data: TracingChannelPayloadWithSpan<TData>) => void;\n\n /**\n * Whether a thrown error is captured as a Sentry event. The span is always marked with error status regardless. Defaults to `false`.\n * You can alternatively pass a function that sets the ExclusiveEventHintOrCaptureContext on the captured error.\n * Set `true` for instrumentations that own the error boundary, (e.g: route handlers)\n * For database drivers, it is not recommended to set this at all.\n */\n captureError?: boolean | ((e: unknown) => ExclusiveEventHintOrCaptureContext);\n}\n\n/** Returned by {@link bindTracingChannelToSpan}: the bound channel plus a teardown handle. */\nexport interface TracingChannelBindingHandle<TData extends object = object> {\n /**\n * The tracing channel with the span bound into async context.\n */\n channel: SentryTracingChannel<TData>;\n\n /**\n * Tears down the binding: unsubscribes lifecycle handlers, when present, and unbinds the start store.\n * Idempotent, and a no-op when no async context binding was available.\n */\n unbind: () => void;\n}\n\nconst NOOP = (): void => {};\n\n/**\n * Bind a span and its lifecycle to a tracing channel so the span becomes the active async context\n * for the traced operation and is ended when the operation completes.\n *\n * `getSpan` may return `undefined` to opt a payload out entirely: nothing is bound, no span is\n * tracked, and the active context is left untouched. Use it for events that ride the same channel\n * but should reuse the enclosing span instead of opening (and ending) their own — e.g. an agent\n * loop's per-step events, where ending a freshly opened span would close the parent prematurely.\n */\nexport function bindTracingChannelToSpan<TData extends object>(\n channel: TracingChannel<TData, TData>,\n getSpan: (data: TracingChannelPayloadWithSpan<TData>) => Span | undefined,\n opts?: TracingChannelLifeCycleOptions<TData>,\n): TracingChannelBindingHandle<TData> {\n const handle = bindSpanToChannelStore(channel, getSpan);\n\n const beforeSpanEnd = opts?.beforeSpanEnd;\n const getErrorHint = (e: unknown): ExclusiveEventHintOrCaptureContext => {\n if (typeof opts?.captureError === 'function') {\n return opts.captureError(e);\n }\n\n return {\n mechanism: {\n type: 'auto.diagnostic_channels.bind_span',\n handled: false,\n },\n };\n };\n\n const subscribers: Partial<TracingChannelSubscribers<TracingChannelPayloadWithSpan<TData>>> = {\n start: NOOP,\n asyncStart: NOOP,\n end(data) {\n // The operation settled synchronously (returned or threw)\n // Presence checks because caller can return `undefined` result or throw a falsy value.\n if ('error' in data || 'result' in data) {\n endBoundSpan(data, beforeSpanEnd);\n }\n },\n error(data) {\n // No span was bound for this payload (`getSpan` returned undefined), so there is nothing to\n // annotate and no instrumentation that owns capturing this error.\n const span = data._sentrySpan;\n if (!span) {\n return;\n }\n\n if (opts?.captureError) {\n captureException(data.error, getErrorHint(data.error));\n }\n\n span.setStatus({ code: SPAN_STATUS_ERROR, message: getErrorMessage(data.error) });\n },\n asyncEnd(data) {\n endBoundSpan(data, beforeSpanEnd);\n },\n };\n\n handle.channel.subscribe(subscribers);\n\n return {\n channel: handle.channel,\n unbind: () => {\n handle.channel.unsubscribe(subscribers);\n handle.unbind();\n },\n };\n}\n\n/**\n * Bind a span into the channel's async context so it becomes active for the traced operation,\n * without managing its lifecycle. The primitive behind {@link bindTracingChannelToSpan}, which\n * layers span-ending and error handling on top.\n *\n * `getSpan` may return `undefined` to leave the active context untouched for that payload.\n */\nfunction bindSpanToChannelStore<TData extends object>(\n channel: TracingChannel<TData, TData>,\n getSpan: (data: TracingChannelPayloadWithSpan<TData>) => Span | undefined,\n): TracingChannelBindingHandle<TData> {\n // Grabs the tracing channel binding defined by the AsyncContext strategy implementation\n const binding = _INTERNAL_getTracingChannelBinding();\n\n // If no binding, then either the implementer doesn't support tracing channels or there is no active strategy\n // Failure mode here means we would still access the channel and potentially subscribe to it, but parenting will be off.\n if (!binding) {\n DEBUG_BUILD && debug.log('[TracingChannel] Could not access async context binding.');\n\n return {\n channel,\n unbind: NOOP,\n };\n }\n\n // Grab the ALS instance, we don't really care what is in it as long as the AsyncContext strategy can use its value to figure out parenting.\n const asyncLocalStorage = binding.asyncLocalStorage as AsyncLocalStorage<TData>;\n\n // bindStore activates the ALS for the traced call; any getStore() inside it returns the value bound for that context.\n // 1. Produce: getStoreWithActiveSpan(span) clones the current scope, plants the span via _INTERNAL_setSpanForScope, and returns { scope, isolationScope }, the active context carrying our span.\n // 2. Bind: the courier hands that opaque value to channel.start.bindStore(asyncLocalStorage, producer), which runs the traced op inside asyncLocalStorage.run(value, …); it never inspects the value.\n // 3. Read: inside the op, Sentry's scope machinery calls getScopes() → asyncStorage.getStore() on that same ALS, so getCurrentScope/getIsolationScope/getActiveSpan resolve to the scope carrying our span.\n // 4. Nest: any child span started in the traced op parents to that active span.\n channel.start.bindStore(asyncLocalStorage, (data: TracingChannelPayloadWithSpan<TData>) => {\n const span = getSpan(data);\n if (!span) {\n // Leave the active context untouched so nested operations keep parenting to the enclosing span.\n return asyncLocalStorage.getStore() as TData;\n }\n data._sentrySpan = span;\n\n return binding.getStoreWithActiveSpan(span) as TData;\n });\n\n return {\n channel,\n unbind: () => {\n // Removes the store\n channel.start.unbindStore(asyncLocalStorage);\n },\n };\n}\n\nfunction endBoundSpan<TData extends object>(\n data: TracingChannelPayloadWithSpan<TData>,\n beforeSpanEnd: TracingChannelLifeCycleOptions<TData>['beforeSpanEnd'],\n): void {\n const span = data._sentrySpan;\n if (!span) {\n return;\n }\n beforeSpanEnd?.(span, data);\n span.end();\n}\n\n/** Best-effort short message for a span status: an error-like's `message`, otherwise its string form. */\nfunction getErrorMessage(error: unknown): string {\n if (error && typeof error === 'object' && 'message' in error && typeof error.message === 'string') {\n return error.message;\n }\n return String(error);\n}\n"],"names":[],"mappings":"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"} |
| import { TracingChannel, TracingChannelSubscribers } from 'node:diagnostics_channel'; | ||
| import { ExclusiveEventHintOrCaptureContext, Span } from '@sentry/core'; | ||
| export type TracingChannelPayloadWithSpan<TData extends object> = TData & { | ||
| _sentrySpan?: Span; | ||
| }; | ||
| export interface SentryTracingChannel<TData extends object = object> extends Pick<TracingChannel<TData, TracingChannelPayloadWithSpan<TData>>, Exclude<keyof TracingChannel<TData, TracingChannelPayloadWithSpan<TData>>, 'subscribe' | 'unsubscribe'>> { | ||
| subscribe(subscribers: Partial<TracingChannelSubscribers<TracingChannelPayloadWithSpan<TData>>>): void; | ||
| unsubscribe(subscribers: Partial<TracingChannelSubscribers<TracingChannelPayloadWithSpan<TData>>>): void; | ||
| } | ||
| export interface TracingChannelLifeCycleOptions<TData extends object = object> { | ||
| /** | ||
| * Invoked with the span and the channel context object once the traced operation completes | ||
| * Use it to enrich the span from the result/error (branch on `'error' in data` / `'result' in data`) or to run cleanup. | ||
| */ | ||
| beforeSpanEnd?: (span: Span, data: TracingChannelPayloadWithSpan<TData>) => void; | ||
| /** | ||
| * Whether a thrown error is captured as a Sentry event. The span is always marked with error status regardless. Defaults to `false`. | ||
| * You can alternatively pass a function that sets the ExclusiveEventHintOrCaptureContext on the captured error. | ||
| * Set `true` for instrumentations that own the error boundary, (e.g: route handlers) | ||
| * For database drivers, it is not recommended to set this at all. | ||
| */ | ||
| captureError?: boolean | ((e: unknown) => ExclusiveEventHintOrCaptureContext); | ||
| } | ||
| /** Returned by {@link bindTracingChannelToSpan}: the bound channel plus a teardown handle. */ | ||
| export interface TracingChannelBindingHandle<TData extends object = object> { | ||
| /** | ||
| * The tracing channel with the span bound into async context. | ||
| */ | ||
| channel: SentryTracingChannel<TData>; | ||
| /** | ||
| * Tears down the binding: unsubscribes lifecycle handlers, when present, and unbinds the start store. | ||
| * Idempotent, and a no-op when no async context binding was available. | ||
| */ | ||
| unbind: () => void; | ||
| } | ||
| /** | ||
| * Bind a span and its lifecycle to a tracing channel so the span becomes the active async context | ||
| * for the traced operation and is ended when the operation completes. | ||
| * | ||
| * `getSpan` may return `undefined` to opt a payload out entirely: nothing is bound, no span is | ||
| * tracked, and the active context is left untouched. Use it for events that ride the same channel | ||
| * but should reuse the enclosing span instead of opening (and ending) their own — e.g. an agent | ||
| * loop's per-step events, where ending a freshly opened span would close the parent prematurely. | ||
| */ | ||
| export declare function bindTracingChannelToSpan<TData extends object>(channel: TracingChannel<TData, TData>, getSpan: (data: TracingChannelPayloadWithSpan<TData>) => Span | undefined, opts?: TracingChannelLifeCycleOptions<TData>): TracingChannelBindingHandle<TData>; | ||
| //# sourceMappingURL=tracing-channel.d.ts.map |
| import type { TracingChannel, TracingChannelSubscribers } from 'node:diagnostics_channel'; | ||
| import type { ExclusiveEventHintOrCaptureContext, Span } from '@sentry/core'; | ||
| export type TracingChannelPayloadWithSpan<TData extends object> = TData & { | ||
| _sentrySpan?: Span; | ||
| }; | ||
| export interface SentryTracingChannel<TData extends object = object> extends Omit<TracingChannel<TData, TracingChannelPayloadWithSpan<TData>>, 'subscribe' | 'unsubscribe'> { | ||
| subscribe(subscribers: Partial<TracingChannelSubscribers<TracingChannelPayloadWithSpan<TData>>>): void; | ||
| unsubscribe(subscribers: Partial<TracingChannelSubscribers<TracingChannelPayloadWithSpan<TData>>>): void; | ||
| } | ||
| export interface TracingChannelLifeCycleOptions<TData extends object = object> { | ||
| /** | ||
| * Invoked with the span and the channel context object once the traced operation completes | ||
| * Use it to enrich the span from the result/error (branch on `'error' in data` / `'result' in data`) or to run cleanup. | ||
| */ | ||
| beforeSpanEnd?: (span: Span, data: TracingChannelPayloadWithSpan<TData>) => void; | ||
| /** | ||
| * Whether a thrown error is captured as a Sentry event. The span is always marked with error status regardless. Defaults to `false`. | ||
| * You can alternatively pass a function that sets the ExclusiveEventHintOrCaptureContext on the captured error. | ||
| * Set `true` for instrumentations that own the error boundary, (e.g: route handlers) | ||
| * For database drivers, it is not recommended to set this at all. | ||
| */ | ||
| captureError?: boolean | ((e: unknown) => ExclusiveEventHintOrCaptureContext); | ||
| } | ||
| /** Returned by {@link bindTracingChannelToSpan}: the bound channel plus a teardown handle. */ | ||
| export interface TracingChannelBindingHandle<TData extends object = object> { | ||
| /** | ||
| * The tracing channel with the span bound into async context. | ||
| */ | ||
| channel: SentryTracingChannel<TData>; | ||
| /** | ||
| * Tears down the binding: unsubscribes lifecycle handlers, when present, and unbinds the start store. | ||
| * Idempotent, and a no-op when no async context binding was available. | ||
| */ | ||
| unbind: () => void; | ||
| } | ||
| /** | ||
| * Bind a span and its lifecycle to a tracing channel so the span becomes the active async context | ||
| * for the traced operation and is ended when the operation completes. | ||
| * | ||
| * `getSpan` may return `undefined` to opt a payload out entirely: nothing is bound, no span is | ||
| * tracked, and the active context is left untouched. Use it for events that ride the same channel | ||
| * but should reuse the enclosing span instead of opening (and ending) their own — e.g. an agent | ||
| * loop's per-step events, where ending a freshly opened span would close the parent prematurely. | ||
| */ | ||
| export declare function bindTracingChannelToSpan<TData extends object>(channel: TracingChannel<TData, TData>, getSpan: (data: TracingChannelPayloadWithSpan<TData>) => Span | undefined, opts?: TracingChannelLifeCycleOptions<TData>): TracingChannelBindingHandle<TData>; | ||
| //# sourceMappingURL=tracing-channel.d.ts.map |
| {"version":3,"file":"tracing-channel.d.ts","sourceRoot":"","sources":["../../src/tracing-channel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AAE1F,OAAO,KAAK,EAAE,kCAAkC,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAI7E,MAAM,MAAM,6BAA6B,CAAC,KAAK,SAAS,MAAM,IAAI,KAAK,GAAG;IACxE,WAAW,CAAC,EAAE,IAAI,CAAC;CACpB,CAAC;AAKF,MAAM,WAAW,oBAAoB,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM,CAAE,SAAQ,IAAI,CAC/E,cAAc,CAAC,KAAK,EAAE,6BAA6B,CAAC,KAAK,CAAC,CAAC,EAC3D,WAAW,GAAG,aAAa,CAC5B;IACC,SAAS,CAAC,WAAW,EAAE,OAAO,CAAC,yBAAyB,CAAC,6BAA6B,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IACvG,WAAW,CAAC,WAAW,EAAE,OAAO,CAAC,yBAAyB,CAAC,6BAA6B,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;CAC1G;AAED,MAAM,WAAW,8BAA8B,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM;IAC3E;;;OAGG;IACH,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,6BAA6B,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC;IAEjF;;;;;OAKG;IACH,YAAY,CAAC,EAAE,OAAO,GAAG,CAAC,CAAC,CAAC,EAAE,OAAO,KAAK,kCAAkC,CAAC,CAAC;CAC/E;AAED,8FAA8F;AAC9F,MAAM,WAAW,2BAA2B,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM;IACxE;;OAEG;IACH,OAAO,EAAE,oBAAoB,CAAC,KAAK,CAAC,CAAC;IAErC;;;OAGG;IACH,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB;AAID;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,SAAS,MAAM,EAC3D,OAAO,EAAE,cAAc,CAAC,KAAK,EAAE,KAAK,CAAC,EACrC,OAAO,EAAE,CAAC,IAAI,EAAE,6BAA6B,CAAC,KAAK,CAAC,KAAK,IAAI,GAAG,SAAS,EACzE,IAAI,CAAC,EAAE,8BAA8B,CAAC,KAAK,CAAC,GAC3C,2BAA2B,CAAC,KAAK,CAAC,CAuDpC"} |
| Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); | ||
| const redisDcSubscriber = require('./redis/redis-dc-subscriber.js'); | ||
| const tracingChannel = require('./tracing-channel.js'); | ||
@@ -13,2 +14,3 @@ | ||
| exports.subscribeRedisDiagnosticChannels = redisDcSubscriber.subscribeRedisDiagnosticChannels; | ||
| exports.bindTracingChannelToSpan = tracingChannel.bindTracingChannelToSpan; | ||
| //# sourceMappingURL=index.js.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;"} | ||
| {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;"} |
@@ -23,2 +23,3 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); | ||
| const spans = /* @__PURE__ */ new WeakMap(); | ||
| const parentScopes = /* @__PURE__ */ new WeakMap(); | ||
| queryCh.subscribe({ | ||
@@ -46,4 +47,5 @@ start(rawCtx) { | ||
| spans.set(rawCtx, span); | ||
| const parentSpan = core.getActiveSpan(); | ||
| if (parentSpan && ctx.arguments.length > 0) { | ||
| const scope = core.getCurrentScope(); | ||
| parentScopes.set(rawCtx, scope); | ||
| if (ctx.arguments.length > 0) { | ||
| const cbIdx = ctx.arguments.length - 1; | ||
@@ -54,3 +56,3 @@ const orchestrionWrappedCb = ctx.arguments[cbIdx]; | ||
| ctx.arguments[cbIdx] = function(...args) { | ||
| return core.withActiveSpan(parentSpan, () => wrapped.apply(this, args)); | ||
| return core.withScope(scope, () => wrapped.apply(this, args)); | ||
| }; | ||
@@ -70,2 +72,6 @@ } | ||
| if (!span) return; | ||
| const parentScope = parentScopes.get(rawCtx); | ||
| if (parentScope) { | ||
| core.bindScopeToEmitter(result, parentScope); | ||
| } | ||
| result.on("error", (err) => { | ||
@@ -102,2 +108,3 @@ span.setStatus({ | ||
| spans.delete(rawCtx); | ||
| parentScopes.delete(rawCtx); | ||
| } | ||
@@ -104,0 +111,0 @@ } |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"mysql.js","sources":["../../../../src/integrations/tracing-channel/mysql.ts"],"sourcesContent":["import * as diagnosticsChannel from 'node:diagnostics_channel';\nimport type { IntegrationFn, Span } from '@sentry/core';\nimport {\n debug,\n defineIntegration,\n getActiveSpan,\n SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,\n SPAN_STATUS_ERROR,\n startInactiveSpan,\n withActiveSpan,\n} from '@sentry/core';\nimport { DEBUG_BUILD } from '../../debug-build';\nimport { CHANNELS } from '../../orchestrion/channels';\n\n// NOTE: this uses the same name as the OTel integration by design.\n// When enabled, OTel 'Mysql' integration is omitted from the default set.\nconst INTEGRATION_NAME = 'Mysql';\n\n// OpenTelemetry \"OLD\" db/net semantic-conventions. We inline them rather than\n// importing `@opentelemetry/semantic-conventions` to keep this integration's\n// dependency surface free of OTel — orchestrion's whole point is to step away\n// from the OTel auto-instrumentation stack.\n//\n// We emit the OLD conventions to match `@opentelemetry/instrumentation-mysql`'s\n// default (it only emits the stable `db.system.name` / `db.query.text` set when\n// `OTEL_SEMCONV_STABILITY_OPT_IN=database` is opted into) and the rest of the\n// Sentry JS SDK, whose `inferDbSpanData` processor renames spans based on\n// `db.statement`.\nconst ATTR_DB_SYSTEM = 'db.system';\nconst ATTR_DB_CONNECTION_STRING = 'db.connection_string';\nconst ATTR_DB_NAME = 'db.name';\nconst ATTR_DB_USER = 'db.user';\nconst ATTR_DB_STATEMENT = 'db.statement';\nconst ATTR_NET_PEER_NAME = 'net.peer.name';\nconst ATTR_NET_PEER_PORT = 'net.peer.port';\n\n/**\n * The shape orchestrion's wrapCallback transform attaches to the tracing-channel\n * `context` object. Documented here rather than imported because orchestrion's\n * runtime doesn't export it — see `node_modules/@apm-js-collab/code-transformer/lib/transforms.js`.\n *\n * `arguments` is the *live* args array the wrapper passes to the wrapped function:\n * orchestrion splices the user's callback out and inserts its own wrapper at\n * the same index before publishing `start`. We mutate that last entry again in\n * our `start` hook so the callback (and any nested `connection.query(...)`)\n * runs inside `withActiveSpan(parent, …)` — mysql v2 loses ALS state when it\n * dispatches callbacks from its socket handler, which would otherwise cause\n * nested queries to begin a fresh root trace.\n */\ninterface MysqlQueryChannelContext {\n arguments: unknown[];\n self?: MysqlConnection;\n moduleVersion?: string;\n result?: unknown;\n error?: unknown;\n}\n\ninterface MysqlConnectionConfig {\n host?: string;\n port?: number | string;\n database?: string;\n user?: string;\n // Pool connections nest the real config one level deeper.\n connectionConfig?: MysqlConnectionConfig;\n}\n\ninterface MysqlConnection {\n config?: MysqlConnectionConfig;\n}\n\nconst _mysqlChannelIntegration = (() => {\n return {\n name: INTEGRATION_NAME,\n setupOnce() {\n DEBUG_BUILD && debug.log(`[orchestrion:mysql] subscribing to channel \"${CHANNELS.MYSQL_QUERY}\"`);\n const queryCh = diagnosticsChannel.tracingChannel(CHANNELS.MYSQL_QUERY);\n\n // Each `context` object is shared across start/end/asyncStart/asyncEnd/error\n // for one call (orchestrion creates one per invocation). We key the span\n // off the same identity. WeakMap so we don't leak if a path never reaches\n // asyncEnd for some reason.\n const spans = new WeakMap<object, Span>();\n\n // `subscribe()` requires all five lifecycle hooks. The orchestrion\n // `wrapAuto` transform fires events in one of four orders depending on\n // call shape:\n // - sync throw : start → error → end\n // (NO asyncEnd)\n // - async-callback error : start → end → error →\n // asyncStart → asyncEnd\n // - async-callback success : start → end → asyncStart →\n // asyncEnd\n // - no-callback (streamable Query) : start → end\n // (ctx.result is the Query\n // emitter, no async events)\n //\n // We end the span on `asyncEnd` for the two callback paths (so the span\n // covers the full network round-trip + callback duration). For the\n // sync-throw path, `end` finishes the span because `ctx.error` is set\n // there. For the streamable no-callback path, `end` finishes by\n // attaching `'end'`/`'error'` listeners to `ctx.result` (the returned\n // `Query` emitter).\n //\n // The discriminator between \"end fired before any error\" and \"end fired\n // after a sync throw\" is whether `ctx.error` is set when `end` runs —\n // orchestrion populates it before publishing `error`. The discriminator\n // between callback and no-callback is whether `ctx.result` is set — only\n // the `wrapPromise` (no-callback) path stores it.\n queryCh.subscribe({\n start(rawCtx) {\n const ctx = rawCtx as MysqlQueryChannelContext;\n const sql = extractSql(ctx.arguments[0]);\n const { host, port, database, user } = getConnectionConfig(ctx.self);\n const portNumber = typeof port === 'string' ? parseInt(port, 10) : port;\n const portIsNumber = typeof portNumber === 'number' && !isNaN(portNumber);\n\n const span = startInactiveSpan({\n name: sql ?? 'mysql.query',\n op: 'db',\n attributes: {\n [ATTR_DB_SYSTEM]: 'mysql',\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.db.orchestrion.mysql',\n [ATTR_DB_CONNECTION_STRING]: getJDBCString(host, portIsNumber ? portNumber : undefined, database),\n ...(database ? { [ATTR_DB_NAME]: database } : {}),\n ...(user ? { [ATTR_DB_USER]: user } : {}),\n ...(sql ? { [ATTR_DB_STATEMENT]: sql } : {}),\n ...(host ? { [ATTR_NET_PEER_NAME]: host } : {}),\n ...(portIsNumber ? { [ATTR_NET_PEER_PORT]: portNumber } : {}),\n },\n });\n spans.set(rawCtx, span);\n\n // Restore the Sentry/OTel context across mysql's internal callback\n // dispatch. The orchestrion transform has already spliced the user's\n // callback out of `ctx.arguments` and put its own wrapper\n // (`__apm$wrappedCb`) at the same index. mysql v2 drains callbacks\n // from a socket data handler — by the time the response arrives, the\n // AsyncLocalStorage store backing `getActiveSpan()` no longer\n // reflects the caller's context. We re-wrap orchestrion's wrapper so\n // the user's callback (and any nested `connection.query(...)` inside\n // it) runs with the parent span active again.\n //\n // This must happen at `start` (we're synchronously inside the\n // caller's `connection.query` call, so OTel context is still\n // correct). `asyncStart`/`asyncEnd` fire from the same lost context\n // as the callback itself, so they're too late.\n const parentSpan = getActiveSpan();\n if (parentSpan && ctx.arguments.length > 0) {\n const cbIdx = ctx.arguments.length - 1;\n const orchestrionWrappedCb = ctx.arguments[cbIdx];\n if (typeof orchestrionWrappedCb === 'function') {\n const wrapped = orchestrionWrappedCb as (...a: unknown[]) => unknown;\n ctx.arguments[cbIdx] = function (this: unknown, ...args: unknown[]): unknown {\n return withActiveSpan(parentSpan, () => wrapped.apply(this, args));\n };\n }\n }\n },\n\n end(rawCtx) {\n const ctx = rawCtx as MysqlQueryChannelContext;\n\n // Sync throw: `end` fires AFTER `error` (both inside the wrapper's\n // `try/catch/finally`), so `ctx.error` is already set. Close the\n // span now since no `asyncEnd` will fire.\n if (ctx.error !== undefined) {\n finishSpan(rawCtx);\n return;\n }\n\n // No-callback (streamable Query) path: orchestrion's `wrapPromise`\n // stores the synchronous return value on `ctx.result` and never\n // fires `asyncStart`/`asyncEnd`. The returned `Query` is an\n // `EventEmitter` that emits `'end'` on success and `'error'` on\n // failure — hook those to close the span.\n // TODO: streaming spans aren't finished on `connection.destroy()` —\n // mysql guarantees no further events/callbacks for a destroyed\n // connection, so neither `'end'` nor `'error'` fires and the span\n // never ends (it's dropped, never reported). Closing this gap needs\n // connection-level lifecycle hooks, which the per-query channel\n // context doesn't expose here. The `WeakMap` still prevents a leak.\n const result = ctx.result;\n if (result && typeof result === 'object' && hasOnMethod(result)) {\n const span = spans.get(rawCtx);\n if (!span) return;\n result.on('error', err => {\n span.setStatus({\n code: SPAN_STATUS_ERROR,\n message: err instanceof Error ? err.message : 'unknown_error',\n });\n // Defensive: end the span here too in case `'end'` never fires\n // (e.g. abrupt socket destruction). `finishSpan` is idempotent —\n // `spans.delete` makes the subsequent `'end'` listener a no-op.\n finishSpan(rawCtx);\n });\n result.on('end', () => finishSpan(rawCtx));\n return;\n }\n\n // Callback path: `asyncEnd` will close the span. Nothing to do here.\n },\n\n error(rawCtx) {\n const ctx = rawCtx as MysqlQueryChannelContext;\n const span = spans.get(rawCtx);\n if (!span) return;\n span.setStatus({\n code: SPAN_STATUS_ERROR,\n message: ctx.error instanceof Error ? ctx.error.message : 'unknown_error',\n });\n },\n\n asyncStart() {\n // No-op: we end on `asyncEnd` so the span covers the full callback duration.\n },\n\n asyncEnd(rawCtx) {\n finishSpan(rawCtx);\n },\n });\n\n function finishSpan(rawCtx: object): void {\n const span = spans.get(rawCtx);\n if (!span) return;\n span.end();\n spans.delete(rawCtx);\n }\n },\n };\n}) satisfies IntegrationFn;\n\nfunction hasOnMethod(obj: object): obj is { on: (event: string, listener: (arg?: unknown) => void) => unknown } {\n return 'on' in obj && typeof (obj as { on?: unknown }).on === 'function';\n}\n\nfunction extractSql(firstArg: unknown): string | undefined {\n if (typeof firstArg === 'string') {\n return firstArg;\n }\n if (firstArg && typeof firstArg === 'object' && 'sql' in firstArg) {\n const sql = (firstArg as { sql?: unknown }).sql;\n return typeof sql === 'string' ? sql : undefined;\n }\n return undefined;\n}\n\nfunction getConnectionConfig(connection: MysqlConnection | undefined): {\n host?: string;\n port?: number | string;\n database?: string;\n user?: string;\n} {\n // Pool connections nest the real config under `.connectionConfig`; single\n // connections expose it directly. Matches `@opentelemetry/instrumentation-mysql`.\n const config = connection?.config?.connectionConfig ?? connection?.config ?? {};\n return {\n host: config.host,\n port: config.port,\n database: config.database,\n user: config.user,\n };\n}\n\nfunction getJDBCString(host: string | undefined, port: number | undefined, database: string | undefined): string {\n let s = `jdbc:mysql://${host || 'localhost'}`;\n if (typeof port === 'number') {\n s += `:${port}`;\n }\n if (database) {\n s += `/${database}`;\n }\n return s;\n}\n\n/**\n * EXPERIMENTAL — orchestrion-driven mysql integration.\n *\n * Subscribes to the `orchestrion:mysql:query` diagnostics_channel that the\n * orchestrion code transform injects into `mysql/lib/Connection.js`'s\n * `Connection.prototype.query`. Requires the orchestrion runtime hook or\n * bundler plugin to be active — wire that up via `_experimentalSetupOrchestrion`.\n */\nexport const mysqlChannelIntegration = defineIntegration(_mysqlChannelIntegration);\n"],"names":["DEBUG_BUILD","debug","CHANNELS","startInactiveSpan","SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN","getActiveSpan","withActiveSpan","SPAN_STATUS_ERROR","defineIntegration"],"mappings":"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"} | ||
| {"version":3,"file":"mysql.js","sources":["../../../../src/integrations/tracing-channel/mysql.ts"],"sourcesContent":["import * as diagnosticsChannel from 'node:diagnostics_channel';\nimport type { IntegrationFn, Scope, Span } from '@sentry/core';\nimport {\n bindScopeToEmitter,\n debug,\n defineIntegration,\n getCurrentScope,\n SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,\n SPAN_STATUS_ERROR,\n startInactiveSpan,\n withScope,\n} from '@sentry/core';\nimport { DEBUG_BUILD } from '../../debug-build';\nimport { CHANNELS } from '../../orchestrion/channels';\n\n// NOTE: this uses the same name as the OTel integration by design.\n// When enabled, OTel 'Mysql' integration is omitted from the default set.\nconst INTEGRATION_NAME = 'Mysql';\n\n// OpenTelemetry \"OLD\" db/net semantic-conventions. We inline them rather than\n// importing `@opentelemetry/semantic-conventions` to keep this integration's\n// dependency surface free of OTel — orchestrion's whole point is to step away\n// from the OTel auto-instrumentation stack.\n//\n// We emit the OLD conventions to match `@opentelemetry/instrumentation-mysql`'s\n// default (it only emits the stable `db.system.name` / `db.query.text` set when\n// `OTEL_SEMCONV_STABILITY_OPT_IN=database` is opted into) and the rest of the\n// Sentry JS SDK, whose `inferDbSpanData` processor renames spans based on\n// `db.statement`.\nconst ATTR_DB_SYSTEM = 'db.system';\nconst ATTR_DB_CONNECTION_STRING = 'db.connection_string';\nconst ATTR_DB_NAME = 'db.name';\nconst ATTR_DB_USER = 'db.user';\nconst ATTR_DB_STATEMENT = 'db.statement';\nconst ATTR_NET_PEER_NAME = 'net.peer.name';\nconst ATTR_NET_PEER_PORT = 'net.peer.port';\n\n/**\n * The shape orchestrion's wrapCallback transform attaches to the tracing-channel\n * `context` object. Documented here rather than imported because orchestrion's\n * runtime doesn't export it — see `node_modules/@apm-js-collab/code-transformer/lib/transforms.js`.\n *\n * `arguments` is the *live* args array passed to the wrapped function: orchestrion\n * splices the user's callback out and inserts its own wrapper at the same index\n * before publishing `start`. The `start` hook re-wraps that entry to restore the\n * caller's scope across mysql's async callback dispatch (see below).\n */\ninterface MysqlQueryChannelContext {\n arguments: unknown[];\n self?: MysqlConnection;\n moduleVersion?: string;\n result?: unknown;\n error?: unknown;\n}\n\ninterface MysqlConnectionConfig {\n host?: string;\n port?: number | string;\n database?: string;\n user?: string;\n // Pool connections nest the real config one level deeper.\n connectionConfig?: MysqlConnectionConfig;\n}\n\ninterface MysqlConnection {\n config?: MysqlConnectionConfig;\n}\n\nconst _mysqlChannelIntegration = (() => {\n return {\n name: INTEGRATION_NAME,\n setupOnce() {\n DEBUG_BUILD && debug.log(`[orchestrion:mysql] subscribing to channel \"${CHANNELS.MYSQL_QUERY}\"`);\n const queryCh = diagnosticsChannel.tracingChannel(CHANNELS.MYSQL_QUERY);\n\n // Orchestrion creates one `context` object per call, shared across all\n // lifecycle hooks. We key both maps off that identity; `WeakMap` so an\n // unfinished path can't leak its entries.\n const spans = new WeakMap<object, Span>();\n // The scope active when the query was issued, consumed in `end` to bind\n // the streamed `Query` emitter's listeners to it.\n const parentScopes = new WeakMap<object, Scope>();\n\n // `subscribe()` requires all five lifecycle hooks. The orchestrion\n // `wrapAuto` transform fires events in one of four orders depending on\n // call shape:\n // - sync throw : start → error → end\n // (NO asyncEnd)\n // - async-callback error : start → end → error →\n // asyncStart → asyncEnd\n // - async-callback success : start → end → asyncStart →\n // asyncEnd\n // - no-callback (streamable Query) : start → end\n // (ctx.result is the Query\n // emitter, no async events)\n //\n // Where the span closes depends on the path: `asyncEnd` for callbacks (so\n // it spans the full round-trip + callback), or `end` for the sync-throw\n // and streamable paths. The `end` hook tells those apart via `ctx.error`\n // / `ctx.result` — see there.\n queryCh.subscribe({\n start(rawCtx) {\n const ctx = rawCtx as MysqlQueryChannelContext;\n const sql = extractSql(ctx.arguments[0]);\n const { host, port, database, user } = getConnectionConfig(ctx.self);\n const portNumber = typeof port === 'string' ? parseInt(port, 10) : port;\n const portIsNumber = typeof portNumber === 'number' && !isNaN(portNumber);\n\n const span = startInactiveSpan({\n name: sql ?? 'mysql.query',\n op: 'db',\n attributes: {\n [ATTR_DB_SYSTEM]: 'mysql',\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.db.orchestrion.mysql',\n [ATTR_DB_CONNECTION_STRING]: getJDBCString(host, portIsNumber ? portNumber : undefined, database),\n ...(database ? { [ATTR_DB_NAME]: database } : {}),\n ...(user ? { [ATTR_DB_USER]: user } : {}),\n ...(sql ? { [ATTR_DB_STATEMENT]: sql } : {}),\n ...(host ? { [ATTR_NET_PEER_NAME]: host } : {}),\n ...(portIsNumber ? { [ATTR_NET_PEER_PORT]: portNumber } : {}),\n },\n });\n spans.set(rawCtx, span);\n\n // Capture the scope while we're still synchronously inside the\n // caller's `connection.query` call. mysql v2 drains callbacks and\n // emits streamed-query events from its socket data handler, where the\n // AsyncLocalStorage store backing the active span no longer reflects\n // the caller's context — and `asyncStart`/`asyncEnd` fire from that\n // same lost context, so capturing has to happen now.\n const scope = getCurrentScope();\n parentScopes.set(rawCtx, scope);\n\n // Callback path: orchestrion has spliced the user's callback out of\n // `ctx.arguments` and put its own wrapper (`__apm$wrappedCb`) at the\n // same index. Re-wrap it so the callback — and any nested\n // `connection.query(...)` — runs with the captured scope active.\n if (ctx.arguments.length > 0) {\n const cbIdx = ctx.arguments.length - 1;\n const orchestrionWrappedCb = ctx.arguments[cbIdx];\n if (typeof orchestrionWrappedCb === 'function') {\n const wrapped = orchestrionWrappedCb as (...a: unknown[]) => unknown;\n ctx.arguments[cbIdx] = function (this: unknown, ...args: unknown[]): unknown {\n return withScope(scope, () => wrapped.apply(this, args));\n };\n }\n }\n },\n\n end(rawCtx) {\n const ctx = rawCtx as MysqlQueryChannelContext;\n\n // Sync throw: `end` fires AFTER `error` (both inside the wrapper's\n // `try/catch/finally`), so `ctx.error` is already set. Close the\n // span now since no `asyncEnd` will fire.\n if (ctx.error !== undefined) {\n finishSpan(rawCtx);\n return;\n }\n\n // No-callback (streamable Query) path: orchestrion's `wrapPromise`\n // stores the synchronous return value on `ctx.result` and never\n // fires `asyncStart`/`asyncEnd`. The returned `Query` is an\n // `EventEmitter` that emits `'end'` on success and `'error'` on\n // failure — hook those to close the span.\n // Note: a streamed span never finishes if the connection is destroyed\n // mid-flight — mysql then emits neither `'end'` nor `'error'`, so the\n // span is dropped (the `WeakMap` still prevents a leak). Closing this\n // needs connection-level hooks the per-query context doesn't expose.\n const result = ctx.result;\n if (result && typeof result === 'object' && hasOnMethod(result)) {\n const span = spans.get(rawCtx);\n if (!span) return;\n\n // Bind the captured scope to the streamed `Query` emitter: its\n // `'end'`/`'error'`/`'fields'`/… events fire from mysql's socket\n // handler with the caller's context lost, so without this a span\n // started in a user's stream listener would begin a fresh root trace\n // instead of nesting under the parent. `bindScopeToEmitter` patches\n // `on`/`addListener`/… so listeners added after `query()` returns\n // inherit the scope (like OTel's `context.bind`).\n const parentScope = parentScopes.get(rawCtx);\n if (parentScope) {\n bindScopeToEmitter(result, parentScope);\n }\n\n result.on('error', err => {\n span.setStatus({\n code: SPAN_STATUS_ERROR,\n message: err instanceof Error ? err.message : 'unknown_error',\n });\n // Defensive: end the span here too in case `'end'` never fires\n // (e.g. abrupt socket destruction). `finishSpan` is idempotent —\n // `spans.delete` makes the subsequent `'end'` listener a no-op.\n finishSpan(rawCtx);\n });\n result.on('end', () => finishSpan(rawCtx));\n return;\n }\n\n // Callback path: `asyncEnd` will close the span. Nothing to do here.\n },\n\n error(rawCtx) {\n const ctx = rawCtx as MysqlQueryChannelContext;\n const span = spans.get(rawCtx);\n if (!span) return;\n span.setStatus({\n code: SPAN_STATUS_ERROR,\n message: ctx.error instanceof Error ? ctx.error.message : 'unknown_error',\n });\n },\n\n asyncStart() {\n // No-op: we end on `asyncEnd` so the span covers the full callback duration.\n },\n\n asyncEnd(rawCtx) {\n finishSpan(rawCtx);\n },\n });\n\n function finishSpan(rawCtx: object): void {\n const span = spans.get(rawCtx);\n if (!span) return;\n span.end();\n spans.delete(rawCtx);\n parentScopes.delete(rawCtx);\n }\n },\n };\n}) satisfies IntegrationFn;\n\nfunction hasOnMethod(obj: object): obj is { on: (event: string, listener: (arg?: unknown) => void) => unknown } {\n return 'on' in obj && typeof (obj as { on?: unknown }).on === 'function';\n}\n\nfunction extractSql(firstArg: unknown): string | undefined {\n if (typeof firstArg === 'string') {\n return firstArg;\n }\n if (firstArg && typeof firstArg === 'object' && 'sql' in firstArg) {\n const sql = (firstArg as { sql?: unknown }).sql;\n return typeof sql === 'string' ? sql : undefined;\n }\n return undefined;\n}\n\nfunction getConnectionConfig(connection: MysqlConnection | undefined): {\n host?: string;\n port?: number | string;\n database?: string;\n user?: string;\n} {\n // Pool connections nest the real config under `.connectionConfig`; single\n // connections expose it directly. Matches `@opentelemetry/instrumentation-mysql`.\n const config = connection?.config?.connectionConfig ?? connection?.config ?? {};\n return {\n host: config.host,\n port: config.port,\n database: config.database,\n user: config.user,\n };\n}\n\nfunction getJDBCString(host: string | undefined, port: number | undefined, database: string | undefined): string {\n let s = `jdbc:mysql://${host || 'localhost'}`;\n if (typeof port === 'number') {\n s += `:${port}`;\n }\n if (database) {\n s += `/${database}`;\n }\n return s;\n}\n\n/**\n * EXPERIMENTAL — orchestrion-driven mysql integration.\n *\n * Subscribes to the `orchestrion:mysql:query` diagnostics_channel that the\n * orchestrion code transform injects into `mysql/lib/Connection.js`'s\n * `Connection.prototype.query`. Requires the orchestrion runtime hook or\n * bundler plugin to be active — wire that up via `_experimentalSetupOrchestrion`.\n */\nexport const mysqlChannelIntegration = defineIntegration(_mysqlChannelIntegration);\n"],"names":["DEBUG_BUILD","debug","CHANNELS","startInactiveSpan","SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN","getCurrentScope","withScope","bindScopeToEmitter","SPAN_STATUS_ERROR","defineIntegration"],"mappings":"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"} |
@@ -6,2 +6,3 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); | ||
| const debugBuild = require('../debug-build.js'); | ||
| const tracingChannel = require('../tracing-channel.js'); | ||
@@ -15,6 +16,5 @@ const REDIS_DC_CHANNEL_COMMAND = "node-redis:command"; | ||
| const DB_SYSTEM_NAME_VALUE_REDIS = "redis"; | ||
| const NOOP = () => { | ||
| }; | ||
| let subscribed = false; | ||
| let currentResponseHook; | ||
| let activeUnbinds = []; | ||
| function subscribeRedisDiagnosticChannels(tracingChannel, responseHook) { | ||
@@ -25,11 +25,16 @@ currentResponseHook = responseHook; | ||
| try { | ||
| setupCommandChannel(tracingChannel, REDIS_DC_CHANNEL_COMMAND, (data) => data.args.slice(1)); | ||
| setupBatchChannel( | ||
| tracingChannel, | ||
| REDIS_DC_CHANNEL_BATCH, | ||
| (data) => data.batchMode === "PIPELINE" ? "PIPELINE" : "MULTI" | ||
| activeUnbinds.push( | ||
| setupCommandChannel(tracingChannel, REDIS_DC_CHANNEL_COMMAND, (data) => data.args.slice(1)), | ||
| setupBatchChannel( | ||
| tracingChannel, | ||
| REDIS_DC_CHANNEL_BATCH, | ||
| (data) => data.batchMode === "PIPELINE" ? "PIPELINE" : "MULTI" | ||
| ), | ||
| setupConnectChannel(tracingChannel, REDIS_DC_CHANNEL_CONNECT), | ||
| // ioredis: args already exclude the command name; no slicing needed. And | ||
| // ioredis has no separate batch channel — pipeline/MULTI metadata rides | ||
| // on the per-command payload via `batchMode`/`batchSize`. | ||
| setupCommandChannel(tracingChannel, IOREDIS_DC_CHANNEL_COMMAND, (data) => data.args), | ||
| setupConnectChannel(tracingChannel, IOREDIS_DC_CHANNEL_CONNECT) | ||
| ); | ||
| setupConnectChannel(tracingChannel, REDIS_DC_CHANNEL_CONNECT); | ||
| setupCommandChannel(tracingChannel, IOREDIS_DC_CHANNEL_COMMAND, (data) => data.args); | ||
| setupConnectChannel(tracingChannel, IOREDIS_DC_CHANNEL_CONNECT); | ||
| } catch { | ||
@@ -39,8 +44,9 @@ debugBuild.DEBUG_BUILD && core.debug.log("Redis node:diagnostics_channel subscription failed."); | ||
| } | ||
| function setupCommandChannel(tracingChannel, channelName, getCommandArgs) { | ||
| const channel = tracingChannel(channelName, (data) => { | ||
| const args = getCommandArgs(data); | ||
| const statement = args.length ? `${data.command} ${args.join(" ")}` : data.command; | ||
| return core.startSpanManual( | ||
| { | ||
| function setupCommandChannel(tracingChannel$1, channelName, getCommandArgs) { | ||
| return tracingChannel.bindTracingChannelToSpan( | ||
| tracingChannel$1(channelName), | ||
| (data) => { | ||
| const args = getCommandArgs(data); | ||
| const statement = args.length ? `${data.command} ${args.join(" ")}` : data.command; | ||
| return core.startInactiveSpan({ | ||
| name: `redis-${data.command}`, | ||
@@ -55,30 +61,20 @@ attributes: { | ||
| } | ||
| }, | ||
| (span) => span | ||
| ); | ||
| }); | ||
| channel.subscribe({ | ||
| start: NOOP, | ||
| asyncStart: NOOP, | ||
| end: NOOP, | ||
| asyncEnd: (data) => { | ||
| const span = data._sentrySpan; | ||
| if (!span || data.error) return; | ||
| runResponseHook(span, data.command, getCommandArgs(data), data.result); | ||
| span.end(); | ||
| }); | ||
| }, | ||
| error: (data) => { | ||
| const span = data._sentrySpan; | ||
| if (!span) return; | ||
| if (data.error) { | ||
| span.setStatus({ code: core.SPAN_STATUS_ERROR, message: data.error.message }); | ||
| { | ||
| // Command failures are surfaced to (and usually handled by) the caller; only annotate the | ||
| // span so we don't emit a duplicate error event for every failed command. | ||
| captureError: false, | ||
| beforeSpanEnd(span, data) { | ||
| if ("error" in data) return; | ||
| runResponseHook(span, data.command, getCommandArgs(data), data.result); | ||
| } | ||
| span.end(); | ||
| } | ||
| }); | ||
| ).unbind; | ||
| } | ||
| function setupBatchChannel(tracingChannel, channelName, getOperationName) { | ||
| const channel = tracingChannel(channelName, (data) => { | ||
| return core.startSpanManual( | ||
| { | ||
| function setupBatchChannel(tracingChannel$1, channelName, getOperationName) { | ||
| return tracingChannel.bindTracingChannelToSpan( | ||
| tracingChannel$1(channelName), | ||
| (data) => { | ||
| return core.startInactiveSpan({ | ||
| name: getOperationName(data), | ||
@@ -95,27 +91,12 @@ attributes: { | ||
| } | ||
| }, | ||
| (span) => span | ||
| ); | ||
| }); | ||
| channel.subscribe({ | ||
| start: NOOP, | ||
| asyncStart: NOOP, | ||
| end: NOOP, | ||
| asyncEnd: (data) => { | ||
| if (!data.error) data._sentrySpan?.end(); | ||
| }); | ||
| }, | ||
| error: (data) => { | ||
| const span = data._sentrySpan; | ||
| if (!span) return; | ||
| if (data.error) { | ||
| span.setStatus({ code: core.SPAN_STATUS_ERROR, message: data.error.message }); | ||
| } | ||
| span.end(); | ||
| } | ||
| }); | ||
| { captureError: false } | ||
| ).unbind; | ||
| } | ||
| function setupConnectChannel(tracingChannel, channelName) { | ||
| const channel = tracingChannel(channelName, (data) => { | ||
| return core.startSpanManual( | ||
| { | ||
| function setupConnectChannel(tracingChannel$1, channelName) { | ||
| return tracingChannel.bindTracingChannelToSpan( | ||
| tracingChannel$1(channelName), | ||
| (data) => { | ||
| return core.startInactiveSpan({ | ||
| name: "redis-connect", | ||
@@ -129,22 +110,6 @@ attributes: { | ||
| } | ||
| }, | ||
| (span) => span | ||
| ); | ||
| }); | ||
| channel.subscribe({ | ||
| start: NOOP, | ||
| asyncStart: NOOP, | ||
| end: NOOP, | ||
| asyncEnd: (data) => { | ||
| if (!data.error) data._sentrySpan?.end(); | ||
| }); | ||
| }, | ||
| error: (data) => { | ||
| const span = data._sentrySpan; | ||
| if (!span) return; | ||
| if (data.error) { | ||
| span.setStatus({ code: core.SPAN_STATUS_ERROR, message: data.error.message }); | ||
| } | ||
| span.end(); | ||
| } | ||
| }); | ||
| { captureError: false } | ||
| ).unbind; | ||
| } | ||
@@ -151,0 +116,0 @@ function runResponseHook(span, command, args, result) { |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"redis-dc-subscriber.js","sources":["../../../src/redis/redis-dc-subscriber.ts"],"sourcesContent":["import {\n DB_OPERATION_BATCH_SIZE,\n DB_QUERY_TEXT,\n DB_SYSTEM_NAME,\n SERVER_ADDRESS,\n SERVER_PORT,\n} from '@sentry/conventions/attributes';\nimport type { Span } from '@sentry/core';\nimport {\n debug,\n SEMANTIC_ATTRIBUTE_SENTRY_OP,\n SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,\n SPAN_STATUS_ERROR,\n startSpanManual,\n} from '@sentry/core';\nimport { DEBUG_BUILD } from '../debug-build';\n\n// Channel names published by node-redis >= 5.12.0 and ioredis >= 5.11.0.\n// Hardcoded so the subscriber does not have to import either library — the\n// channels just have to be subscribed to before the user's redis code\n// publishes.\nexport const REDIS_DC_CHANNEL_COMMAND = 'node-redis:command';\nexport const REDIS_DC_CHANNEL_BATCH = 'node-redis:batch';\nexport const REDIS_DC_CHANNEL_CONNECT = 'node-redis:connect';\nexport const IOREDIS_DC_CHANNEL_COMMAND = 'ioredis:command';\nexport const IOREDIS_DC_CHANNEL_CONNECT = 'ioredis:connect';\n\nconst ORIGIN = 'auto.db.redis.diagnostic_channel';\nconst DB_SYSTEM_NAME_VALUE_REDIS = 'redis';\n\nconst NOOP = (): void => {};\n\n/**\n * Shape of the `node-redis:command` channel payload published by node-redis.\n *\n * Both `command` and `args` are already redacted by node-redis itself (see\n * `sanitizeArgs` in @redis/client) using the OTel `redis-common` rules. The\n * arg array is `[<command>, <safe arg>, ..., '?', ...]` — `?` replaces any\n * value the library considers sensitive. Subscribers can emit `args` directly\n * as `db.query.text` without further serialization.\n */\nexport interface RedisCommandData {\n command: string;\n /** First arg is the command name itself; consumers should slice it off. */\n args: string[];\n database?: number;\n serverAddress?: string;\n serverPort?: number;\n result?: unknown;\n error?: Error;\n}\n\n/**\n * Shape of the `ioredis:command` channel payload published by ioredis >= 5.11.0.\n *\n * As with node-redis, args are already sanitized by ioredis (`sanitizeArgs` in\n * `lib/tracing.ts`) before publishing. Unlike node-redis, the command name is\n * NOT prepended to `args`.\n */\nexport interface IORedisCommandData {\n command: string;\n args: string[];\n batchMode?: 'MULTI';\n batchSize?: number;\n database?: number;\n serverAddress?: string;\n serverPort?: number;\n result?: unknown;\n error?: Error;\n}\n\n/** Shape of the `node-redis:batch` channel payload published by node-redis. */\nexport interface RedisBatchData {\n batchMode?: 'MULTI' | 'PIPELINE';\n batchSize?: number;\n database?: number;\n clientId?: string | number;\n serverAddress?: string;\n serverPort?: number;\n result?: unknown[];\n error?: Error;\n}\n\n/** Shape of the `*:connect` channel payload published by node-redis and ioredis. */\nexport interface RedisConnectData {\n serverAddress?: string;\n serverPort?: number;\n url?: string;\n error?: Error;\n}\n\n/**\n * Optional callback invoked once the redis command response arrives. Useful\n * for attaching response-derived attributes (e.g. cache hit/miss, payload size).\n *\n * Mirrors `@opentelemetry/instrumentation-ioredis`' response hook so existing\n * Sentry node code (`cacheResponseHook`) can be reused unchanged.\n */\nexport type RedisDiagnosticChannelResponseHook = (\n span: Span,\n cmdName: string,\n cmdArgs: string[],\n result: unknown,\n) => void;\n\n/**\n * Payload type observed by tracing-channel subscribers — the channel payload\n * with `_sentrySpan` stamped on it by the start handler so async/error\n * handlers downstream can read it back.\n */\nexport type RedisTracingChannelContextWithSpan<T> = T & { _sentrySpan?: Span };\n\n/** Subscriber object accepted by {@link RedisTracingChannel.subscribe}. */\nexport interface RedisTracingChannelSubscribers<T> {\n start: (data: RedisTracingChannelContextWithSpan<T>) => void;\n asyncStart: (data: RedisTracingChannelContextWithSpan<T>) => void;\n asyncEnd: (data: RedisTracingChannelContextWithSpan<T>) => void;\n end: (data: RedisTracingChannelContextWithSpan<T>) => void;\n error: (data: RedisTracingChannelContextWithSpan<T>) => void;\n}\n\n/** Minimal tracing-channel surface the subscriber depends on. */\nexport interface RedisTracingChannel<T extends object> {\n subscribe(subs: Partial<RedisTracingChannelSubscribers<T>>): void;\n}\n\n/**\n * Platform-provided factory that returns a tracing channel for the given\n * channel name. Implementations are responsible for ensuring that, when the\n * channel's `start` event fires, the span returned by `transformStart(data)`\n * ends up stored on `data._sentrySpan` so the subscriber's `asyncEnd`/`error`\n * handlers can read it.\n *\n * - Node passes `@sentry/opentelemetry/tracing-channel` which uses\n * `bindStore` to also propagate the span as the active OTel context.\n * - Deno (and other non-OTel runtimes) pass a portable wrapper around\n * `node:diagnostics_channel.tracingChannel` that just stamps\n * `data._sentrySpan` in `start` without `bindStore`.\n */\nexport type RedisTracingChannelFactory = <T extends object>(\n name: string,\n transformStart: (data: T) => Span,\n) => RedisTracingChannel<T>;\n\nlet subscribed = false;\nlet currentResponseHook: RedisDiagnosticChannelResponseHook | undefined;\n\n/**\n * Subscribe Sentry span handlers to node-redis and ioredis diagnostics-channel\n * events: `node-redis:command`/`:batch`/`:connect` (published by node-redis\n * >= 5.12.0) and `ioredis:command`/`:connect` (published by ioredis >= 5.11.0).\n *\n * On older client versions the channels are never published to, so subscribers\n * are inert — there is no double-instrumentation against any IITM-based\n * patcher gated to those older versions.\n *\n * Idempotent: subsequent calls update the response hook but do not\n * re-subscribe.\n */\nexport function subscribeRedisDiagnosticChannels(\n tracingChannel: RedisTracingChannelFactory,\n responseHook?: RedisDiagnosticChannelResponseHook,\n): void {\n currentResponseHook = responseHook;\n if (subscribed) return;\n subscribed = true;\n\n try {\n // node-redis: command name appears as args[0] in the channel payload, so\n // strip it before the statement and response hook see it.\n setupCommandChannel<RedisCommandData>(tracingChannel, REDIS_DC_CHANNEL_COMMAND, data => data.args.slice(1));\n setupBatchChannel(tracingChannel, REDIS_DC_CHANNEL_BATCH, data =>\n data.batchMode === 'PIPELINE' ? 'PIPELINE' : 'MULTI',\n );\n setupConnectChannel(tracingChannel, REDIS_DC_CHANNEL_CONNECT);\n\n // ioredis: args already exclude the command name; no slicing needed. And\n // ioredis has no separate batch channel — pipeline/MULTI metadata rides\n // on the per-command payload via `batchMode`/`batchSize`.\n setupCommandChannel<IORedisCommandData>(tracingChannel, IOREDIS_DC_CHANNEL_COMMAND, data => data.args);\n setupConnectChannel(tracingChannel, IOREDIS_DC_CHANNEL_CONNECT);\n } catch {\n // The factory may rely on `node:diagnostics_channel`, which isn't always\n // available. Fail closed; the SDK simply won't emit redis spans here.\n DEBUG_BUILD && debug.log('Redis node:diagnostics_channel subscription failed.');\n }\n}\n\nfunction setupCommandChannel<T extends RedisCommandData | IORedisCommandData>(\n tracingChannel: RedisTracingChannelFactory,\n channelName: string,\n getCommandArgs: (data: T) => string[],\n): void {\n const channel = tracingChannel<T>(channelName, data => {\n // `args` is already sanitized by the publishing library (node-redis /\n // ioredis call their own `sanitizeArgs` before publishing). Join with\n // spaces to mirror the format the libraries themselves intend.\n const args = getCommandArgs(data);\n const statement = args.length ? `${data.command} ${args.join(' ')}` : data.command;\n return startSpanManual(\n {\n name: `redis-${data.command}`,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ORIGIN,\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'db.redis',\n [DB_SYSTEM_NAME]: DB_SYSTEM_NAME_VALUE_REDIS,\n [DB_QUERY_TEXT]: statement,\n ...(data.serverAddress != null ? { [SERVER_ADDRESS]: data.serverAddress } : {}),\n ...(data.serverPort != null ? { [SERVER_PORT]: data.serverPort } : {}),\n },\n },\n span => span,\n );\n });\n\n channel.subscribe({\n start: NOOP,\n asyncStart: NOOP,\n end: NOOP,\n asyncEnd: data => {\n const span = data._sentrySpan;\n // Only end here if the error handler isn't going to.\n if (!span || data.error) return;\n runResponseHook(span, data.command, getCommandArgs(data), data.result);\n span.end();\n },\n error: data => {\n const span = data._sentrySpan;\n if (!span) return;\n if (data.error) {\n span.setStatus({ code: SPAN_STATUS_ERROR, message: data.error.message });\n }\n span.end();\n },\n });\n}\n\nfunction setupBatchChannel(\n tracingChannel: RedisTracingChannelFactory,\n channelName: string,\n getOperationName: (data: RedisBatchData) => string,\n): void {\n const channel = tracingChannel<RedisBatchData>(channelName, data => {\n return startSpanManual(\n {\n name: getOperationName(data),\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ORIGIN,\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'db.redis',\n [DB_SYSTEM_NAME]: DB_SYSTEM_NAME_VALUE_REDIS,\n // should only include batch size greater than 1,\n // or else it isn't properly considered a \"batch\"\n ...(Number(data.batchSize) > 1 ? { [DB_OPERATION_BATCH_SIZE]: data.batchSize } : {}),\n ...(data.serverAddress != null ? { [SERVER_ADDRESS]: data.serverAddress } : {}),\n ...(data.serverPort != null ? { [SERVER_PORT]: data.serverPort } : {}),\n },\n },\n span => span,\n );\n });\n\n channel.subscribe({\n start: NOOP,\n asyncStart: NOOP,\n end: NOOP,\n asyncEnd: data => {\n if (!data.error) data._sentrySpan?.end();\n },\n error: data => {\n const span = data._sentrySpan;\n if (!span) return;\n if (data.error) {\n span.setStatus({ code: SPAN_STATUS_ERROR, message: data.error.message });\n }\n span.end();\n },\n });\n}\n\nfunction setupConnectChannel(tracingChannel: RedisTracingChannelFactory, channelName: string): void {\n const channel = tracingChannel<RedisConnectData>(channelName, data => {\n return startSpanManual(\n {\n name: 'redis-connect',\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ORIGIN,\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'db.redis.connect',\n [DB_SYSTEM_NAME]: DB_SYSTEM_NAME_VALUE_REDIS,\n ...(data.serverAddress != null ? { [SERVER_ADDRESS]: data.serverAddress } : {}),\n ...(data.serverPort != null ? { [SERVER_PORT]: data.serverPort } : {}),\n },\n },\n span => span,\n );\n });\n\n channel.subscribe({\n start: NOOP,\n asyncStart: NOOP,\n end: NOOP,\n asyncEnd: data => {\n if (!data.error) data._sentrySpan?.end();\n },\n error: data => {\n const span = data._sentrySpan;\n if (!span) return;\n if (data.error) {\n span.setStatus({ code: SPAN_STATUS_ERROR, message: data.error.message });\n }\n span.end();\n },\n });\n}\n\nfunction runResponseHook(span: Span, command: string, args: string[], result: unknown): void {\n const hook = currentResponseHook;\n if (!hook) return;\n try {\n hook(span, command, args, result);\n } catch {\n // never let user hooks break instrumentation\n }\n}\n\n/** Test-only: reset module-local subscribe state. */\nexport function _resetRedisDiagnosticChannelsForTesting(): void {\n subscribed = false;\n currentResponseHook = undefined;\n}\n"],"names":["DEBUG_BUILD","debug","startSpanManual","SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN","SEMANTIC_ATTRIBUTE_SENTRY_OP","DB_SYSTEM_NAME","DB_QUERY_TEXT","SERVER_ADDRESS","SERVER_PORT","SPAN_STATUS_ERROR","DB_OPERATION_BATCH_SIZE"],"mappings":"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"} | ||
| {"version":3,"file":"redis-dc-subscriber.js","sources":["../../../src/redis/redis-dc-subscriber.ts"],"sourcesContent":["import type { TracingChannel } from 'node:diagnostics_channel';\nimport {\n DB_OPERATION_BATCH_SIZE,\n DB_QUERY_TEXT,\n DB_SYSTEM_NAME,\n SERVER_ADDRESS,\n SERVER_PORT,\n} from '@sentry/conventions/attributes';\nimport type { Span } from '@sentry/core';\nimport { debug, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, startInactiveSpan } from '@sentry/core';\nimport { DEBUG_BUILD } from '../debug-build';\nimport { bindTracingChannelToSpan } from '../tracing-channel';\n\n// Channel names published by node-redis >= 5.12.0 and ioredis >= 5.11.0.\n// Hardcoded so the subscriber does not have to import either library — the\n// channels just have to be subscribed to before the user's redis code\n// publishes.\nexport const REDIS_DC_CHANNEL_COMMAND = 'node-redis:command';\nexport const REDIS_DC_CHANNEL_BATCH = 'node-redis:batch';\nexport const REDIS_DC_CHANNEL_CONNECT = 'node-redis:connect';\nexport const IOREDIS_DC_CHANNEL_COMMAND = 'ioredis:command';\nexport const IOREDIS_DC_CHANNEL_CONNECT = 'ioredis:connect';\n\nconst ORIGIN = 'auto.db.redis.diagnostic_channel';\nconst DB_SYSTEM_NAME_VALUE_REDIS = 'redis';\n\n/**\n * Shape of the `node-redis:command` channel payload published by node-redis.\n *\n * Both `command` and `args` are already redacted by node-redis itself (see\n * `sanitizeArgs` in @redis/client) using the OTel `redis-common` rules. The\n * arg array is `[<command>, <safe arg>, ..., '?', ...]` — `?` replaces any\n * value the library considers sensitive. Subscribers can emit `args` directly\n * as `db.query.text` without further serialization.\n */\nexport interface RedisCommandData {\n command: string;\n /** First arg is the command name itself; consumers should slice it off. */\n args: string[];\n database?: number;\n serverAddress?: string;\n serverPort?: number;\n result?: unknown;\n error?: Error;\n}\n\n/**\n * Shape of the `ioredis:command` channel payload published by ioredis >= 5.11.0.\n *\n * As with node-redis, args are already sanitized by ioredis (`sanitizeArgs` in\n * `lib/tracing.ts`) before publishing. Unlike node-redis, the command name is\n * NOT prepended to `args`.\n */\nexport interface IORedisCommandData {\n command: string;\n args: string[];\n batchMode?: 'MULTI';\n batchSize?: number;\n database?: number;\n serverAddress?: string;\n serverPort?: number;\n result?: unknown;\n error?: Error;\n}\n\n/** Shape of the `node-redis:batch` channel payload published by node-redis. */\nexport interface RedisBatchData {\n batchMode?: 'MULTI' | 'PIPELINE';\n batchSize?: number;\n database?: number;\n clientId?: string | number;\n serverAddress?: string;\n serverPort?: number;\n result?: unknown[];\n error?: Error;\n}\n\n/** Shape of the `*:connect` channel payload published by node-redis and ioredis. */\nexport interface RedisConnectData {\n serverAddress?: string;\n serverPort?: number;\n url?: string;\n error?: Error;\n}\n\n/**\n * Optional callback invoked once the redis command response arrives. Useful\n * for attaching response-derived attributes (e.g. cache hit/miss, payload size).\n *\n * Mirrors `@opentelemetry/instrumentation-ioredis`' response hook so existing\n * Sentry node code (`cacheResponseHook`) can be reused unchanged.\n */\nexport type RedisDiagnosticChannelResponseHook = (\n span: Span,\n cmdName: string,\n cmdArgs: string[],\n result: unknown,\n) => void;\n\n/**\n * Platform-provided factory that creates a native tracing channel for the given name. The\n * subscriber binds the span and its lifecycle onto the channel via `bindTracingChannelToSpan`,\n * which propagates the active span through the runtime's async context.\n *\n * Both Node and Deno pass `node:diagnostics_channel`'s `tracingChannel` directly.\n */\nexport type RedisTracingChannelFactory = <T extends object>(name: string) => TracingChannel<T, T>;\n\nlet subscribed = false;\nlet currentResponseHook: RedisDiagnosticChannelResponseHook | undefined;\nlet activeUnbinds: Array<() => void> = [];\n\n/**\n * Subscribe Sentry span handlers to node-redis and ioredis diagnostics-channel\n * events: `node-redis:command`/`:batch`/`:connect` (published by node-redis\n * >= 5.12.0) and `ioredis:command`/`:connect` (published by ioredis >= 5.11.0).\n *\n * On older client versions the channels are never published to, so subscribers\n * are inert — there is no double-instrumentation against any IITM-based\n * patcher gated to those older versions.\n *\n * Idempotent: subsequent calls update the response hook but do not\n * re-subscribe.\n */\nexport function subscribeRedisDiagnosticChannels(\n tracingChannel: RedisTracingChannelFactory,\n responseHook?: RedisDiagnosticChannelResponseHook,\n): void {\n currentResponseHook = responseHook;\n if (subscribed) return;\n subscribed = true;\n\n try {\n // node-redis: command name appears as args[0] in the channel payload, so\n // strip it before the statement and response hook see it.\n activeUnbinds.push(\n setupCommandChannel<RedisCommandData>(tracingChannel, REDIS_DC_CHANNEL_COMMAND, data => data.args.slice(1)),\n setupBatchChannel(tracingChannel, REDIS_DC_CHANNEL_BATCH, data =>\n data.batchMode === 'PIPELINE' ? 'PIPELINE' : 'MULTI',\n ),\n setupConnectChannel(tracingChannel, REDIS_DC_CHANNEL_CONNECT),\n // ioredis: args already exclude the command name; no slicing needed. And\n // ioredis has no separate batch channel — pipeline/MULTI metadata rides\n // on the per-command payload via `batchMode`/`batchSize`.\n setupCommandChannel<IORedisCommandData>(tracingChannel, IOREDIS_DC_CHANNEL_COMMAND, data => data.args),\n setupConnectChannel(tracingChannel, IOREDIS_DC_CHANNEL_CONNECT),\n );\n } catch {\n // The factory may rely on `node:diagnostics_channel`, which isn't always\n // available. Fail closed; the SDK simply won't emit redis spans here.\n DEBUG_BUILD && debug.log('Redis node:diagnostics_channel subscription failed.');\n }\n}\n\nfunction setupCommandChannel<T extends RedisCommandData | IORedisCommandData>(\n tracingChannel: RedisTracingChannelFactory,\n channelName: string,\n getCommandArgs: (data: T) => string[],\n): () => void {\n return bindTracingChannelToSpan(\n tracingChannel<T>(channelName),\n data => {\n // `args` is already sanitized by the publishing library (node-redis /\n // ioredis call their own `sanitizeArgs` before publishing). Join with\n // spaces to mirror the format the libraries themselves intend.\n const args = getCommandArgs(data);\n const statement = args.length ? `${data.command} ${args.join(' ')}` : data.command;\n return startInactiveSpan({\n name: `redis-${data.command}`,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ORIGIN,\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'db.redis',\n [DB_SYSTEM_NAME]: DB_SYSTEM_NAME_VALUE_REDIS,\n [DB_QUERY_TEXT]: statement,\n ...(data.serverAddress != null ? { [SERVER_ADDRESS]: data.serverAddress } : {}),\n ...(data.serverPort != null ? { [SERVER_PORT]: data.serverPort } : {}),\n },\n });\n },\n {\n // Command failures are surfaced to (and usually handled by) the caller; only annotate the\n // span so we don't emit a duplicate error event for every failed command.\n captureError: false,\n beforeSpanEnd(span, data) {\n if ('error' in data) return;\n runResponseHook(span, data.command, getCommandArgs(data), data.result);\n },\n },\n ).unbind;\n}\n\nfunction setupBatchChannel(\n tracingChannel: RedisTracingChannelFactory,\n channelName: string,\n getOperationName: (data: RedisBatchData) => string,\n): () => void {\n return bindTracingChannelToSpan(\n tracingChannel<RedisBatchData>(channelName),\n data => {\n return startInactiveSpan({\n name: getOperationName(data),\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ORIGIN,\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'db.redis',\n [DB_SYSTEM_NAME]: DB_SYSTEM_NAME_VALUE_REDIS,\n // should only include batch size greater than 1,\n // or else it isn't properly considered a \"batch\"\n ...(Number(data.batchSize) > 1 ? { [DB_OPERATION_BATCH_SIZE]: data.batchSize } : {}),\n ...(data.serverAddress != null ? { [SERVER_ADDRESS]: data.serverAddress } : {}),\n ...(data.serverPort != null ? { [SERVER_PORT]: data.serverPort } : {}),\n },\n });\n },\n { captureError: false },\n ).unbind;\n}\n\nfunction setupConnectChannel(tracingChannel: RedisTracingChannelFactory, channelName: string): () => void {\n return bindTracingChannelToSpan(\n tracingChannel<RedisConnectData>(channelName),\n data => {\n return startInactiveSpan({\n name: 'redis-connect',\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ORIGIN,\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'db.redis.connect',\n [DB_SYSTEM_NAME]: DB_SYSTEM_NAME_VALUE_REDIS,\n ...(data.serverAddress != null ? { [SERVER_ADDRESS]: data.serverAddress } : {}),\n ...(data.serverPort != null ? { [SERVER_PORT]: data.serverPort } : {}),\n },\n });\n },\n { captureError: false },\n ).unbind;\n}\n\nfunction runResponseHook(span: Span, command: string, args: string[], result: unknown): void {\n const hook = currentResponseHook;\n if (!hook) return;\n try {\n hook(span, command, args, result);\n } catch {\n // never let user hooks break instrumentation\n }\n}\n\n/** Test-only: detach all channel bindings and reset module-local subscribe state. */\nexport function _resetRedisDiagnosticChannelsForTesting(): void {\n activeUnbinds.forEach(unbind => unbind());\n activeUnbinds = [];\n subscribed = false;\n currentResponseHook = undefined;\n}\n"],"names":["DEBUG_BUILD","debug","tracingChannel","bindTracingChannelToSpan","startInactiveSpan","SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN","SEMANTIC_ATTRIBUTE_SENTRY_OP","DB_SYSTEM_NAME","DB_QUERY_TEXT","SERVER_ADDRESS","SERVER_PORT","DB_OPERATION_BATCH_SIZE"],"mappings":"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"} |
| export { IOREDIS_DC_CHANNEL_COMMAND, IOREDIS_DC_CHANNEL_CONNECT, REDIS_DC_CHANNEL_BATCH, REDIS_DC_CHANNEL_COMMAND, REDIS_DC_CHANNEL_CONNECT, subscribeRedisDiagnosticChannels } from './redis/redis-dc-subscriber.js'; | ||
| export { bindTracingChannelToSpan } from './tracing-channel.js'; | ||
| //# sourceMappingURL=index.js.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":""} | ||
| {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"} |
| import * as diagnosticsChannel from 'node:diagnostics_channel'; | ||
| import { defineIntegration, debug, SPAN_STATUS_ERROR, startInactiveSpan, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, getActiveSpan, withActiveSpan } from '@sentry/core'; | ||
| import { defineIntegration, debug, SPAN_STATUS_ERROR, bindScopeToEmitter, startInactiveSpan, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, getCurrentScope, withScope } from '@sentry/core'; | ||
| import { DEBUG_BUILD } from '../../debug-build.js'; | ||
@@ -21,2 +21,3 @@ import { CHANNELS } from '../../orchestrion/channels.js'; | ||
| const spans = /* @__PURE__ */ new WeakMap(); | ||
| const parentScopes = /* @__PURE__ */ new WeakMap(); | ||
| queryCh.subscribe({ | ||
@@ -44,4 +45,5 @@ start(rawCtx) { | ||
| spans.set(rawCtx, span); | ||
| const parentSpan = getActiveSpan(); | ||
| if (parentSpan && ctx.arguments.length > 0) { | ||
| const scope = getCurrentScope(); | ||
| parentScopes.set(rawCtx, scope); | ||
| if (ctx.arguments.length > 0) { | ||
| const cbIdx = ctx.arguments.length - 1; | ||
@@ -52,3 +54,3 @@ const orchestrionWrappedCb = ctx.arguments[cbIdx]; | ||
| ctx.arguments[cbIdx] = function(...args) { | ||
| return withActiveSpan(parentSpan, () => wrapped.apply(this, args)); | ||
| return withScope(scope, () => wrapped.apply(this, args)); | ||
| }; | ||
@@ -68,2 +70,6 @@ } | ||
| if (!span) return; | ||
| const parentScope = parentScopes.get(rawCtx); | ||
| if (parentScope) { | ||
| bindScopeToEmitter(result, parentScope); | ||
| } | ||
| result.on("error", (err) => { | ||
@@ -100,2 +106,3 @@ span.setStatus({ | ||
| spans.delete(rawCtx); | ||
| parentScopes.delete(rawCtx); | ||
| } | ||
@@ -102,0 +109,0 @@ } |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"mysql.js","sources":["../../../../src/integrations/tracing-channel/mysql.ts"],"sourcesContent":["import * as diagnosticsChannel from 'node:diagnostics_channel';\nimport type { IntegrationFn, Span } from '@sentry/core';\nimport {\n debug,\n defineIntegration,\n getActiveSpan,\n SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,\n SPAN_STATUS_ERROR,\n startInactiveSpan,\n withActiveSpan,\n} from '@sentry/core';\nimport { DEBUG_BUILD } from '../../debug-build';\nimport { CHANNELS } from '../../orchestrion/channels';\n\n// NOTE: this uses the same name as the OTel integration by design.\n// When enabled, OTel 'Mysql' integration is omitted from the default set.\nconst INTEGRATION_NAME = 'Mysql';\n\n// OpenTelemetry \"OLD\" db/net semantic-conventions. We inline them rather than\n// importing `@opentelemetry/semantic-conventions` to keep this integration's\n// dependency surface free of OTel — orchestrion's whole point is to step away\n// from the OTel auto-instrumentation stack.\n//\n// We emit the OLD conventions to match `@opentelemetry/instrumentation-mysql`'s\n// default (it only emits the stable `db.system.name` / `db.query.text` set when\n// `OTEL_SEMCONV_STABILITY_OPT_IN=database` is opted into) and the rest of the\n// Sentry JS SDK, whose `inferDbSpanData` processor renames spans based on\n// `db.statement`.\nconst ATTR_DB_SYSTEM = 'db.system';\nconst ATTR_DB_CONNECTION_STRING = 'db.connection_string';\nconst ATTR_DB_NAME = 'db.name';\nconst ATTR_DB_USER = 'db.user';\nconst ATTR_DB_STATEMENT = 'db.statement';\nconst ATTR_NET_PEER_NAME = 'net.peer.name';\nconst ATTR_NET_PEER_PORT = 'net.peer.port';\n\n/**\n * The shape orchestrion's wrapCallback transform attaches to the tracing-channel\n * `context` object. Documented here rather than imported because orchestrion's\n * runtime doesn't export it — see `node_modules/@apm-js-collab/code-transformer/lib/transforms.js`.\n *\n * `arguments` is the *live* args array the wrapper passes to the wrapped function:\n * orchestrion splices the user's callback out and inserts its own wrapper at\n * the same index before publishing `start`. We mutate that last entry again in\n * our `start` hook so the callback (and any nested `connection.query(...)`)\n * runs inside `withActiveSpan(parent, …)` — mysql v2 loses ALS state when it\n * dispatches callbacks from its socket handler, which would otherwise cause\n * nested queries to begin a fresh root trace.\n */\ninterface MysqlQueryChannelContext {\n arguments: unknown[];\n self?: MysqlConnection;\n moduleVersion?: string;\n result?: unknown;\n error?: unknown;\n}\n\ninterface MysqlConnectionConfig {\n host?: string;\n port?: number | string;\n database?: string;\n user?: string;\n // Pool connections nest the real config one level deeper.\n connectionConfig?: MysqlConnectionConfig;\n}\n\ninterface MysqlConnection {\n config?: MysqlConnectionConfig;\n}\n\nconst _mysqlChannelIntegration = (() => {\n return {\n name: INTEGRATION_NAME,\n setupOnce() {\n DEBUG_BUILD && debug.log(`[orchestrion:mysql] subscribing to channel \"${CHANNELS.MYSQL_QUERY}\"`);\n const queryCh = diagnosticsChannel.tracingChannel(CHANNELS.MYSQL_QUERY);\n\n // Each `context` object is shared across start/end/asyncStart/asyncEnd/error\n // for one call (orchestrion creates one per invocation). We key the span\n // off the same identity. WeakMap so we don't leak if a path never reaches\n // asyncEnd for some reason.\n const spans = new WeakMap<object, Span>();\n\n // `subscribe()` requires all five lifecycle hooks. The orchestrion\n // `wrapAuto` transform fires events in one of four orders depending on\n // call shape:\n // - sync throw : start → error → end\n // (NO asyncEnd)\n // - async-callback error : start → end → error →\n // asyncStart → asyncEnd\n // - async-callback success : start → end → asyncStart →\n // asyncEnd\n // - no-callback (streamable Query) : start → end\n // (ctx.result is the Query\n // emitter, no async events)\n //\n // We end the span on `asyncEnd` for the two callback paths (so the span\n // covers the full network round-trip + callback duration). For the\n // sync-throw path, `end` finishes the span because `ctx.error` is set\n // there. For the streamable no-callback path, `end` finishes by\n // attaching `'end'`/`'error'` listeners to `ctx.result` (the returned\n // `Query` emitter).\n //\n // The discriminator between \"end fired before any error\" and \"end fired\n // after a sync throw\" is whether `ctx.error` is set when `end` runs —\n // orchestrion populates it before publishing `error`. The discriminator\n // between callback and no-callback is whether `ctx.result` is set — only\n // the `wrapPromise` (no-callback) path stores it.\n queryCh.subscribe({\n start(rawCtx) {\n const ctx = rawCtx as MysqlQueryChannelContext;\n const sql = extractSql(ctx.arguments[0]);\n const { host, port, database, user } = getConnectionConfig(ctx.self);\n const portNumber = typeof port === 'string' ? parseInt(port, 10) : port;\n const portIsNumber = typeof portNumber === 'number' && !isNaN(portNumber);\n\n const span = startInactiveSpan({\n name: sql ?? 'mysql.query',\n op: 'db',\n attributes: {\n [ATTR_DB_SYSTEM]: 'mysql',\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.db.orchestrion.mysql',\n [ATTR_DB_CONNECTION_STRING]: getJDBCString(host, portIsNumber ? portNumber : undefined, database),\n ...(database ? { [ATTR_DB_NAME]: database } : {}),\n ...(user ? { [ATTR_DB_USER]: user } : {}),\n ...(sql ? { [ATTR_DB_STATEMENT]: sql } : {}),\n ...(host ? { [ATTR_NET_PEER_NAME]: host } : {}),\n ...(portIsNumber ? { [ATTR_NET_PEER_PORT]: portNumber } : {}),\n },\n });\n spans.set(rawCtx, span);\n\n // Restore the Sentry/OTel context across mysql's internal callback\n // dispatch. The orchestrion transform has already spliced the user's\n // callback out of `ctx.arguments` and put its own wrapper\n // (`__apm$wrappedCb`) at the same index. mysql v2 drains callbacks\n // from a socket data handler — by the time the response arrives, the\n // AsyncLocalStorage store backing `getActiveSpan()` no longer\n // reflects the caller's context. We re-wrap orchestrion's wrapper so\n // the user's callback (and any nested `connection.query(...)` inside\n // it) runs with the parent span active again.\n //\n // This must happen at `start` (we're synchronously inside the\n // caller's `connection.query` call, so OTel context is still\n // correct). `asyncStart`/`asyncEnd` fire from the same lost context\n // as the callback itself, so they're too late.\n const parentSpan = getActiveSpan();\n if (parentSpan && ctx.arguments.length > 0) {\n const cbIdx = ctx.arguments.length - 1;\n const orchestrionWrappedCb = ctx.arguments[cbIdx];\n if (typeof orchestrionWrappedCb === 'function') {\n const wrapped = orchestrionWrappedCb as (...a: unknown[]) => unknown;\n ctx.arguments[cbIdx] = function (this: unknown, ...args: unknown[]): unknown {\n return withActiveSpan(parentSpan, () => wrapped.apply(this, args));\n };\n }\n }\n },\n\n end(rawCtx) {\n const ctx = rawCtx as MysqlQueryChannelContext;\n\n // Sync throw: `end` fires AFTER `error` (both inside the wrapper's\n // `try/catch/finally`), so `ctx.error` is already set. Close the\n // span now since no `asyncEnd` will fire.\n if (ctx.error !== undefined) {\n finishSpan(rawCtx);\n return;\n }\n\n // No-callback (streamable Query) path: orchestrion's `wrapPromise`\n // stores the synchronous return value on `ctx.result` and never\n // fires `asyncStart`/`asyncEnd`. The returned `Query` is an\n // `EventEmitter` that emits `'end'` on success and `'error'` on\n // failure — hook those to close the span.\n // TODO: streaming spans aren't finished on `connection.destroy()` —\n // mysql guarantees no further events/callbacks for a destroyed\n // connection, so neither `'end'` nor `'error'` fires and the span\n // never ends (it's dropped, never reported). Closing this gap needs\n // connection-level lifecycle hooks, which the per-query channel\n // context doesn't expose here. The `WeakMap` still prevents a leak.\n const result = ctx.result;\n if (result && typeof result === 'object' && hasOnMethod(result)) {\n const span = spans.get(rawCtx);\n if (!span) return;\n result.on('error', err => {\n span.setStatus({\n code: SPAN_STATUS_ERROR,\n message: err instanceof Error ? err.message : 'unknown_error',\n });\n // Defensive: end the span here too in case `'end'` never fires\n // (e.g. abrupt socket destruction). `finishSpan` is idempotent —\n // `spans.delete` makes the subsequent `'end'` listener a no-op.\n finishSpan(rawCtx);\n });\n result.on('end', () => finishSpan(rawCtx));\n return;\n }\n\n // Callback path: `asyncEnd` will close the span. Nothing to do here.\n },\n\n error(rawCtx) {\n const ctx = rawCtx as MysqlQueryChannelContext;\n const span = spans.get(rawCtx);\n if (!span) return;\n span.setStatus({\n code: SPAN_STATUS_ERROR,\n message: ctx.error instanceof Error ? ctx.error.message : 'unknown_error',\n });\n },\n\n asyncStart() {\n // No-op: we end on `asyncEnd` so the span covers the full callback duration.\n },\n\n asyncEnd(rawCtx) {\n finishSpan(rawCtx);\n },\n });\n\n function finishSpan(rawCtx: object): void {\n const span = spans.get(rawCtx);\n if (!span) return;\n span.end();\n spans.delete(rawCtx);\n }\n },\n };\n}) satisfies IntegrationFn;\n\nfunction hasOnMethod(obj: object): obj is { on: (event: string, listener: (arg?: unknown) => void) => unknown } {\n return 'on' in obj && typeof (obj as { on?: unknown }).on === 'function';\n}\n\nfunction extractSql(firstArg: unknown): string | undefined {\n if (typeof firstArg === 'string') {\n return firstArg;\n }\n if (firstArg && typeof firstArg === 'object' && 'sql' in firstArg) {\n const sql = (firstArg as { sql?: unknown }).sql;\n return typeof sql === 'string' ? sql : undefined;\n }\n return undefined;\n}\n\nfunction getConnectionConfig(connection: MysqlConnection | undefined): {\n host?: string;\n port?: number | string;\n database?: string;\n user?: string;\n} {\n // Pool connections nest the real config under `.connectionConfig`; single\n // connections expose it directly. Matches `@opentelemetry/instrumentation-mysql`.\n const config = connection?.config?.connectionConfig ?? connection?.config ?? {};\n return {\n host: config.host,\n port: config.port,\n database: config.database,\n user: config.user,\n };\n}\n\nfunction getJDBCString(host: string | undefined, port: number | undefined, database: string | undefined): string {\n let s = `jdbc:mysql://${host || 'localhost'}`;\n if (typeof port === 'number') {\n s += `:${port}`;\n }\n if (database) {\n s += `/${database}`;\n }\n return s;\n}\n\n/**\n * EXPERIMENTAL — orchestrion-driven mysql integration.\n *\n * Subscribes to the `orchestrion:mysql:query` diagnostics_channel that the\n * orchestrion code transform injects into `mysql/lib/Connection.js`'s\n * `Connection.prototype.query`. Requires the orchestrion runtime hook or\n * bundler plugin to be active — wire that up via `_experimentalSetupOrchestrion`.\n */\nexport const mysqlChannelIntegration = defineIntegration(_mysqlChannelIntegration);\n"],"names":[],"mappings":"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"} | ||
| {"version":3,"file":"mysql.js","sources":["../../../../src/integrations/tracing-channel/mysql.ts"],"sourcesContent":["import * as diagnosticsChannel from 'node:diagnostics_channel';\nimport type { IntegrationFn, Scope, Span } from '@sentry/core';\nimport {\n bindScopeToEmitter,\n debug,\n defineIntegration,\n getCurrentScope,\n SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,\n SPAN_STATUS_ERROR,\n startInactiveSpan,\n withScope,\n} from '@sentry/core';\nimport { DEBUG_BUILD } from '../../debug-build';\nimport { CHANNELS } from '../../orchestrion/channels';\n\n// NOTE: this uses the same name as the OTel integration by design.\n// When enabled, OTel 'Mysql' integration is omitted from the default set.\nconst INTEGRATION_NAME = 'Mysql';\n\n// OpenTelemetry \"OLD\" db/net semantic-conventions. We inline them rather than\n// importing `@opentelemetry/semantic-conventions` to keep this integration's\n// dependency surface free of OTel — orchestrion's whole point is to step away\n// from the OTel auto-instrumentation stack.\n//\n// We emit the OLD conventions to match `@opentelemetry/instrumentation-mysql`'s\n// default (it only emits the stable `db.system.name` / `db.query.text` set when\n// `OTEL_SEMCONV_STABILITY_OPT_IN=database` is opted into) and the rest of the\n// Sentry JS SDK, whose `inferDbSpanData` processor renames spans based on\n// `db.statement`.\nconst ATTR_DB_SYSTEM = 'db.system';\nconst ATTR_DB_CONNECTION_STRING = 'db.connection_string';\nconst ATTR_DB_NAME = 'db.name';\nconst ATTR_DB_USER = 'db.user';\nconst ATTR_DB_STATEMENT = 'db.statement';\nconst ATTR_NET_PEER_NAME = 'net.peer.name';\nconst ATTR_NET_PEER_PORT = 'net.peer.port';\n\n/**\n * The shape orchestrion's wrapCallback transform attaches to the tracing-channel\n * `context` object. Documented here rather than imported because orchestrion's\n * runtime doesn't export it — see `node_modules/@apm-js-collab/code-transformer/lib/transforms.js`.\n *\n * `arguments` is the *live* args array passed to the wrapped function: orchestrion\n * splices the user's callback out and inserts its own wrapper at the same index\n * before publishing `start`. The `start` hook re-wraps that entry to restore the\n * caller's scope across mysql's async callback dispatch (see below).\n */\ninterface MysqlQueryChannelContext {\n arguments: unknown[];\n self?: MysqlConnection;\n moduleVersion?: string;\n result?: unknown;\n error?: unknown;\n}\n\ninterface MysqlConnectionConfig {\n host?: string;\n port?: number | string;\n database?: string;\n user?: string;\n // Pool connections nest the real config one level deeper.\n connectionConfig?: MysqlConnectionConfig;\n}\n\ninterface MysqlConnection {\n config?: MysqlConnectionConfig;\n}\n\nconst _mysqlChannelIntegration = (() => {\n return {\n name: INTEGRATION_NAME,\n setupOnce() {\n DEBUG_BUILD && debug.log(`[orchestrion:mysql] subscribing to channel \"${CHANNELS.MYSQL_QUERY}\"`);\n const queryCh = diagnosticsChannel.tracingChannel(CHANNELS.MYSQL_QUERY);\n\n // Orchestrion creates one `context` object per call, shared across all\n // lifecycle hooks. We key both maps off that identity; `WeakMap` so an\n // unfinished path can't leak its entries.\n const spans = new WeakMap<object, Span>();\n // The scope active when the query was issued, consumed in `end` to bind\n // the streamed `Query` emitter's listeners to it.\n const parentScopes = new WeakMap<object, Scope>();\n\n // `subscribe()` requires all five lifecycle hooks. The orchestrion\n // `wrapAuto` transform fires events in one of four orders depending on\n // call shape:\n // - sync throw : start → error → end\n // (NO asyncEnd)\n // - async-callback error : start → end → error →\n // asyncStart → asyncEnd\n // - async-callback success : start → end → asyncStart →\n // asyncEnd\n // - no-callback (streamable Query) : start → end\n // (ctx.result is the Query\n // emitter, no async events)\n //\n // Where the span closes depends on the path: `asyncEnd` for callbacks (so\n // it spans the full round-trip + callback), or `end` for the sync-throw\n // and streamable paths. The `end` hook tells those apart via `ctx.error`\n // / `ctx.result` — see there.\n queryCh.subscribe({\n start(rawCtx) {\n const ctx = rawCtx as MysqlQueryChannelContext;\n const sql = extractSql(ctx.arguments[0]);\n const { host, port, database, user } = getConnectionConfig(ctx.self);\n const portNumber = typeof port === 'string' ? parseInt(port, 10) : port;\n const portIsNumber = typeof portNumber === 'number' && !isNaN(portNumber);\n\n const span = startInactiveSpan({\n name: sql ?? 'mysql.query',\n op: 'db',\n attributes: {\n [ATTR_DB_SYSTEM]: 'mysql',\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.db.orchestrion.mysql',\n [ATTR_DB_CONNECTION_STRING]: getJDBCString(host, portIsNumber ? portNumber : undefined, database),\n ...(database ? { [ATTR_DB_NAME]: database } : {}),\n ...(user ? { [ATTR_DB_USER]: user } : {}),\n ...(sql ? { [ATTR_DB_STATEMENT]: sql } : {}),\n ...(host ? { [ATTR_NET_PEER_NAME]: host } : {}),\n ...(portIsNumber ? { [ATTR_NET_PEER_PORT]: portNumber } : {}),\n },\n });\n spans.set(rawCtx, span);\n\n // Capture the scope while we're still synchronously inside the\n // caller's `connection.query` call. mysql v2 drains callbacks and\n // emits streamed-query events from its socket data handler, where the\n // AsyncLocalStorage store backing the active span no longer reflects\n // the caller's context — and `asyncStart`/`asyncEnd` fire from that\n // same lost context, so capturing has to happen now.\n const scope = getCurrentScope();\n parentScopes.set(rawCtx, scope);\n\n // Callback path: orchestrion has spliced the user's callback out of\n // `ctx.arguments` and put its own wrapper (`__apm$wrappedCb`) at the\n // same index. Re-wrap it so the callback — and any nested\n // `connection.query(...)` — runs with the captured scope active.\n if (ctx.arguments.length > 0) {\n const cbIdx = ctx.arguments.length - 1;\n const orchestrionWrappedCb = ctx.arguments[cbIdx];\n if (typeof orchestrionWrappedCb === 'function') {\n const wrapped = orchestrionWrappedCb as (...a: unknown[]) => unknown;\n ctx.arguments[cbIdx] = function (this: unknown, ...args: unknown[]): unknown {\n return withScope(scope, () => wrapped.apply(this, args));\n };\n }\n }\n },\n\n end(rawCtx) {\n const ctx = rawCtx as MysqlQueryChannelContext;\n\n // Sync throw: `end` fires AFTER `error` (both inside the wrapper's\n // `try/catch/finally`), so `ctx.error` is already set. Close the\n // span now since no `asyncEnd` will fire.\n if (ctx.error !== undefined) {\n finishSpan(rawCtx);\n return;\n }\n\n // No-callback (streamable Query) path: orchestrion's `wrapPromise`\n // stores the synchronous return value on `ctx.result` and never\n // fires `asyncStart`/`asyncEnd`. The returned `Query` is an\n // `EventEmitter` that emits `'end'` on success and `'error'` on\n // failure — hook those to close the span.\n // Note: a streamed span never finishes if the connection is destroyed\n // mid-flight — mysql then emits neither `'end'` nor `'error'`, so the\n // span is dropped (the `WeakMap` still prevents a leak). Closing this\n // needs connection-level hooks the per-query context doesn't expose.\n const result = ctx.result;\n if (result && typeof result === 'object' && hasOnMethod(result)) {\n const span = spans.get(rawCtx);\n if (!span) return;\n\n // Bind the captured scope to the streamed `Query` emitter: its\n // `'end'`/`'error'`/`'fields'`/… events fire from mysql's socket\n // handler with the caller's context lost, so without this a span\n // started in a user's stream listener would begin a fresh root trace\n // instead of nesting under the parent. `bindScopeToEmitter` patches\n // `on`/`addListener`/… so listeners added after `query()` returns\n // inherit the scope (like OTel's `context.bind`).\n const parentScope = parentScopes.get(rawCtx);\n if (parentScope) {\n bindScopeToEmitter(result, parentScope);\n }\n\n result.on('error', err => {\n span.setStatus({\n code: SPAN_STATUS_ERROR,\n message: err instanceof Error ? err.message : 'unknown_error',\n });\n // Defensive: end the span here too in case `'end'` never fires\n // (e.g. abrupt socket destruction). `finishSpan` is idempotent —\n // `spans.delete` makes the subsequent `'end'` listener a no-op.\n finishSpan(rawCtx);\n });\n result.on('end', () => finishSpan(rawCtx));\n return;\n }\n\n // Callback path: `asyncEnd` will close the span. Nothing to do here.\n },\n\n error(rawCtx) {\n const ctx = rawCtx as MysqlQueryChannelContext;\n const span = spans.get(rawCtx);\n if (!span) return;\n span.setStatus({\n code: SPAN_STATUS_ERROR,\n message: ctx.error instanceof Error ? ctx.error.message : 'unknown_error',\n });\n },\n\n asyncStart() {\n // No-op: we end on `asyncEnd` so the span covers the full callback duration.\n },\n\n asyncEnd(rawCtx) {\n finishSpan(rawCtx);\n },\n });\n\n function finishSpan(rawCtx: object): void {\n const span = spans.get(rawCtx);\n if (!span) return;\n span.end();\n spans.delete(rawCtx);\n parentScopes.delete(rawCtx);\n }\n },\n };\n}) satisfies IntegrationFn;\n\nfunction hasOnMethod(obj: object): obj is { on: (event: string, listener: (arg?: unknown) => void) => unknown } {\n return 'on' in obj && typeof (obj as { on?: unknown }).on === 'function';\n}\n\nfunction extractSql(firstArg: unknown): string | undefined {\n if (typeof firstArg === 'string') {\n return firstArg;\n }\n if (firstArg && typeof firstArg === 'object' && 'sql' in firstArg) {\n const sql = (firstArg as { sql?: unknown }).sql;\n return typeof sql === 'string' ? sql : undefined;\n }\n return undefined;\n}\n\nfunction getConnectionConfig(connection: MysqlConnection | undefined): {\n host?: string;\n port?: number | string;\n database?: string;\n user?: string;\n} {\n // Pool connections nest the real config under `.connectionConfig`; single\n // connections expose it directly. Matches `@opentelemetry/instrumentation-mysql`.\n const config = connection?.config?.connectionConfig ?? connection?.config ?? {};\n return {\n host: config.host,\n port: config.port,\n database: config.database,\n user: config.user,\n };\n}\n\nfunction getJDBCString(host: string | undefined, port: number | undefined, database: string | undefined): string {\n let s = `jdbc:mysql://${host || 'localhost'}`;\n if (typeof port === 'number') {\n s += `:${port}`;\n }\n if (database) {\n s += `/${database}`;\n }\n return s;\n}\n\n/**\n * EXPERIMENTAL — orchestrion-driven mysql integration.\n *\n * Subscribes to the `orchestrion:mysql:query` diagnostics_channel that the\n * orchestrion code transform injects into `mysql/lib/Connection.js`'s\n * `Connection.prototype.query`. Requires the orchestrion runtime hook or\n * bundler plugin to be active — wire that up via `_experimentalSetupOrchestrion`.\n */\nexport const mysqlChannelIntegration = defineIntegration(_mysqlChannelIntegration);\n"],"names":[],"mappings":"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"} |
@@ -1,1 +0,1 @@ | ||
| {"type":"module","version":"10.60.0","sideEffects":false} | ||
| {"type":"module","version":"10.61.0","sideEffects":false} |
| import { SERVER_PORT, SERVER_ADDRESS, DB_QUERY_TEXT, DB_SYSTEM_NAME, DB_OPERATION_BATCH_SIZE } from '@sentry/conventions/attributes'; | ||
| import { debug, startSpanManual, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SPAN_STATUS_ERROR } from '@sentry/core'; | ||
| import { debug, startInactiveSpan, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core'; | ||
| import { DEBUG_BUILD } from '../debug-build.js'; | ||
| import { bindTracingChannelToSpan } from '../tracing-channel.js'; | ||
@@ -12,6 +13,5 @@ const REDIS_DC_CHANNEL_COMMAND = "node-redis:command"; | ||
| const DB_SYSTEM_NAME_VALUE_REDIS = "redis"; | ||
| const NOOP = () => { | ||
| }; | ||
| let subscribed = false; | ||
| let currentResponseHook; | ||
| let activeUnbinds = []; | ||
| function subscribeRedisDiagnosticChannels(tracingChannel, responseHook) { | ||
@@ -22,11 +22,16 @@ currentResponseHook = responseHook; | ||
| try { | ||
| setupCommandChannel(tracingChannel, REDIS_DC_CHANNEL_COMMAND, (data) => data.args.slice(1)); | ||
| setupBatchChannel( | ||
| tracingChannel, | ||
| REDIS_DC_CHANNEL_BATCH, | ||
| (data) => data.batchMode === "PIPELINE" ? "PIPELINE" : "MULTI" | ||
| activeUnbinds.push( | ||
| setupCommandChannel(tracingChannel, REDIS_DC_CHANNEL_COMMAND, (data) => data.args.slice(1)), | ||
| setupBatchChannel( | ||
| tracingChannel, | ||
| REDIS_DC_CHANNEL_BATCH, | ||
| (data) => data.batchMode === "PIPELINE" ? "PIPELINE" : "MULTI" | ||
| ), | ||
| setupConnectChannel(tracingChannel, REDIS_DC_CHANNEL_CONNECT), | ||
| // ioredis: args already exclude the command name; no slicing needed. And | ||
| // ioredis has no separate batch channel — pipeline/MULTI metadata rides | ||
| // on the per-command payload via `batchMode`/`batchSize`. | ||
| setupCommandChannel(tracingChannel, IOREDIS_DC_CHANNEL_COMMAND, (data) => data.args), | ||
| setupConnectChannel(tracingChannel, IOREDIS_DC_CHANNEL_CONNECT) | ||
| ); | ||
| setupConnectChannel(tracingChannel, REDIS_DC_CHANNEL_CONNECT); | ||
| setupCommandChannel(tracingChannel, IOREDIS_DC_CHANNEL_COMMAND, (data) => data.args); | ||
| setupConnectChannel(tracingChannel, IOREDIS_DC_CHANNEL_CONNECT); | ||
| } catch { | ||
@@ -37,7 +42,8 @@ DEBUG_BUILD && debug.log("Redis node:diagnostics_channel subscription failed."); | ||
| function setupCommandChannel(tracingChannel, channelName, getCommandArgs) { | ||
| const channel = tracingChannel(channelName, (data) => { | ||
| const args = getCommandArgs(data); | ||
| const statement = args.length ? `${data.command} ${args.join(" ")}` : data.command; | ||
| return startSpanManual( | ||
| { | ||
| return bindTracingChannelToSpan( | ||
| tracingChannel(channelName), | ||
| (data) => { | ||
| const args = getCommandArgs(data); | ||
| const statement = args.length ? `${data.command} ${args.join(" ")}` : data.command; | ||
| return startInactiveSpan({ | ||
| name: `redis-${data.command}`, | ||
@@ -52,30 +58,20 @@ attributes: { | ||
| } | ||
| }, | ||
| (span) => span | ||
| ); | ||
| }); | ||
| channel.subscribe({ | ||
| start: NOOP, | ||
| asyncStart: NOOP, | ||
| end: NOOP, | ||
| asyncEnd: (data) => { | ||
| const span = data._sentrySpan; | ||
| if (!span || data.error) return; | ||
| runResponseHook(span, data.command, getCommandArgs(data), data.result); | ||
| span.end(); | ||
| }); | ||
| }, | ||
| error: (data) => { | ||
| const span = data._sentrySpan; | ||
| if (!span) return; | ||
| if (data.error) { | ||
| span.setStatus({ code: SPAN_STATUS_ERROR, message: data.error.message }); | ||
| { | ||
| // Command failures are surfaced to (and usually handled by) the caller; only annotate the | ||
| // span so we don't emit a duplicate error event for every failed command. | ||
| captureError: false, | ||
| beforeSpanEnd(span, data) { | ||
| if ("error" in data) return; | ||
| runResponseHook(span, data.command, getCommandArgs(data), data.result); | ||
| } | ||
| span.end(); | ||
| } | ||
| }); | ||
| ).unbind; | ||
| } | ||
| function setupBatchChannel(tracingChannel, channelName, getOperationName) { | ||
| const channel = tracingChannel(channelName, (data) => { | ||
| return startSpanManual( | ||
| { | ||
| return bindTracingChannelToSpan( | ||
| tracingChannel(channelName), | ||
| (data) => { | ||
| return startInactiveSpan({ | ||
| name: getOperationName(data), | ||
@@ -92,27 +88,12 @@ attributes: { | ||
| } | ||
| }, | ||
| (span) => span | ||
| ); | ||
| }); | ||
| channel.subscribe({ | ||
| start: NOOP, | ||
| asyncStart: NOOP, | ||
| end: NOOP, | ||
| asyncEnd: (data) => { | ||
| if (!data.error) data._sentrySpan?.end(); | ||
| }); | ||
| }, | ||
| error: (data) => { | ||
| const span = data._sentrySpan; | ||
| if (!span) return; | ||
| if (data.error) { | ||
| span.setStatus({ code: SPAN_STATUS_ERROR, message: data.error.message }); | ||
| } | ||
| span.end(); | ||
| } | ||
| }); | ||
| { captureError: false } | ||
| ).unbind; | ||
| } | ||
| function setupConnectChannel(tracingChannel, channelName) { | ||
| const channel = tracingChannel(channelName, (data) => { | ||
| return startSpanManual( | ||
| { | ||
| return bindTracingChannelToSpan( | ||
| tracingChannel(channelName), | ||
| (data) => { | ||
| return startInactiveSpan({ | ||
| name: "redis-connect", | ||
@@ -126,22 +107,6 @@ attributes: { | ||
| } | ||
| }, | ||
| (span) => span | ||
| ); | ||
| }); | ||
| channel.subscribe({ | ||
| start: NOOP, | ||
| asyncStart: NOOP, | ||
| end: NOOP, | ||
| asyncEnd: (data) => { | ||
| if (!data.error) data._sentrySpan?.end(); | ||
| }); | ||
| }, | ||
| error: (data) => { | ||
| const span = data._sentrySpan; | ||
| if (!span) return; | ||
| if (data.error) { | ||
| span.setStatus({ code: SPAN_STATUS_ERROR, message: data.error.message }); | ||
| } | ||
| span.end(); | ||
| } | ||
| }); | ||
| { captureError: false } | ||
| ).unbind; | ||
| } | ||
@@ -148,0 +113,0 @@ function runResponseHook(span, command, args, result) { |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"redis-dc-subscriber.js","sources":["../../../src/redis/redis-dc-subscriber.ts"],"sourcesContent":["import {\n DB_OPERATION_BATCH_SIZE,\n DB_QUERY_TEXT,\n DB_SYSTEM_NAME,\n SERVER_ADDRESS,\n SERVER_PORT,\n} from '@sentry/conventions/attributes';\nimport type { Span } from '@sentry/core';\nimport {\n debug,\n SEMANTIC_ATTRIBUTE_SENTRY_OP,\n SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,\n SPAN_STATUS_ERROR,\n startSpanManual,\n} from '@sentry/core';\nimport { DEBUG_BUILD } from '../debug-build';\n\n// Channel names published by node-redis >= 5.12.0 and ioredis >= 5.11.0.\n// Hardcoded so the subscriber does not have to import either library — the\n// channels just have to be subscribed to before the user's redis code\n// publishes.\nexport const REDIS_DC_CHANNEL_COMMAND = 'node-redis:command';\nexport const REDIS_DC_CHANNEL_BATCH = 'node-redis:batch';\nexport const REDIS_DC_CHANNEL_CONNECT = 'node-redis:connect';\nexport const IOREDIS_DC_CHANNEL_COMMAND = 'ioredis:command';\nexport const IOREDIS_DC_CHANNEL_CONNECT = 'ioredis:connect';\n\nconst ORIGIN = 'auto.db.redis.diagnostic_channel';\nconst DB_SYSTEM_NAME_VALUE_REDIS = 'redis';\n\nconst NOOP = (): void => {};\n\n/**\n * Shape of the `node-redis:command` channel payload published by node-redis.\n *\n * Both `command` and `args` are already redacted by node-redis itself (see\n * `sanitizeArgs` in @redis/client) using the OTel `redis-common` rules. The\n * arg array is `[<command>, <safe arg>, ..., '?', ...]` — `?` replaces any\n * value the library considers sensitive. Subscribers can emit `args` directly\n * as `db.query.text` without further serialization.\n */\nexport interface RedisCommandData {\n command: string;\n /** First arg is the command name itself; consumers should slice it off. */\n args: string[];\n database?: number;\n serverAddress?: string;\n serverPort?: number;\n result?: unknown;\n error?: Error;\n}\n\n/**\n * Shape of the `ioredis:command` channel payload published by ioredis >= 5.11.0.\n *\n * As with node-redis, args are already sanitized by ioredis (`sanitizeArgs` in\n * `lib/tracing.ts`) before publishing. Unlike node-redis, the command name is\n * NOT prepended to `args`.\n */\nexport interface IORedisCommandData {\n command: string;\n args: string[];\n batchMode?: 'MULTI';\n batchSize?: number;\n database?: number;\n serverAddress?: string;\n serverPort?: number;\n result?: unknown;\n error?: Error;\n}\n\n/** Shape of the `node-redis:batch` channel payload published by node-redis. */\nexport interface RedisBatchData {\n batchMode?: 'MULTI' | 'PIPELINE';\n batchSize?: number;\n database?: number;\n clientId?: string | number;\n serverAddress?: string;\n serverPort?: number;\n result?: unknown[];\n error?: Error;\n}\n\n/** Shape of the `*:connect` channel payload published by node-redis and ioredis. */\nexport interface RedisConnectData {\n serverAddress?: string;\n serverPort?: number;\n url?: string;\n error?: Error;\n}\n\n/**\n * Optional callback invoked once the redis command response arrives. Useful\n * for attaching response-derived attributes (e.g. cache hit/miss, payload size).\n *\n * Mirrors `@opentelemetry/instrumentation-ioredis`' response hook so existing\n * Sentry node code (`cacheResponseHook`) can be reused unchanged.\n */\nexport type RedisDiagnosticChannelResponseHook = (\n span: Span,\n cmdName: string,\n cmdArgs: string[],\n result: unknown,\n) => void;\n\n/**\n * Payload type observed by tracing-channel subscribers — the channel payload\n * with `_sentrySpan` stamped on it by the start handler so async/error\n * handlers downstream can read it back.\n */\nexport type RedisTracingChannelContextWithSpan<T> = T & { _sentrySpan?: Span };\n\n/** Subscriber object accepted by {@link RedisTracingChannel.subscribe}. */\nexport interface RedisTracingChannelSubscribers<T> {\n start: (data: RedisTracingChannelContextWithSpan<T>) => void;\n asyncStart: (data: RedisTracingChannelContextWithSpan<T>) => void;\n asyncEnd: (data: RedisTracingChannelContextWithSpan<T>) => void;\n end: (data: RedisTracingChannelContextWithSpan<T>) => void;\n error: (data: RedisTracingChannelContextWithSpan<T>) => void;\n}\n\n/** Minimal tracing-channel surface the subscriber depends on. */\nexport interface RedisTracingChannel<T extends object> {\n subscribe(subs: Partial<RedisTracingChannelSubscribers<T>>): void;\n}\n\n/**\n * Platform-provided factory that returns a tracing channel for the given\n * channel name. Implementations are responsible for ensuring that, when the\n * channel's `start` event fires, the span returned by `transformStart(data)`\n * ends up stored on `data._sentrySpan` so the subscriber's `asyncEnd`/`error`\n * handlers can read it.\n *\n * - Node passes `@sentry/opentelemetry/tracing-channel` which uses\n * `bindStore` to also propagate the span as the active OTel context.\n * - Deno (and other non-OTel runtimes) pass a portable wrapper around\n * `node:diagnostics_channel.tracingChannel` that just stamps\n * `data._sentrySpan` in `start` without `bindStore`.\n */\nexport type RedisTracingChannelFactory = <T extends object>(\n name: string,\n transformStart: (data: T) => Span,\n) => RedisTracingChannel<T>;\n\nlet subscribed = false;\nlet currentResponseHook: RedisDiagnosticChannelResponseHook | undefined;\n\n/**\n * Subscribe Sentry span handlers to node-redis and ioredis diagnostics-channel\n * events: `node-redis:command`/`:batch`/`:connect` (published by node-redis\n * >= 5.12.0) and `ioredis:command`/`:connect` (published by ioredis >= 5.11.0).\n *\n * On older client versions the channels are never published to, so subscribers\n * are inert — there is no double-instrumentation against any IITM-based\n * patcher gated to those older versions.\n *\n * Idempotent: subsequent calls update the response hook but do not\n * re-subscribe.\n */\nexport function subscribeRedisDiagnosticChannels(\n tracingChannel: RedisTracingChannelFactory,\n responseHook?: RedisDiagnosticChannelResponseHook,\n): void {\n currentResponseHook = responseHook;\n if (subscribed) return;\n subscribed = true;\n\n try {\n // node-redis: command name appears as args[0] in the channel payload, so\n // strip it before the statement and response hook see it.\n setupCommandChannel<RedisCommandData>(tracingChannel, REDIS_DC_CHANNEL_COMMAND, data => data.args.slice(1));\n setupBatchChannel(tracingChannel, REDIS_DC_CHANNEL_BATCH, data =>\n data.batchMode === 'PIPELINE' ? 'PIPELINE' : 'MULTI',\n );\n setupConnectChannel(tracingChannel, REDIS_DC_CHANNEL_CONNECT);\n\n // ioredis: args already exclude the command name; no slicing needed. And\n // ioredis has no separate batch channel — pipeline/MULTI metadata rides\n // on the per-command payload via `batchMode`/`batchSize`.\n setupCommandChannel<IORedisCommandData>(tracingChannel, IOREDIS_DC_CHANNEL_COMMAND, data => data.args);\n setupConnectChannel(tracingChannel, IOREDIS_DC_CHANNEL_CONNECT);\n } catch {\n // The factory may rely on `node:diagnostics_channel`, which isn't always\n // available. Fail closed; the SDK simply won't emit redis spans here.\n DEBUG_BUILD && debug.log('Redis node:diagnostics_channel subscription failed.');\n }\n}\n\nfunction setupCommandChannel<T extends RedisCommandData | IORedisCommandData>(\n tracingChannel: RedisTracingChannelFactory,\n channelName: string,\n getCommandArgs: (data: T) => string[],\n): void {\n const channel = tracingChannel<T>(channelName, data => {\n // `args` is already sanitized by the publishing library (node-redis /\n // ioredis call their own `sanitizeArgs` before publishing). Join with\n // spaces to mirror the format the libraries themselves intend.\n const args = getCommandArgs(data);\n const statement = args.length ? `${data.command} ${args.join(' ')}` : data.command;\n return startSpanManual(\n {\n name: `redis-${data.command}`,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ORIGIN,\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'db.redis',\n [DB_SYSTEM_NAME]: DB_SYSTEM_NAME_VALUE_REDIS,\n [DB_QUERY_TEXT]: statement,\n ...(data.serverAddress != null ? { [SERVER_ADDRESS]: data.serverAddress } : {}),\n ...(data.serverPort != null ? { [SERVER_PORT]: data.serverPort } : {}),\n },\n },\n span => span,\n );\n });\n\n channel.subscribe({\n start: NOOP,\n asyncStart: NOOP,\n end: NOOP,\n asyncEnd: data => {\n const span = data._sentrySpan;\n // Only end here if the error handler isn't going to.\n if (!span || data.error) return;\n runResponseHook(span, data.command, getCommandArgs(data), data.result);\n span.end();\n },\n error: data => {\n const span = data._sentrySpan;\n if (!span) return;\n if (data.error) {\n span.setStatus({ code: SPAN_STATUS_ERROR, message: data.error.message });\n }\n span.end();\n },\n });\n}\n\nfunction setupBatchChannel(\n tracingChannel: RedisTracingChannelFactory,\n channelName: string,\n getOperationName: (data: RedisBatchData) => string,\n): void {\n const channel = tracingChannel<RedisBatchData>(channelName, data => {\n return startSpanManual(\n {\n name: getOperationName(data),\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ORIGIN,\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'db.redis',\n [DB_SYSTEM_NAME]: DB_SYSTEM_NAME_VALUE_REDIS,\n // should only include batch size greater than 1,\n // or else it isn't properly considered a \"batch\"\n ...(Number(data.batchSize) > 1 ? { [DB_OPERATION_BATCH_SIZE]: data.batchSize } : {}),\n ...(data.serverAddress != null ? { [SERVER_ADDRESS]: data.serverAddress } : {}),\n ...(data.serverPort != null ? { [SERVER_PORT]: data.serverPort } : {}),\n },\n },\n span => span,\n );\n });\n\n channel.subscribe({\n start: NOOP,\n asyncStart: NOOP,\n end: NOOP,\n asyncEnd: data => {\n if (!data.error) data._sentrySpan?.end();\n },\n error: data => {\n const span = data._sentrySpan;\n if (!span) return;\n if (data.error) {\n span.setStatus({ code: SPAN_STATUS_ERROR, message: data.error.message });\n }\n span.end();\n },\n });\n}\n\nfunction setupConnectChannel(tracingChannel: RedisTracingChannelFactory, channelName: string): void {\n const channel = tracingChannel<RedisConnectData>(channelName, data => {\n return startSpanManual(\n {\n name: 'redis-connect',\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ORIGIN,\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'db.redis.connect',\n [DB_SYSTEM_NAME]: DB_SYSTEM_NAME_VALUE_REDIS,\n ...(data.serverAddress != null ? { [SERVER_ADDRESS]: data.serverAddress } : {}),\n ...(data.serverPort != null ? { [SERVER_PORT]: data.serverPort } : {}),\n },\n },\n span => span,\n );\n });\n\n channel.subscribe({\n start: NOOP,\n asyncStart: NOOP,\n end: NOOP,\n asyncEnd: data => {\n if (!data.error) data._sentrySpan?.end();\n },\n error: data => {\n const span = data._sentrySpan;\n if (!span) return;\n if (data.error) {\n span.setStatus({ code: SPAN_STATUS_ERROR, message: data.error.message });\n }\n span.end();\n },\n });\n}\n\nfunction runResponseHook(span: Span, command: string, args: string[], result: unknown): void {\n const hook = currentResponseHook;\n if (!hook) return;\n try {\n hook(span, command, args, result);\n } catch {\n // never let user hooks break instrumentation\n }\n}\n\n/** Test-only: reset module-local subscribe state. */\nexport function _resetRedisDiagnosticChannelsForTesting(): void {\n subscribed = false;\n currentResponseHook = undefined;\n}\n"],"names":[],"mappings":"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"} | ||
| {"version":3,"file":"redis-dc-subscriber.js","sources":["../../../src/redis/redis-dc-subscriber.ts"],"sourcesContent":["import type { TracingChannel } from 'node:diagnostics_channel';\nimport {\n DB_OPERATION_BATCH_SIZE,\n DB_QUERY_TEXT,\n DB_SYSTEM_NAME,\n SERVER_ADDRESS,\n SERVER_PORT,\n} from '@sentry/conventions/attributes';\nimport type { Span } from '@sentry/core';\nimport { debug, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, startInactiveSpan } from '@sentry/core';\nimport { DEBUG_BUILD } from '../debug-build';\nimport { bindTracingChannelToSpan } from '../tracing-channel';\n\n// Channel names published by node-redis >= 5.12.0 and ioredis >= 5.11.0.\n// Hardcoded so the subscriber does not have to import either library — the\n// channels just have to be subscribed to before the user's redis code\n// publishes.\nexport const REDIS_DC_CHANNEL_COMMAND = 'node-redis:command';\nexport const REDIS_DC_CHANNEL_BATCH = 'node-redis:batch';\nexport const REDIS_DC_CHANNEL_CONNECT = 'node-redis:connect';\nexport const IOREDIS_DC_CHANNEL_COMMAND = 'ioredis:command';\nexport const IOREDIS_DC_CHANNEL_CONNECT = 'ioredis:connect';\n\nconst ORIGIN = 'auto.db.redis.diagnostic_channel';\nconst DB_SYSTEM_NAME_VALUE_REDIS = 'redis';\n\n/**\n * Shape of the `node-redis:command` channel payload published by node-redis.\n *\n * Both `command` and `args` are already redacted by node-redis itself (see\n * `sanitizeArgs` in @redis/client) using the OTel `redis-common` rules. The\n * arg array is `[<command>, <safe arg>, ..., '?', ...]` — `?` replaces any\n * value the library considers sensitive. Subscribers can emit `args` directly\n * as `db.query.text` without further serialization.\n */\nexport interface RedisCommandData {\n command: string;\n /** First arg is the command name itself; consumers should slice it off. */\n args: string[];\n database?: number;\n serverAddress?: string;\n serverPort?: number;\n result?: unknown;\n error?: Error;\n}\n\n/**\n * Shape of the `ioredis:command` channel payload published by ioredis >= 5.11.0.\n *\n * As with node-redis, args are already sanitized by ioredis (`sanitizeArgs` in\n * `lib/tracing.ts`) before publishing. Unlike node-redis, the command name is\n * NOT prepended to `args`.\n */\nexport interface IORedisCommandData {\n command: string;\n args: string[];\n batchMode?: 'MULTI';\n batchSize?: number;\n database?: number;\n serverAddress?: string;\n serverPort?: number;\n result?: unknown;\n error?: Error;\n}\n\n/** Shape of the `node-redis:batch` channel payload published by node-redis. */\nexport interface RedisBatchData {\n batchMode?: 'MULTI' | 'PIPELINE';\n batchSize?: number;\n database?: number;\n clientId?: string | number;\n serverAddress?: string;\n serverPort?: number;\n result?: unknown[];\n error?: Error;\n}\n\n/** Shape of the `*:connect` channel payload published by node-redis and ioredis. */\nexport interface RedisConnectData {\n serverAddress?: string;\n serverPort?: number;\n url?: string;\n error?: Error;\n}\n\n/**\n * Optional callback invoked once the redis command response arrives. Useful\n * for attaching response-derived attributes (e.g. cache hit/miss, payload size).\n *\n * Mirrors `@opentelemetry/instrumentation-ioredis`' response hook so existing\n * Sentry node code (`cacheResponseHook`) can be reused unchanged.\n */\nexport type RedisDiagnosticChannelResponseHook = (\n span: Span,\n cmdName: string,\n cmdArgs: string[],\n result: unknown,\n) => void;\n\n/**\n * Platform-provided factory that creates a native tracing channel for the given name. The\n * subscriber binds the span and its lifecycle onto the channel via `bindTracingChannelToSpan`,\n * which propagates the active span through the runtime's async context.\n *\n * Both Node and Deno pass `node:diagnostics_channel`'s `tracingChannel` directly.\n */\nexport type RedisTracingChannelFactory = <T extends object>(name: string) => TracingChannel<T, T>;\n\nlet subscribed = false;\nlet currentResponseHook: RedisDiagnosticChannelResponseHook | undefined;\nlet activeUnbinds: Array<() => void> = [];\n\n/**\n * Subscribe Sentry span handlers to node-redis and ioredis diagnostics-channel\n * events: `node-redis:command`/`:batch`/`:connect` (published by node-redis\n * >= 5.12.0) and `ioredis:command`/`:connect` (published by ioredis >= 5.11.0).\n *\n * On older client versions the channels are never published to, so subscribers\n * are inert — there is no double-instrumentation against any IITM-based\n * patcher gated to those older versions.\n *\n * Idempotent: subsequent calls update the response hook but do not\n * re-subscribe.\n */\nexport function subscribeRedisDiagnosticChannels(\n tracingChannel: RedisTracingChannelFactory,\n responseHook?: RedisDiagnosticChannelResponseHook,\n): void {\n currentResponseHook = responseHook;\n if (subscribed) return;\n subscribed = true;\n\n try {\n // node-redis: command name appears as args[0] in the channel payload, so\n // strip it before the statement and response hook see it.\n activeUnbinds.push(\n setupCommandChannel<RedisCommandData>(tracingChannel, REDIS_DC_CHANNEL_COMMAND, data => data.args.slice(1)),\n setupBatchChannel(tracingChannel, REDIS_DC_CHANNEL_BATCH, data =>\n data.batchMode === 'PIPELINE' ? 'PIPELINE' : 'MULTI',\n ),\n setupConnectChannel(tracingChannel, REDIS_DC_CHANNEL_CONNECT),\n // ioredis: args already exclude the command name; no slicing needed. And\n // ioredis has no separate batch channel — pipeline/MULTI metadata rides\n // on the per-command payload via `batchMode`/`batchSize`.\n setupCommandChannel<IORedisCommandData>(tracingChannel, IOREDIS_DC_CHANNEL_COMMAND, data => data.args),\n setupConnectChannel(tracingChannel, IOREDIS_DC_CHANNEL_CONNECT),\n );\n } catch {\n // The factory may rely on `node:diagnostics_channel`, which isn't always\n // available. Fail closed; the SDK simply won't emit redis spans here.\n DEBUG_BUILD && debug.log('Redis node:diagnostics_channel subscription failed.');\n }\n}\n\nfunction setupCommandChannel<T extends RedisCommandData | IORedisCommandData>(\n tracingChannel: RedisTracingChannelFactory,\n channelName: string,\n getCommandArgs: (data: T) => string[],\n): () => void {\n return bindTracingChannelToSpan(\n tracingChannel<T>(channelName),\n data => {\n // `args` is already sanitized by the publishing library (node-redis /\n // ioredis call their own `sanitizeArgs` before publishing). Join with\n // spaces to mirror the format the libraries themselves intend.\n const args = getCommandArgs(data);\n const statement = args.length ? `${data.command} ${args.join(' ')}` : data.command;\n return startInactiveSpan({\n name: `redis-${data.command}`,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ORIGIN,\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'db.redis',\n [DB_SYSTEM_NAME]: DB_SYSTEM_NAME_VALUE_REDIS,\n [DB_QUERY_TEXT]: statement,\n ...(data.serverAddress != null ? { [SERVER_ADDRESS]: data.serverAddress } : {}),\n ...(data.serverPort != null ? { [SERVER_PORT]: data.serverPort } : {}),\n },\n });\n },\n {\n // Command failures are surfaced to (and usually handled by) the caller; only annotate the\n // span so we don't emit a duplicate error event for every failed command.\n captureError: false,\n beforeSpanEnd(span, data) {\n if ('error' in data) return;\n runResponseHook(span, data.command, getCommandArgs(data), data.result);\n },\n },\n ).unbind;\n}\n\nfunction setupBatchChannel(\n tracingChannel: RedisTracingChannelFactory,\n channelName: string,\n getOperationName: (data: RedisBatchData) => string,\n): () => void {\n return bindTracingChannelToSpan(\n tracingChannel<RedisBatchData>(channelName),\n data => {\n return startInactiveSpan({\n name: getOperationName(data),\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ORIGIN,\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'db.redis',\n [DB_SYSTEM_NAME]: DB_SYSTEM_NAME_VALUE_REDIS,\n // should only include batch size greater than 1,\n // or else it isn't properly considered a \"batch\"\n ...(Number(data.batchSize) > 1 ? { [DB_OPERATION_BATCH_SIZE]: data.batchSize } : {}),\n ...(data.serverAddress != null ? { [SERVER_ADDRESS]: data.serverAddress } : {}),\n ...(data.serverPort != null ? { [SERVER_PORT]: data.serverPort } : {}),\n },\n });\n },\n { captureError: false },\n ).unbind;\n}\n\nfunction setupConnectChannel(tracingChannel: RedisTracingChannelFactory, channelName: string): () => void {\n return bindTracingChannelToSpan(\n tracingChannel<RedisConnectData>(channelName),\n data => {\n return startInactiveSpan({\n name: 'redis-connect',\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ORIGIN,\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'db.redis.connect',\n [DB_SYSTEM_NAME]: DB_SYSTEM_NAME_VALUE_REDIS,\n ...(data.serverAddress != null ? { [SERVER_ADDRESS]: data.serverAddress } : {}),\n ...(data.serverPort != null ? { [SERVER_PORT]: data.serverPort } : {}),\n },\n });\n },\n { captureError: false },\n ).unbind;\n}\n\nfunction runResponseHook(span: Span, command: string, args: string[], result: unknown): void {\n const hook = currentResponseHook;\n if (!hook) return;\n try {\n hook(span, command, args, result);\n } catch {\n // never let user hooks break instrumentation\n }\n}\n\n/** Test-only: detach all channel bindings and reset module-local subscribe state. */\nexport function _resetRedisDiagnosticChannelsForTesting(): void {\n activeUnbinds.forEach(unbind => unbind());\n activeUnbinds = [];\n subscribed = false;\n currentResponseHook = undefined;\n}\n"],"names":[],"mappings":"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"} |
@@ -7,3 +7,5 @@ /** | ||
| export { IOREDIS_DC_CHANNEL_COMMAND, IOREDIS_DC_CHANNEL_CONNECT, REDIS_DC_CHANNEL_BATCH, REDIS_DC_CHANNEL_COMMAND, REDIS_DC_CHANNEL_CONNECT, subscribeRedisDiagnosticChannels, } from './redis/redis-dc-subscriber'; | ||
| export { IORedisCommandData, RedisBatchData, RedisCommandData, RedisConnectData, RedisDiagnosticChannelResponseHook, RedisTracingChannel, RedisTracingChannelContextWithSpan, RedisTracingChannelFactory, RedisTracingChannelSubscribers, } from './redis/redis-dc-subscriber'; | ||
| export { IORedisCommandData, RedisBatchData, RedisCommandData, RedisConnectData, RedisDiagnosticChannelResponseHook, RedisTracingChannelFactory, } from './redis/redis-dc-subscriber'; | ||
| export { bindTracingChannelToSpan } from './tracing-channel'; | ||
| export { SentryTracingChannel, TracingChannelLifeCycleOptions, TracingChannelBindingHandle, TracingChannelPayloadWithSpan, } from './tracing-channel'; | ||
| //# sourceMappingURL=index.d.ts.map |
@@ -0,1 +1,2 @@ | ||
| import { TracingChannel } from 'node:diagnostics_channel'; | ||
| import { Span } from '@sentry/core'; | ||
@@ -71,35 +72,9 @@ export declare const REDIS_DC_CHANNEL_COMMAND = "node-redis:command"; | ||
| /** | ||
| * Payload type observed by tracing-channel subscribers — the channel payload | ||
| * with `_sentrySpan` stamped on it by the start handler so async/error | ||
| * handlers downstream can read it back. | ||
| */ | ||
| export type RedisTracingChannelContextWithSpan<T> = T & { | ||
| _sentrySpan?: Span; | ||
| }; | ||
| /** Subscriber object accepted by {@link RedisTracingChannel.subscribe}. */ | ||
| export interface RedisTracingChannelSubscribers<T> { | ||
| start: (data: RedisTracingChannelContextWithSpan<T>) => void; | ||
| asyncStart: (data: RedisTracingChannelContextWithSpan<T>) => void; | ||
| asyncEnd: (data: RedisTracingChannelContextWithSpan<T>) => void; | ||
| end: (data: RedisTracingChannelContextWithSpan<T>) => void; | ||
| error: (data: RedisTracingChannelContextWithSpan<T>) => void; | ||
| } | ||
| /** Minimal tracing-channel surface the subscriber depends on. */ | ||
| export interface RedisTracingChannel<T extends object> { | ||
| subscribe(subs: Partial<RedisTracingChannelSubscribers<T>>): void; | ||
| } | ||
| /** | ||
| * Platform-provided factory that returns a tracing channel for the given | ||
| * channel name. Implementations are responsible for ensuring that, when the | ||
| * channel's `start` event fires, the span returned by `transformStart(data)` | ||
| * ends up stored on `data._sentrySpan` so the subscriber's `asyncEnd`/`error` | ||
| * handlers can read it. | ||
| * Platform-provided factory that creates a native tracing channel for the given name. The | ||
| * subscriber binds the span and its lifecycle onto the channel via `bindTracingChannelToSpan`, | ||
| * which propagates the active span through the runtime's async context. | ||
| * | ||
| * - Node passes `@sentry/opentelemetry/tracing-channel` which uses | ||
| * `bindStore` to also propagate the span as the active OTel context. | ||
| * - Deno (and other non-OTel runtimes) pass a portable wrapper around | ||
| * `node:diagnostics_channel.tracingChannel` that just stamps | ||
| * `data._sentrySpan` in `start` without `bindStore`. | ||
| * Both Node and Deno pass `node:diagnostics_channel`'s `tracingChannel` directly. | ||
| */ | ||
| export type RedisTracingChannelFactory = <T extends object>(name: string, transformStart: (data: T) => Span) => RedisTracingChannel<T>; | ||
| export type RedisTracingChannelFactory = <T extends object>(name: string) => TracingChannel<T, T>; | ||
| /** | ||
@@ -118,4 +93,4 @@ * Subscribe Sentry span handlers to node-redis and ioredis diagnostics-channel | ||
| export declare function subscribeRedisDiagnosticChannels(tracingChannel: RedisTracingChannelFactory, responseHook?: RedisDiagnosticChannelResponseHook): void; | ||
| /** Test-only: reset module-local subscribe state. */ | ||
| /** Test-only: detach all channel bindings and reset module-local subscribe state. */ | ||
| export declare function _resetRedisDiagnosticChannelsForTesting(): void; | ||
| //# sourceMappingURL=redis-dc-subscriber.d.ts.map |
@@ -7,3 +7,5 @@ /** | ||
| export { IOREDIS_DC_CHANNEL_COMMAND, IOREDIS_DC_CHANNEL_CONNECT, REDIS_DC_CHANNEL_BATCH, REDIS_DC_CHANNEL_COMMAND, REDIS_DC_CHANNEL_CONNECT, subscribeRedisDiagnosticChannels, } from './redis/redis-dc-subscriber'; | ||
| export type { IORedisCommandData, RedisBatchData, RedisCommandData, RedisConnectData, RedisDiagnosticChannelResponseHook, RedisTracingChannel, RedisTracingChannelContextWithSpan, RedisTracingChannelFactory, RedisTracingChannelSubscribers, } from './redis/redis-dc-subscriber'; | ||
| export type { IORedisCommandData, RedisBatchData, RedisCommandData, RedisConnectData, RedisDiagnosticChannelResponseHook, RedisTracingChannelFactory, } from './redis/redis-dc-subscriber'; | ||
| export { bindTracingChannelToSpan } from './tracing-channel'; | ||
| export type { SentryTracingChannel, TracingChannelLifeCycleOptions, TracingChannelBindingHandle, TracingChannelPayloadWithSpan, } from './tracing-channel'; | ||
| //# sourceMappingURL=index.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,0BAA0B,EAC1B,0BAA0B,EAC1B,sBAAsB,EACtB,wBAAwB,EACxB,wBAAwB,EACxB,gCAAgC,GACjC,MAAM,6BAA6B,CAAC;AACrC,YAAY,EACV,kBAAkB,EAClB,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAChB,kCAAkC,EAClC,mBAAmB,EACnB,kCAAkC,EAClC,0BAA0B,EAC1B,8BAA8B,GAC/B,MAAM,6BAA6B,CAAC"} | ||
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,0BAA0B,EAC1B,0BAA0B,EAC1B,sBAAsB,EACtB,wBAAwB,EACxB,wBAAwB,EACxB,gCAAgC,GACjC,MAAM,6BAA6B,CAAC;AACrC,YAAY,EACV,kBAAkB,EAClB,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAChB,kCAAkC,EAClC,0BAA0B,GAC3B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAC7D,YAAY,EACV,oBAAoB,EACpB,8BAA8B,EAC9B,2BAA2B,EAC3B,6BAA6B,GAC9B,MAAM,mBAAmB,CAAC"} |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"mysql.d.ts","sourceRoot":"","sources":["../../../../src/integrations/tracing-channel/mysql.ts"],"names":[],"mappings":"AAkRA;;;;;;;GAOG;AACH,eAAO,MAAM,uBAAuB,0CAA8C,CAAC"} | ||
| {"version":3,"file":"mysql.d.ts","sourceRoot":"","sources":["../../../../src/integrations/tracing-channel/mysql.ts"],"names":[],"mappings":"AAoRA;;;;;;;GAOG;AACH,eAAO,MAAM,uBAAuB,0CAA8C,CAAC"} |
@@ -0,1 +1,2 @@ | ||
| import type { TracingChannel } from 'node:diagnostics_channel'; | ||
| import type { Span } from '@sentry/core'; | ||
@@ -71,35 +72,9 @@ export declare const REDIS_DC_CHANNEL_COMMAND = "node-redis:command"; | ||
| /** | ||
| * Payload type observed by tracing-channel subscribers — the channel payload | ||
| * with `_sentrySpan` stamped on it by the start handler so async/error | ||
| * handlers downstream can read it back. | ||
| */ | ||
| export type RedisTracingChannelContextWithSpan<T> = T & { | ||
| _sentrySpan?: Span; | ||
| }; | ||
| /** Subscriber object accepted by {@link RedisTracingChannel.subscribe}. */ | ||
| export interface RedisTracingChannelSubscribers<T> { | ||
| start: (data: RedisTracingChannelContextWithSpan<T>) => void; | ||
| asyncStart: (data: RedisTracingChannelContextWithSpan<T>) => void; | ||
| asyncEnd: (data: RedisTracingChannelContextWithSpan<T>) => void; | ||
| end: (data: RedisTracingChannelContextWithSpan<T>) => void; | ||
| error: (data: RedisTracingChannelContextWithSpan<T>) => void; | ||
| } | ||
| /** Minimal tracing-channel surface the subscriber depends on. */ | ||
| export interface RedisTracingChannel<T extends object> { | ||
| subscribe(subs: Partial<RedisTracingChannelSubscribers<T>>): void; | ||
| } | ||
| /** | ||
| * Platform-provided factory that returns a tracing channel for the given | ||
| * channel name. Implementations are responsible for ensuring that, when the | ||
| * channel's `start` event fires, the span returned by `transformStart(data)` | ||
| * ends up stored on `data._sentrySpan` so the subscriber's `asyncEnd`/`error` | ||
| * handlers can read it. | ||
| * Platform-provided factory that creates a native tracing channel for the given name. The | ||
| * subscriber binds the span and its lifecycle onto the channel via `bindTracingChannelToSpan`, | ||
| * which propagates the active span through the runtime's async context. | ||
| * | ||
| * - Node passes `@sentry/opentelemetry/tracing-channel` which uses | ||
| * `bindStore` to also propagate the span as the active OTel context. | ||
| * - Deno (and other non-OTel runtimes) pass a portable wrapper around | ||
| * `node:diagnostics_channel.tracingChannel` that just stamps | ||
| * `data._sentrySpan` in `start` without `bindStore`. | ||
| * Both Node and Deno pass `node:diagnostics_channel`'s `tracingChannel` directly. | ||
| */ | ||
| export type RedisTracingChannelFactory = <T extends object>(name: string, transformStart: (data: T) => Span) => RedisTracingChannel<T>; | ||
| export type RedisTracingChannelFactory = <T extends object>(name: string) => TracingChannel<T, T>; | ||
| /** | ||
@@ -118,4 +93,4 @@ * Subscribe Sentry span handlers to node-redis and ioredis diagnostics-channel | ||
| export declare function subscribeRedisDiagnosticChannels(tracingChannel: RedisTracingChannelFactory, responseHook?: RedisDiagnosticChannelResponseHook): void; | ||
| /** Test-only: reset module-local subscribe state. */ | ||
| /** Test-only: detach all channel bindings and reset module-local subscribe state. */ | ||
| export declare function _resetRedisDiagnosticChannelsForTesting(): void; | ||
| //# sourceMappingURL=redis-dc-subscriber.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"redis-dc-subscriber.d.ts","sourceRoot":"","sources":["../../../src/redis/redis-dc-subscriber.ts"],"names":[],"mappings":"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"} | ||
| {"version":3,"file":"redis-dc-subscriber.d.ts","sourceRoot":"","sources":["../../../src/redis/redis-dc-subscriber.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAQ/D,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AASzC,eAAO,MAAM,wBAAwB,uBAAuB,CAAC;AAC7D,eAAO,MAAM,sBAAsB,qBAAqB,CAAC;AACzD,eAAO,MAAM,wBAAwB,uBAAuB,CAAC;AAC7D,eAAO,MAAM,0BAA0B,oBAAoB,CAAC;AAC5D,eAAO,MAAM,0BAA0B,oBAAoB,CAAC;AAK5D;;;;;;;;GAQG;AACH,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,2EAA2E;IAC3E,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,KAAK,CAAC;CACf;AAED;;;;;;GAMG;AACH,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,KAAK,CAAC;CACf;AAED,+EAA+E;AAC/E,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,EAAE,OAAO,GAAG,UAAU,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC;CACf;AAED,oFAAoF;AACpF,MAAM,WAAW,gBAAgB;IAC/B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,KAAK,CAAC;CACf;AAED;;;;;;GAMG;AACH,MAAM,MAAM,kCAAkC,GAAG,CAC/C,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EAAE,EACjB,MAAM,EAAE,OAAO,KACZ,IAAI,CAAC;AAEV;;;;;;GAMG;AACH,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,SAAS,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAMlG;;;;;;;;;;;GAWG;AACH,wBAAgB,gCAAgC,CAC9C,cAAc,EAAE,0BAA0B,EAC1C,YAAY,CAAC,EAAE,kCAAkC,GAChD,IAAI,CAyBN;AA8FD,qFAAqF;AACrF,wBAAgB,uCAAuC,IAAI,IAAI,CAK9D"} |
+2
-2
| { | ||
| "name": "@sentry/server-utils", | ||
| "version": "10.60.0", | ||
| "version": "10.61.0", | ||
| "description": "Server Utilities for all Sentry JavaScript SDKs", | ||
@@ -89,3 +89,3 @@ "repository": "git://github.com/getsentry/sentry-javascript.git", | ||
| "@sentry/conventions": "^0.12.0", | ||
| "@sentry/core": "10.60.0", | ||
| "@sentry/core": "10.61.0", | ||
| "magic-string": "~0.30.0" | ||
@@ -92,0 +92,0 @@ }, |
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
209965
13.35%81
9.46%1601
11.96%9
28.57%+ Added
- Removed
Updated