Skip to content

Commit f1723da

Browse files
nicohrubecclaude
andauthored
ref(node): Vendor @prisma/instrumentation (#21098)
Vendors `@prisma/instrumentation@7.8.0` and the required parts of `@prisma/instrumentation-contract@7.8.0` into the SDK with no logic changes. Closes #20164 --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent fb78a25 commit f1723da

12 files changed

Lines changed: 445 additions & 30 deletions

File tree

.oxlintrc.base.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@
157157
"**/integrations/tracing/hapi/vendored/**/*.ts",
158158
"**/integrations/tracing/mongoose/vendored/**/*.ts",
159159
"**/integrations/tracing/amqplib/vendored/**/*.ts",
160+
"**/integrations/tracing/prisma/vendored/**/*.ts",
160161
"**/integrations/tracing/graphql/vendored/**/*.ts"
161162
],
162163
"rules": {

packages/node/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@
7373
"@opentelemetry/instrumentation-pg": "0.66.0",
7474
"@opentelemetry/sdk-trace-base": "^2.6.1",
7575
"@opentelemetry/semantic-conventions": "^1.40.0",
76-
"@prisma/instrumentation": "7.6.0",
7776
"@fastify/otel": "0.18.0",
7877
"@sentry/core": "10.53.1",
7978
"@sentry/node-core": "10.53.1",

packages/node/src/integrations/tracing/prisma.ts renamed to packages/node/src/integrations/tracing/prisma/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ import type { Link, Tracer } from '@opentelemetry/api';
22
import { context, SpanKind, trace, TraceFlags } from '@opentelemetry/api';
33
import type { Instrumentation } from '@opentelemetry/instrumentation';
44
import type { IdGenerator } from '@opentelemetry/sdk-trace-base';
5-
import { PrismaInstrumentation } from '@prisma/instrumentation';
65
import { consoleSandbox, defineIntegration, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, spanToJSON } from '@sentry/core';
76
import { generateInstrumentOnce } from '@sentry/node-core';
8-
import type { PrismaV5TracingHelper } from './prisma/vendor/v5-tracing-helper';
9-
import type { PrismaV6TracingHelper } from './prisma/vendor/v6-tracing-helper';
7+
import { PrismaInstrumentation } from './vendored/instrumentation';
8+
import type { PrismaV5TracingHelper } from './vendored/v5-tracing-helper';
9+
import type { PrismaV6TracingHelper } from './vendored/v6-tracing-helper';
1010

1111
const INTEGRATION_NAME = 'Prisma';
1212

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
/*
2+
* Copyright Prisma
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* NOTICE from the Sentry authors:
17+
* - Vendored from: https://github.com/prisma/prisma/tree/b6feea5565ec577545a79547d24273ccdd11b4c7/packages/instrumentation
18+
* - Upstream version: @prisma/instrumentation@7.8.0
19+
* - Replaced `@prisma/instrumentation-contract` imports with local vendored types
20+
* - Minor TypeScript strictness adjustments for this repository's compiler settings
21+
*/
22+
/* eslint-disable */
23+
24+
import {
25+
Attributes,
26+
Context,
27+
context as _context,
28+
Span,
29+
SpanKind,
30+
SpanOptions,
31+
trace,
32+
Tracer,
33+
TracerProvider,
34+
} from '@opentelemetry/api';
35+
import type { EngineSpan, EngineSpanKind, ExtendedSpanOptions, SpanCallback, TracingHelper } from './types';
36+
37+
const showAllTraces = process.env.PRISMA_SHOW_ALL_TRACES === 'true';
38+
39+
const nonSampledTraceParent = `00-10-10-00`;
40+
41+
type Options = {
42+
tracerProvider: TracerProvider;
43+
ignoreSpanTypes: (string | RegExp)[];
44+
};
45+
46+
function engineSpanKindToOtelSpanKind(engineSpanKind: EngineSpanKind): SpanKind {
47+
switch (engineSpanKind) {
48+
case 'client':
49+
return SpanKind.CLIENT;
50+
case 'internal':
51+
default:
52+
return SpanKind.INTERNAL;
53+
}
54+
}
55+
56+
export class ActiveTracingHelper implements TracingHelper {
57+
private tracerProvider: TracerProvider;
58+
private ignoreSpanTypes: (string | RegExp)[];
59+
60+
constructor({ tracerProvider, ignoreSpanTypes }: Options) {
61+
this.tracerProvider = tracerProvider;
62+
this.ignoreSpanTypes = ignoreSpanTypes;
63+
}
64+
65+
isEnabled(): boolean {
66+
return true;
67+
}
68+
69+
getTraceParent(context?: Context | undefined): string {
70+
const span = trace.getSpanContext(context ?? _context.active());
71+
if (span) {
72+
return `00-${span.traceId}-${span.spanId}-0${span.traceFlags}`;
73+
}
74+
return nonSampledTraceParent;
75+
}
76+
77+
dispatchEngineSpans(spans: EngineSpan[]): void {
78+
const tracer = this.tracerProvider.getTracer('prisma');
79+
const linkIds = new Map<string, string>();
80+
const roots = spans.filter(span => span.parentId === null);
81+
82+
for (const root of roots) {
83+
dispatchEngineSpan(tracer, root, spans, linkIds, this.ignoreSpanTypes);
84+
}
85+
}
86+
87+
getActiveContext(): Context | undefined {
88+
return _context.active();
89+
}
90+
91+
runInChildSpan<R>(options: string | ExtendedSpanOptions, callback: SpanCallback<R>): R {
92+
if (typeof options === 'string') {
93+
options = { name: options };
94+
}
95+
96+
if (options.internal && !showAllTraces) {
97+
return callback();
98+
}
99+
100+
const tracer = this.tracerProvider.getTracer('prisma');
101+
const context = options.context ?? this.getActiveContext();
102+
const name = `prisma:client:${options.name}`;
103+
104+
if (shouldIgnoreSpan(name, this.ignoreSpanTypes)) {
105+
return callback();
106+
}
107+
108+
if (options.active === false) {
109+
const span = tracer.startSpan(name, options, context);
110+
return endSpan(span, callback(span, context));
111+
}
112+
113+
return tracer.startActiveSpan(name, options, span => endSpan(span, callback(span, context)));
114+
}
115+
}
116+
117+
function dispatchEngineSpan(
118+
tracer: Tracer,
119+
engineSpan: EngineSpan,
120+
allSpans: EngineSpan[],
121+
linkIds: Map<string, string>,
122+
ignoreSpanTypes: (string | RegExp)[],
123+
) {
124+
if (shouldIgnoreSpan(engineSpan.name, ignoreSpanTypes)) return;
125+
126+
const spanOptions = {
127+
attributes: engineSpan.attributes as Attributes,
128+
kind: engineSpanKindToOtelSpanKind(engineSpan.kind),
129+
startTime: engineSpan.startTime,
130+
} satisfies SpanOptions;
131+
132+
tracer.startActiveSpan(engineSpan.name, spanOptions, span => {
133+
linkIds.set(engineSpan.id, span.spanContext().spanId);
134+
135+
if (engineSpan.links) {
136+
span.addLinks(
137+
engineSpan.links.flatMap(link => {
138+
const linkedId = linkIds.get(link);
139+
if (!linkedId) {
140+
return [];
141+
}
142+
return {
143+
context: {
144+
spanId: linkedId,
145+
traceId: span.spanContext().traceId,
146+
traceFlags: span.spanContext().traceFlags,
147+
},
148+
};
149+
}),
150+
);
151+
}
152+
153+
const children = allSpans.filter(s => s.parentId === engineSpan.id);
154+
for (const child of children) {
155+
dispatchEngineSpan(tracer, child, allSpans, linkIds, ignoreSpanTypes);
156+
}
157+
158+
span.end(engineSpan.endTime);
159+
});
160+
}
161+
162+
function endSpan<T>(span: Span, result: T): T {
163+
if (isPromiseLike(result)) {
164+
return result.then(
165+
value => {
166+
span.end();
167+
return value;
168+
},
169+
reason => {
170+
span.end();
171+
throw reason;
172+
},
173+
) as T;
174+
}
175+
span.end();
176+
return result;
177+
}
178+
179+
function isPromiseLike(value: unknown): value is PromiseLike<unknown> {
180+
return value != null && typeof (value as Record<string, unknown>)['then'] === 'function';
181+
}
182+
183+
function shouldIgnoreSpan(spanName: string, ignoreSpanTypes: (string | RegExp)[]): boolean {
184+
return ignoreSpanTypes.some(pattern => (typeof pattern === 'string' ? pattern === spanName : pattern.test(spanName)));
185+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright Prisma
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* NOTICE from the Sentry authors:
17+
* - Vendored from: https://github.com/prisma/prisma/tree/b6feea5565ec577545a79547d24273ccdd11b4c7/packages/instrumentation
18+
* - Upstream version: @prisma/instrumentation@7.8.0
19+
* - Replaced `import packageJson from '../package.json'` with hardcoded values
20+
*/
21+
/* eslint-disable */
22+
23+
import { SDK_VERSION } from '@sentry/core';
24+
25+
export const VERSION = SDK_VERSION;
26+
27+
export const NAME = '@sentry/instrumentation-prisma';
28+
29+
export const MODULE_NAME = '@prisma/client';
30+
31+
export const SUPPORTED_MODULE_VERSIONS = ['>=5.0.0'];
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright Prisma
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* NOTICE from the Sentry authors:
17+
* - Vendored from: https://github.com/prisma/prisma/tree/b6feea5565ec577545a79547d24273ccdd11b4c7/packages/instrumentation-contract
18+
* - Upstream version: @prisma/instrumentation-contract@7.8.0
19+
* - Replaced `import packageJson from '../package.json'` with hardcoded major version
20+
*/
21+
/* eslint-disable */
22+
23+
import type { PrismaInstrumentationGlobalValue, TracingHelper } from './types';
24+
25+
const majorVersion = '7';
26+
27+
const GLOBAL_INSTRUMENTATION_KEY = 'PRISMA_INSTRUMENTATION';
28+
const GLOBAL_VERSIONED_INSTRUMENTATION_KEY = `V${majorVersion}_PRISMA_INSTRUMENTATION` as const;
29+
30+
type GlobalThisWithPrismaInstrumentation = typeof globalThis & {
31+
[GLOBAL_INSTRUMENTATION_KEY]?: PrismaInstrumentationGlobalValue;
32+
} & {
33+
[K in typeof GLOBAL_VERSIONED_INSTRUMENTATION_KEY]?: PrismaInstrumentationGlobalValue;
34+
};
35+
36+
const globalThisWithPrismaInstrumentation = globalThis as GlobalThisWithPrismaInstrumentation;
37+
38+
export function getGlobalTracingHelper(): TracingHelper | undefined {
39+
const versionedGlobal = globalThisWithPrismaInstrumentation[GLOBAL_VERSIONED_INSTRUMENTATION_KEY];
40+
41+
if (versionedGlobal?.helper) {
42+
return versionedGlobal.helper;
43+
}
44+
45+
const fallbackGlobal = globalThisWithPrismaInstrumentation[GLOBAL_INSTRUMENTATION_KEY];
46+
47+
return fallbackGlobal?.helper;
48+
}
49+
50+
export function setGlobalTracingHelper(helper: TracingHelper): void {
51+
const globalValue: PrismaInstrumentationGlobalValue = { helper };
52+
53+
globalThisWithPrismaInstrumentation[GLOBAL_VERSIONED_INSTRUMENTATION_KEY] = globalValue;
54+
globalThisWithPrismaInstrumentation[GLOBAL_INSTRUMENTATION_KEY] = globalValue;
55+
}
56+
57+
export function clearGlobalTracingHelper(): void {
58+
delete globalThisWithPrismaInstrumentation[GLOBAL_VERSIONED_INSTRUMENTATION_KEY];
59+
delete globalThisWithPrismaInstrumentation[GLOBAL_INSTRUMENTATION_KEY];
60+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Copyright Prisma
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* NOTICE from the Sentry authors:
17+
* - Vendored from: https://github.com/prisma/prisma/tree/b6feea5565ec577545a79547d24273ccdd11b4c7/packages/instrumentation
18+
* - Upstream version: @prisma/instrumentation@7.8.0
19+
* - Replaced `@prisma/instrumentation-contract` imports with local vendored equivalents
20+
* - Replaced `import { VERSION, NAME, MODULE_NAME } from './constants'` with local vendored constants
21+
*/
22+
/* eslint-disable */
23+
24+
import { trace, TracerProvider } from '@opentelemetry/api';
25+
import {
26+
InstrumentationBase,
27+
InstrumentationConfig,
28+
InstrumentationNodeModuleDefinition,
29+
} from '@opentelemetry/instrumentation';
30+
import { clearGlobalTracingHelper, getGlobalTracingHelper, setGlobalTracingHelper } from './global';
31+
32+
import { ActiveTracingHelper } from './active-tracing-helper';
33+
import { MODULE_NAME, NAME, SUPPORTED_MODULE_VERSIONS, VERSION } from './constants';
34+
35+
export interface PrismaInstrumentationConfig {
36+
ignoreSpanTypes?: (string | RegExp)[];
37+
}
38+
39+
type Config = PrismaInstrumentationConfig & InstrumentationConfig;
40+
41+
export class PrismaInstrumentation extends InstrumentationBase {
42+
private tracerProvider: TracerProvider | undefined;
43+
44+
constructor(config: Config = {}) {
45+
super(NAME, VERSION, config);
46+
}
47+
48+
setTracerProvider(tracerProvider: TracerProvider): void {
49+
this.tracerProvider = tracerProvider;
50+
}
51+
52+
init() {
53+
const module = new InstrumentationNodeModuleDefinition(MODULE_NAME, SUPPORTED_MODULE_VERSIONS);
54+
55+
return [module];
56+
}
57+
58+
enable() {
59+
const config = this._config as Config;
60+
61+
setGlobalTracingHelper(
62+
new ActiveTracingHelper({
63+
tracerProvider: this.tracerProvider ?? trace.getTracerProvider(),
64+
ignoreSpanTypes: config.ignoreSpanTypes ?? [],
65+
}),
66+
);
67+
}
68+
69+
disable() {
70+
clearGlobalTracingHelper();
71+
}
72+
73+
isEnabled() {
74+
return getGlobalTracingHelper() !== undefined;
75+
}
76+
}

0 commit comments

Comments
 (0)