Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ setInterval(() => {}, 1000);
async function run() {
const client = new PrismaClient();

await Sentry.startSpan(
await Sentry.startSpanManual(
{
name: 'Test Transaction',
op: 'transaction',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,28 +42,6 @@ describe('Prisma ORM Tests', () => {
}),
);

expect(spans).toContainEqual(
expect.objectContaining({
data: {
'sentry.origin': 'auto.db.otel.prisma',
},
description: 'prisma:engine',
status: 'ok',
}),
);
expect(spans).toContainEqual(
expect.objectContaining({
data: {
'sentry.origin': 'auto.db.otel.prisma',
'sentry.op': 'db',
'db.system': 'postgresql',
},
description: 'prisma:engine:connection',
status: 'ok',
op: 'db',
}),
);

expect(spans).toContainEqual(
expect.objectContaining({
data: {
Expand All @@ -82,24 +60,6 @@ describe('Prisma ORM Tests', () => {
op: 'db',
}),
);
expect(spans).toContainEqual(
expect.objectContaining({
data: {
'sentry.origin': 'auto.db.otel.prisma',
},
description: 'prisma:engine:serialize',
status: 'ok',
}),
);
expect(spans).toContainEqual(
expect.objectContaining({
data: {
'sentry.origin': 'auto.db.otel.prisma',
},
description: 'prisma:engine:response_json_serialization',
status: 'ok',
}),
);
expect(spans).toContainEqual(
expect.objectContaining({
data: {
Expand All @@ -121,15 +81,6 @@ describe('Prisma ORM Tests', () => {
status: 'ok',
}),
);
expect(spans).toContainEqual(
expect.objectContaining({
data: {
'sentry.origin': 'auto.db.otel.prisma',
},
description: 'prisma:engine',
status: 'ok',
}),
);
},
})
.start(done);
Expand Down
56 changes: 49 additions & 7 deletions packages/node/src/integrations/tracing/prisma.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,58 @@
import type { Instrumentation } from '@opentelemetry/instrumentation';
// When importing CJS modules into an ESM module, we cannot import the named exports directly.
import * as prismaInstrumentation from '@prisma/instrumentation';
import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, defineIntegration, spanToJSON } from '@sentry/core';
import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, consoleSandbox, defineIntegration, spanToJSON } from '@sentry/core';
import { generateInstrumentOnce } from '../../otel/instrument';
import type { PrismaV5TracingHelper } from './prisma/vendor/v5-tracing-helper';
import type { PrismaV6TracingHelper } from './prisma/vendor/v6-tracing-helper';

const INTEGRATION_NAME = 'Prisma';

const EsmInteropPrismaInstrumentation: typeof prismaInstrumentation.PrismaInstrumentation =
// @ts-expect-error We need to do the following for interop reasons
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
prismaInstrumentation.default?.PrismaInstrumentation || prismaInstrumentation.PrismaInstrumentation;

type CompatibilityLayerTraceHelper = PrismaV5TracingHelper & PrismaV6TracingHelper;

function isPrismaV6TracingHelper(helper: unknown): helper is PrismaV6TracingHelper {
return !!helper && typeof helper === 'object' && 'dispatchEngineSpans' in helper;
}

class SentryPrismaInteropInstrumentation extends EsmInteropPrismaInstrumentation {
public constructor() {
super();
}

public enable(): void {
super.enable();

const prismaInstrumentationObject = (globalThis as Record<string, unknown>).PRISMA_INSTRUMENTATION;
Copy link
Member

Choose a reason for hiding this comment

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

m: Can we add a comment here explaining what and why we are doing here? For future reference :D

const prismaTracingHelper =
prismaInstrumentationObject &&
typeof prismaInstrumentationObject === 'object' &&
'helper' in prismaInstrumentationObject
? prismaInstrumentationObject.helper
: undefined;

let emittedWarning = false;

if (isPrismaV6TracingHelper(prismaTracingHelper)) {
(prismaTracingHelper as CompatibilityLayerTraceHelper).createEngineSpan = () => {
consoleSandbox(() => {
if (!emittedWarning) {
emittedWarning = true;
// eslint-disable-next-line no-console
console.warn(
'[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/',
);
}
});
};
}
}
}

export const instrumentPrisma = generateInstrumentOnce<{ prismaInstrumentation?: Instrumentation }>(
INTEGRATION_NAME,
options => {
Expand All @@ -14,12 +61,7 @@ export const instrumentPrisma = generateInstrumentOnce<{ prismaInstrumentation?:
return options.prismaInstrumentation;
}

const EsmInteropPrismaInstrumentation: typeof prismaInstrumentation.PrismaInstrumentation =
// @ts-expect-error We need to do the following for interop reasons
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
prismaInstrumentation.default?.PrismaInstrumentation || prismaInstrumentation.PrismaInstrumentation;

return new EsmInteropPrismaInstrumentation({});
return new SentryPrismaInteropInstrumentation();
},
);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Vendored from https://github.com/prisma/prisma/blob/718358aa37975c18e5ea62f5b659fb47630b7609/packages/internals/src/tracing/types.ts#L1

import type { Context, Span, SpanOptions } from '@opentelemetry/api';

type V5SpanCallback<R> = (span?: Span, context?: Context) => R;

type V5ExtendedSpanOptions = SpanOptions & {
name: string;
internal?: boolean;
middleware?: boolean;
active?: boolean;
context?: Context;
};

type EngineSpanEvent = {
span: boolean;
spans: V5EngineSpan[];
};

type V5EngineSpanKind = 'client' | 'internal';

type V5EngineSpan = {
span: boolean;
name: string;
trace_id: string;
span_id: string;
parent_span_id: string;
start_time: [number, number];
end_time: [number, number];
attributes?: Record<string, string>;
links?: { trace_id: string; span_id: string }[];
kind: V5EngineSpanKind;
};

export interface PrismaV5TracingHelper {
isEnabled(): boolean;
getTraceParent(context?: Context): string;
createEngineSpan(engineSpanEvent: EngineSpanEvent): void;
getActiveContext(): Context | undefined;
runInChildSpan<R>(nameOrOptions: string | V5ExtendedSpanOptions, callback: V5SpanCallback<R>): R;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// https://github.com/prisma/prisma/blob/d45607dfa10c4ef08cb8f79f18fa84ef33910150/packages/internals/src/tracing/types.ts#L1

import type { Context, Span, SpanOptions } from '@opentelemetry/api';

type V6SpanCallback<R> = (span?: Span, context?: Context) => R;

type V6ExtendedSpanOptions = SpanOptions & {
name: string;
internal?: boolean;
middleware?: boolean;
active?: boolean;
context?: Context;
};

type V6EngineSpanId = string;

type V6HrTime = [number, number];

type EngineSpanKind = 'client' | 'internal';

type PrismaV6EngineSpan = {
id: V6EngineSpanId;
parentId: string | null;
name: string;
startTime: V6HrTime;
endTime: V6HrTime;
kind: EngineSpanKind;
attributes?: Record<string, unknown>;
links?: V6EngineSpanId[];
};

export interface PrismaV6TracingHelper {
isEnabled(): boolean;
getTraceParent(context?: Context): string;
dispatchEngineSpans(spans: PrismaV6EngineSpan[]): void;
getActiveContext(): Context | undefined;
runInChildSpan<R>(nameOrOptions: string | V6ExtendedSpanOptions, callback: V6SpanCallback<R>): R;
}
Loading