diff --git a/packages/datadog-instrumentations/src/prisma.js b/packages/datadog-instrumentations/src/prisma.js index ab2b79e0323..80c815335e6 100644 --- a/packages/datadog-instrumentations/src/prisma.js +++ b/packages/datadog-instrumentations/src/prisma.js @@ -4,6 +4,7 @@ const { channel, addHook } = require('./helpers/instrument') +const { storage } = require('../../datadog-core') const prismaEngineStart = channel('apm:prisma:engine:start') const tracingChannel = require('dc-polyfill').tracingChannel @@ -23,7 +24,29 @@ class TracingHelper { // needs a sampled tracecontext to generate engine spans getTraceParent (context) { - return '00-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-bbbbbbbbbbbbbbbb-01' // valid sampled traceparent + // try to get span from current async storage context + const store = storage('legacy').getStore() + const span = store && store.span + + if (span && typeof span.context === 'function') { + const spanContext = span.context() + if (spanContext && typeof spanContext.toTraceparent === 'function') { + let traceparent = spanContext.toTraceparent() + + // force the sampled flag to '01' for Prisma's engine span generation + // prisma only generates engine spans when the traceparent indicates the trace is sampled + // this ensures engine spans are created and dispatched to dd-trace, which will then + // apply its own sampling decision when recording/sending spans + // the trace IDs and span IDs remain correct for DBM correlation + traceparent = traceparent.replace(/-0[01]$/, '-01') + + return traceparent + } + } + + // fallback to a valid sampled traceparent if no active span. + // this ensures Prisma can generate engine spans even when there's no active dd-trace context + return '00-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-bbbbbbbbbbbbbbbb-01' } dispatchEngineSpans (spans) { diff --git a/packages/datadog-plugin-prisma/test/index.spec.js b/packages/datadog-plugin-prisma/test/index.spec.js index 276b2b3be43..87a5ba06401 100644 --- a/packages/datadog-plugin-prisma/test/index.spec.js +++ b/packages/datadog-plugin-prisma/test/index.spec.js @@ -204,6 +204,51 @@ describe('Plugin', () => { tracingPromise ]) }) + + it('should return real traceparent from active span context', async () => { + let capturedTraceparent + const tracingPromise = agent.assertSomeTraces(traces => { + // Get the trace ID and span ID from the created span + const clientSpan = traces[0].find(span => span.meta['prisma.type'] === 'client') + assert.ok(clientSpan != null) + + // Verify the traceparent is not the hardcoded fallback + assert.notEqual(capturedTraceparent, '00-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-bbbbbbbbbbbbbbbb-01') + + // Verify it matches W3C traceparent format + const traceparentRegex = /^00-[a-f0-9]{32}-[a-f0-9]{16}-[0-9]{2}$/ + assert.ok(traceparentRegex.test(capturedTraceparent), + `Invalid traceparent format: ${capturedTraceparent}`) + + // Extract trace ID from traceparent (format: version-traceid-spanid-flags) + const [, traceparentTraceId] = capturedTraceparent.split('-') + + // Convert the span's trace_id to hex for comparison + const spanTraceIdHex = clientSpan.trace_id.toString(16).padStart(32, '0') + + // The last 16 characters of the traceparent trace ID should match + // the span's trace ID (dd-trace uses 64-bit trace IDs by default) + assert.ok(traceparentTraceId.endsWith(spanTraceIdHex.slice(-16)), + `Traceparent trace ID ${traceparentTraceId} should contain span trace ID ${spanTraceIdHex}`) + }) + + // Capture the traceparent during the query + await prismaClient.$queryRaw`SELECT 1`.then(() => { + // Get traceparent while span is still active + capturedTraceparent = tracingHelper.getTraceParent() + }) + + await tracingPromise + }) + + it('should return fallback traceparent when no active span', () => { + // Get traceparent outside of a traced operation + // This should return the fallback value since there's no active span + const result = tracingHelper.getTraceParent() + + // Should be the valid sampled fallback traceparent + assert.strictEqual(result, '00-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-bbbbbbbbbbbbbbbb-01') + }) }) describe('with configuration', () => {