Skip to content

Commit 0f69739

Browse files
author
Luca Forstner
committed
tests and warning
1 parent a182658 commit 0f69739

File tree

5 files changed

+129
-57
lines changed

5 files changed

+129
-57
lines changed

dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/scenario.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ setInterval(() => {}, 1000);
2323
async function run() {
2424
const client = new PrismaClient();
2525

26-
await Sentry.startSpan(
26+
await Sentry.startSpanManual(
2727
{
2828
name: 'Test Transaction',
2929
op: 'transaction',

dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/test.ts

Lines changed: 0 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -42,28 +42,6 @@ describe('Prisma ORM Tests', () => {
4242
}),
4343
);
4444

45-
expect(spans).toContainEqual(
46-
expect.objectContaining({
47-
data: {
48-
'sentry.origin': 'auto.db.otel.prisma',
49-
},
50-
description: 'prisma:engine',
51-
status: 'ok',
52-
}),
53-
);
54-
expect(spans).toContainEqual(
55-
expect.objectContaining({
56-
data: {
57-
'sentry.origin': 'auto.db.otel.prisma',
58-
'sentry.op': 'db',
59-
'db.system': 'postgresql',
60-
},
61-
description: 'prisma:engine:connection',
62-
status: 'ok',
63-
op: 'db',
64-
}),
65-
);
66-
6745
expect(spans).toContainEqual(
6846
expect.objectContaining({
6947
data: {
@@ -82,24 +60,6 @@ describe('Prisma ORM Tests', () => {
8260
op: 'db',
8361
}),
8462
);
85-
expect(spans).toContainEqual(
86-
expect.objectContaining({
87-
data: {
88-
'sentry.origin': 'auto.db.otel.prisma',
89-
},
90-
description: 'prisma:engine:serialize',
91-
status: 'ok',
92-
}),
93-
);
94-
expect(spans).toContainEqual(
95-
expect.objectContaining({
96-
data: {
97-
'sentry.origin': 'auto.db.otel.prisma',
98-
},
99-
description: 'prisma:engine:response_json_serialization',
100-
status: 'ok',
101-
}),
102-
);
10363
expect(spans).toContainEqual(
10464
expect.objectContaining({
10565
data: {
@@ -121,15 +81,6 @@ describe('Prisma ORM Tests', () => {
12181
status: 'ok',
12282
}),
12383
);
124-
expect(spans).toContainEqual(
125-
expect.objectContaining({
126-
data: {
127-
'sentry.origin': 'auto.db.otel.prisma',
128-
},
129-
description: 'prisma:engine',
130-
status: 'ok',
131-
}),
132-
);
13384
},
13485
})
13586
.start(done);

packages/node/src/integrations/tracing/prisma.ts

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,58 @@
11
import type { Instrumentation } from '@opentelemetry/instrumentation';
22
// When importing CJS modules into an ESM module, we cannot import the named exports directly.
33
import * as prismaInstrumentation from '@prisma/instrumentation';
4-
import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, defineIntegration, spanToJSON } from '@sentry/core';
4+
import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, consoleSandbox, defineIntegration, spanToJSON } from '@sentry/core';
55
import { generateInstrumentOnce } from '../../otel/instrument';
6+
import type { PrismaV5TracingHelper } from './prisma/vendor/v5-tracing-helper';
7+
import type { PrismaV6TracingHelper } from './prisma/vendor/v6-tracing-helper';
68

79
const INTEGRATION_NAME = 'Prisma';
810

11+
const EsmInteropPrismaInstrumentation: typeof prismaInstrumentation.PrismaInstrumentation =
12+
// @ts-expect-error We need to do the following for interop reasons
13+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
14+
prismaInstrumentation.default?.PrismaInstrumentation || prismaInstrumentation.PrismaInstrumentation;
15+
16+
type CompatibilityLayerTraceHelper = PrismaV5TracingHelper & PrismaV6TracingHelper;
17+
18+
function isPrismaV6TracingHelper(helper: unknown): helper is PrismaV6TracingHelper {
19+
return !!helper && typeof helper === 'object' && 'dispatchEngineSpans' in helper;
20+
}
21+
22+
class SentryPrismaInteropInstrumentation extends EsmInteropPrismaInstrumentation {
23+
public constructor() {
24+
super();
25+
}
26+
27+
public enable(): void {
28+
super.enable();
29+
30+
const prismaInstrumentationObject = (globalThis as Record<string, unknown>).PRISMA_INSTRUMENTATION;
31+
const prismaTracingHelper =
32+
prismaInstrumentationObject &&
33+
typeof prismaInstrumentationObject === 'object' &&
34+
'helper' in prismaInstrumentationObject
35+
? prismaInstrumentationObject.helper
36+
: undefined;
37+
38+
let emittedWarning = false;
39+
40+
if (isPrismaV6TracingHelper(prismaTracingHelper)) {
41+
(prismaTracingHelper as CompatibilityLayerTraceHelper).createEngineSpan = () => {
42+
consoleSandbox(() => {
43+
if (!emittedWarning) {
44+
emittedWarning = true;
45+
// eslint-disable-next-line no-console
46+
console.warn(
47+
'[Sentry] The Sentry SDK supports tracing with Prisma version 5 only with limited capabilities. For full tracing capabilities pass `prismaInstrumentation` for version 5 to the Sentry `prismaIntegration`. Read more: https://docs.sentry.io/platforms/javascript/guides/node/configuration/integrations/prisma/',
48+
);
49+
}
50+
});
51+
};
52+
}
53+
}
54+
}
55+
956
export const instrumentPrisma = generateInstrumentOnce<{ prismaInstrumentation?: Instrumentation }>(
1057
INTEGRATION_NAME,
1158
options => {
@@ -14,12 +61,7 @@ export const instrumentPrisma = generateInstrumentOnce<{ prismaInstrumentation?:
1461
return options.prismaInstrumentation;
1562
}
1663

17-
const EsmInteropPrismaInstrumentation: typeof prismaInstrumentation.PrismaInstrumentation =
18-
// @ts-expect-error We need to do the following for interop reasons
19-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
20-
prismaInstrumentation.default?.PrismaInstrumentation || prismaInstrumentation.PrismaInstrumentation;
21-
22-
return new EsmInteropPrismaInstrumentation({});
64+
return new SentryPrismaInteropInstrumentation();
2365
},
2466
);
2567

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Vendored from https://github.com/prisma/prisma/blob/718358aa37975c18e5ea62f5b659fb47630b7609/packages/internals/src/tracing/types.ts#L1
2+
3+
import type { Context, Span, SpanOptions } from '@opentelemetry/api';
4+
5+
type V5SpanCallback<R> = (span?: Span, context?: Context) => R;
6+
7+
type V5ExtendedSpanOptions = SpanOptions & {
8+
name: string;
9+
internal?: boolean;
10+
middleware?: boolean;
11+
active?: boolean;
12+
context?: Context;
13+
};
14+
15+
type EngineSpanEvent = {
16+
span: boolean;
17+
spans: V5EngineSpan[];
18+
};
19+
20+
type V5EngineSpanKind = 'client' | 'internal';
21+
22+
type V5EngineSpan = {
23+
span: boolean;
24+
name: string;
25+
trace_id: string;
26+
span_id: string;
27+
parent_span_id: string;
28+
start_time: [number, number];
29+
end_time: [number, number];
30+
attributes?: Record<string, string>;
31+
links?: { trace_id: string; span_id: string }[];
32+
kind: V5EngineSpanKind;
33+
};
34+
35+
export interface PrismaV5TracingHelper {
36+
isEnabled(): boolean;
37+
getTraceParent(context?: Context): string;
38+
createEngineSpan(engineSpanEvent: EngineSpanEvent): void;
39+
getActiveContext(): Context | undefined;
40+
runInChildSpan<R>(nameOrOptions: string | V5ExtendedSpanOptions, callback: V5SpanCallback<R>): R;
41+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// https://github.com/prisma/prisma/blob/d45607dfa10c4ef08cb8f79f18fa84ef33910150/packages/internals/src/tracing/types.ts#L1
2+
3+
import type { Context, Span, SpanOptions } from '@opentelemetry/api';
4+
5+
type V6SpanCallback<R> = (span?: Span, context?: Context) => R;
6+
7+
type V6ExtendedSpanOptions = SpanOptions & {
8+
name: string;
9+
internal?: boolean;
10+
middleware?: boolean;
11+
active?: boolean;
12+
context?: Context;
13+
};
14+
15+
type V6EngineSpanId = string;
16+
17+
type V6HrTime = [number, number];
18+
19+
type EngineSpanKind = 'client' | 'internal';
20+
21+
type PrismaV6EngineSpan = {
22+
id: V6EngineSpanId;
23+
parentId: string | null;
24+
name: string;
25+
startTime: V6HrTime;
26+
endTime: V6HrTime;
27+
kind: EngineSpanKind;
28+
attributes?: Record<string, unknown>;
29+
links?: V6EngineSpanId[];
30+
};
31+
32+
export interface PrismaV6TracingHelper {
33+
isEnabled(): boolean;
34+
getTraceParent(context?: Context): string;
35+
dispatchEngineSpans(spans: PrismaV6EngineSpan[]): void;
36+
getActiveContext(): Context | undefined;
37+
runInChildSpan<R>(nameOrOptions: string | V6ExtendedSpanOptions, callback: V6SpanCallback<R>): R;
38+
}

0 commit comments

Comments
 (0)