From d0764da5f766c20fc83461d27e7ade5cfb471fc8 Mon Sep 17 00:00:00 2001 From: Michael Zhao Date: Thu, 24 Jul 2025 19:51:20 -0400 Subject: [PATCH 01/20] set checkpoint for all supported dsm queue types --- src/trace/context/extractors/kinesis.ts | 8 ++++++++ src/trace/context/extractors/sns-sqs.ts | 9 ++++++++- src/trace/context/extractors/sns.ts | 8 +++++++- src/trace/context/extractors/sqs.ts | 7 +++++++ src/trace/tracer-wrapper.ts | 25 +++++++++++++++++++++++++ 5 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/trace/context/extractors/kinesis.ts b/src/trace/context/extractors/kinesis.ts index 5f5d0b1f..d87565b2 100644 --- a/src/trace/context/extractors/kinesis.ts +++ b/src/trace/context/extractors/kinesis.ts @@ -8,15 +8,19 @@ export class KinesisEventTraceExtractor implements EventTraceExtractor { constructor(private tracerWrapper: TracerWrapper) {} extract(event: KinesisStreamEvent): SpanContextWrapper | null { + let sourceARN = ""; const kinesisData = event?.Records?.[0]?.kinesis.data; if (kinesisData === undefined) return null; + sourceARN = event.Records[0].eventSourceARN; + try { const decodedData = Buffer.from(kinesisData, "base64").toString("ascii"); const parsedBody = JSON.parse(decodedData); const headers = parsedBody?._datadog; if (headers) { const traceContext = this.tracerWrapper.extract(headers); + this.tracerWrapper.setConsumeCheckpoint(headers, "kinesis", sourceARN); if (traceContext === null) return null; logDebug(`Extracted trace context from Kinesis event`, { traceContext, headers }); @@ -24,10 +28,14 @@ export class KinesisEventTraceExtractor implements EventTraceExtractor { } } catch (error) { if (error instanceof Error) { + // Still want to set a DSM checkpoint even if DSM context not propagated + this.tracerWrapper.setConsumeCheckpoint(null, "kinesis", sourceARN); logDebug("Unable to extract trace context from Kinesis event", error); } } + // Still want to set a DSM checkpoint even if DSM context not propagated + this.tracerWrapper.setConsumeCheckpoint(null, "kinesis", sourceARN); return null; } } diff --git a/src/trace/context/extractors/sns-sqs.ts b/src/trace/context/extractors/sns-sqs.ts index e68ee6a2..09379dc7 100644 --- a/src/trace/context/extractors/sns-sqs.ts +++ b/src/trace/context/extractors/sns-sqs.ts @@ -9,9 +9,12 @@ export class SNSSQSEventTraceExtractor implements EventTraceExtractor { constructor(private tracerWrapper: TracerWrapper) {} extract(event: SQSEvent): SpanContextWrapper | null { + let sourceARN = ""; + try { // First try to extract trace context from message attributes if (event?.Records?.[0]?.body) { + sourceARN = event.Records[0].eventSourceARN; const parsedBody = JSON.parse(event?.Records?.[0]?.body) as SNSMessage; const messageAttribute = parsedBody?.MessageAttributes?._datadog; if (messageAttribute?.Value) { @@ -24,6 +27,7 @@ export class SNSSQSEventTraceExtractor implements EventTraceExtractor { } const traceContext = this.tracerWrapper.extract(headers); + this.tracerWrapper.setConsumeCheckpoint(headers, "sqs", event.Records[0].eventSourceARN); if (traceContext) { logDebug("Extracted trace context from SNS-SQS event"); return traceContext; @@ -45,10 +49,13 @@ export class SNSSQSEventTraceExtractor implements EventTraceExtractor { } } catch (error) { if (error instanceof Error) { + // Still want to set a DSM checkpoint even if DSM context not propagated + this.tracerWrapper.setConsumeCheckpoint(null, "sqs", sourceARN); logDebug("Unable to extract trace context from SNS-SQS event", error); } } - + // Still want to set a DSM checkpoint even if DSM context not propagated + this.tracerWrapper.setConsumeCheckpoint(null, "sqs", sourceARN); return null; } } diff --git a/src/trace/context/extractors/sns.ts b/src/trace/context/extractors/sns.ts index 33a009db..2cdd5a73 100644 --- a/src/trace/context/extractors/sns.ts +++ b/src/trace/context/extractors/sns.ts @@ -9,9 +9,11 @@ export class SNSEventTraceExtractor implements EventTraceExtractor { constructor(private tracerWrapper: TracerWrapper) {} extract(event: SNSEvent): SpanContextWrapper | null { + let sourceARN = ""; try { // First try to extract trace context from message attributes const messageAttribute = event?.Records?.[0]?.Sns?.MessageAttributes?._datadog; + sourceARN = event.Records[0].Sns.TopicArn; if (messageAttribute?.Value) { let headers; if (messageAttribute.Type === "String") { @@ -21,7 +23,7 @@ export class SNSEventTraceExtractor implements EventTraceExtractor { const decodedValue = Buffer.from(messageAttribute.Value, "base64").toString("ascii"); headers = JSON.parse(decodedValue); } - + this.tracerWrapper.setConsumeCheckpoint(headers, "sns", sourceARN); const traceContext = this.tracerWrapper.extract(headers); if (traceContext) { logDebug("Extracted trace context from SNS event"); @@ -43,10 +45,14 @@ export class SNSEventTraceExtractor implements EventTraceExtractor { } } catch (error) { if (error instanceof Error) { + // Still want to set a DSM checkpoint even if DSM context not propagated + this.tracerWrapper.setConsumeCheckpoint(null, "sns", sourceARN); logDebug("Unable to extract trace context from SNS event", error); } } + // Still want to set a DSM checkpoint even if DSM context not propagated + this.tracerWrapper.setConsumeCheckpoint(null, "sns", sourceARN); return null; } } diff --git a/src/trace/context/extractors/sqs.ts b/src/trace/context/extractors/sqs.ts index 01d1472f..3db4e303 100644 --- a/src/trace/context/extractors/sqs.ts +++ b/src/trace/context/extractors/sqs.ts @@ -9,9 +9,11 @@ export class SQSEventTraceExtractor implements EventTraceExtractor { constructor(private tracerWrapper: TracerWrapper) {} extract(event: SQSEvent): SpanContextWrapper | null { + let sourceARN = ""; try { // First try to extract trace context from message attributes let headers = event?.Records?.[0]?.messageAttributes?._datadog?.stringValue; + sourceARN = event.Records[0].eventSourceARN; if (!headers) { // Then try to get from binary value. This happens when SNS->SQS, but SNS has raw message delivery enabled. @@ -24,6 +26,7 @@ export class SQSEventTraceExtractor implements EventTraceExtractor { } if (headers !== undefined) { + this.tracerWrapper.setConsumeCheckpoint(JSON.parse(headers), "sqs", sourceARN); const traceContext = this.tracerWrapper.extract(JSON.parse(headers)); if (traceContext) { logDebug("Extracted trace context from SQS event messageAttributes"); @@ -45,10 +48,14 @@ export class SQSEventTraceExtractor implements EventTraceExtractor { } } catch (error) { if (error instanceof Error) { + // Still want to set a DSM checkpoint even if DSM context not propagated + this.tracerWrapper.setConsumeCheckpoint(null, "sqs", sourceARN); logDebug("Unable to extract trace context from SQS event", error); } } + // Still want to set a DSM checkpoint even if DSM context not propagated + this.tracerWrapper.setConsumeCheckpoint(null, "sqs", sourceARN); return null; } } diff --git a/src/trace/tracer-wrapper.ts b/src/trace/tracer-wrapper.ts index 78cb6b49..7ffd809f 100644 --- a/src/trace/tracer-wrapper.ts +++ b/src/trace/tracer-wrapper.ts @@ -1,3 +1,4 @@ +import { getEnvValue } from "../index"; import { logDebug } from "../utils"; import { SpanContextWrapper } from "./span-context-wrapper"; import { TraceSource } from "./trace-context-service"; @@ -27,6 +28,7 @@ export interface TraceOptions { // This lets a customer bring their own version of the tracer. export class TracerWrapper { private tracer: any; + private dataStreamsCheckpointer: any; constructor() { try { @@ -35,6 +37,14 @@ export class TracerWrapper { // and one in the user's code. const path = require.resolve("dd-trace", { paths: ["/var/task/node_modules", ...module.paths] }); this.tracer = require(path); + if (getEnvValue("DD_DATA_STREAMS_ENABLED", "false").toLowerCase() === "true") { + console.log("DataStreamsCheckpointer initialization beginning"); + console.log("this.tracer._tracer", this.tracer._tracer); + console.log("this.tracer._tracer._dataStreamsProcessor", this.tracer._tracer._dataStreamsProcessor); + const DataStreamsCheckpointer = + require("dd-trace/packages/dd-trace/src/datastreams/checkpointer").DataStreamsCheckpointer; + this.dataStreamsCheckpointer = new DataStreamsCheckpointer(this.tracer._tracer); + } return; } catch (err) { if (err instanceof Object || err instanceof Error) { @@ -98,4 +108,19 @@ export class TracerWrapper { this.tracer.inject(span, "text_map", dest); return dest; } + + public setConsumeCheckpoint(context_json: any, event_type: string, arn: string): any { + if (!arn) { + return; + } + + try { + this.dataStreamsCheckpointer.setConsumeCheckpoint(event_type, arn, context_json); + } catch (error) { + logDebug( + `DSM: Failed to set consume checkpoint for ${event_type} ${arn}:`, + error instanceof Error ? error : new Error(String(error)), + ); + } + } } From 5d4da9f518077fe70c883fe03fd0bdec54bead27 Mon Sep 17 00:00:00 2001 From: Michael Zhao Date: Mon, 28 Jul 2025 09:22:25 -0400 Subject: [PATCH 02/20] removed not needed checkpoint set --- src/trace/context/extractors/kinesis.ts | 2 -- src/trace/context/extractors/sns-sqs.ts | 2 -- src/trace/context/extractors/sns.ts | 2 -- src/trace/context/extractors/sqs.ts | 2 -- 4 files changed, 8 deletions(-) diff --git a/src/trace/context/extractors/kinesis.ts b/src/trace/context/extractors/kinesis.ts index d87565b2..fbf3b4ea 100644 --- a/src/trace/context/extractors/kinesis.ts +++ b/src/trace/context/extractors/kinesis.ts @@ -28,8 +28,6 @@ export class KinesisEventTraceExtractor implements EventTraceExtractor { } } catch (error) { if (error instanceof Error) { - // Still want to set a DSM checkpoint even if DSM context not propagated - this.tracerWrapper.setConsumeCheckpoint(null, "kinesis", sourceARN); logDebug("Unable to extract trace context from Kinesis event", error); } } diff --git a/src/trace/context/extractors/sns-sqs.ts b/src/trace/context/extractors/sns-sqs.ts index 09379dc7..8d7dacce 100644 --- a/src/trace/context/extractors/sns-sqs.ts +++ b/src/trace/context/extractors/sns-sqs.ts @@ -49,8 +49,6 @@ export class SNSSQSEventTraceExtractor implements EventTraceExtractor { } } catch (error) { if (error instanceof Error) { - // Still want to set a DSM checkpoint even if DSM context not propagated - this.tracerWrapper.setConsumeCheckpoint(null, "sqs", sourceARN); logDebug("Unable to extract trace context from SNS-SQS event", error); } } diff --git a/src/trace/context/extractors/sns.ts b/src/trace/context/extractors/sns.ts index 2cdd5a73..3973763c 100644 --- a/src/trace/context/extractors/sns.ts +++ b/src/trace/context/extractors/sns.ts @@ -45,8 +45,6 @@ export class SNSEventTraceExtractor implements EventTraceExtractor { } } catch (error) { if (error instanceof Error) { - // Still want to set a DSM checkpoint even if DSM context not propagated - this.tracerWrapper.setConsumeCheckpoint(null, "sns", sourceARN); logDebug("Unable to extract trace context from SNS event", error); } } diff --git a/src/trace/context/extractors/sqs.ts b/src/trace/context/extractors/sqs.ts index 3db4e303..5e76b6f4 100644 --- a/src/trace/context/extractors/sqs.ts +++ b/src/trace/context/extractors/sqs.ts @@ -48,8 +48,6 @@ export class SQSEventTraceExtractor implements EventTraceExtractor { } } catch (error) { if (error instanceof Error) { - // Still want to set a DSM checkpoint even if DSM context not propagated - this.tracerWrapper.setConsumeCheckpoint(null, "sqs", sourceARN); logDebug("Unable to extract trace context from SQS event", error); } } From 4ea047088ef140f05c4c116f304d8b7ed80e3f15 Mon Sep 17 00:00:00 2001 From: Michael Zhao Date: Mon, 28 Jul 2025 15:03:06 -0400 Subject: [PATCH 03/20] clean --- src/trace/tracer-wrapper.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/trace/tracer-wrapper.ts b/src/trace/tracer-wrapper.ts index 7ffd809f..689e5486 100644 --- a/src/trace/tracer-wrapper.ts +++ b/src/trace/tracer-wrapper.ts @@ -38,9 +38,6 @@ export class TracerWrapper { const path = require.resolve("dd-trace", { paths: ["/var/task/node_modules", ...module.paths] }); this.tracer = require(path); if (getEnvValue("DD_DATA_STREAMS_ENABLED", "false").toLowerCase() === "true") { - console.log("DataStreamsCheckpointer initialization beginning"); - console.log("this.tracer._tracer", this.tracer._tracer); - console.log("this.tracer._tracer._dataStreamsProcessor", this.tracer._tracer._dataStreamsProcessor); const DataStreamsCheckpointer = require("dd-trace/packages/dd-trace/src/datastreams/checkpointer").DataStreamsCheckpointer; this.dataStreamsCheckpointer = new DataStreamsCheckpointer(this.tracer._tracer); From c2cefc2309c2f76e1c0c70c18000ead0b026d55a Mon Sep 17 00:00:00 2001 From: Michael Zhao Date: Mon, 28 Jul 2025 15:04:49 -0400 Subject: [PATCH 04/20] clearer return type --- src/trace/tracer-wrapper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/trace/tracer-wrapper.ts b/src/trace/tracer-wrapper.ts index 689e5486..b695432e 100644 --- a/src/trace/tracer-wrapper.ts +++ b/src/trace/tracer-wrapper.ts @@ -106,7 +106,7 @@ export class TracerWrapper { return dest; } - public setConsumeCheckpoint(context_json: any, event_type: string, arn: string): any { + public setConsumeCheckpoint(context_json: any, event_type: string, arn: string): void { if (!arn) { return; } From 26266720d74ad243fb4dad2ffb4a11b958c6b871 Mon Sep 17 00:00:00 2001 From: Michael Zhao Date: Mon, 28 Jul 2025 15:06:00 -0400 Subject: [PATCH 05/20] add tests --- src/trace/context/extractors/kinesis.spec.ts | 49 ++++++++++++-- src/trace/context/extractors/sns-sqs.spec.ts | 70 +++++++++++++++++--- src/trace/context/extractors/sns.spec.ts | 68 ++++++++++++++++--- src/trace/context/extractors/sqs.spec.ts | 58 ++++++++++++++-- src/trace/tracer-wrapper.spec.ts | 45 +++++++++++++ 5 files changed, 257 insertions(+), 33 deletions(-) diff --git a/src/trace/context/extractors/kinesis.spec.ts b/src/trace/context/extractors/kinesis.spec.ts index aa68a082..6a1f8d34 100644 --- a/src/trace/context/extractors/kinesis.spec.ts +++ b/src/trace/context/extractors/kinesis.spec.ts @@ -2,6 +2,7 @@ import { TracerWrapper } from "../../tracer-wrapper"; import { KinesisEventTraceExtractor } from "./kinesis"; let mockSpanContext: any = null; +let mockDataStreamsCheckpointer: any = null; // Mocking extract is needed, due to dd-trace being a No-op // if the detected environment is testing. This is expected, since @@ -14,16 +15,29 @@ jest.mock("dd-trace", () => { extract: (_carrier: any, _headers: any) => mockSpanContext, }; }); +jest.mock("dd-trace/packages/dd-trace/src/datastreams/checkpointer", () => { + mockDataStreamsCheckpointer = { + setConsumeCheckpoint: jest.fn(), + }; + return { + DataStreamsCheckpointer: jest.fn().mockImplementation(() => mockDataStreamsCheckpointer), + }; +}); const spyTracerWrapper = jest.spyOn(TracerWrapper.prototype, "extract"); describe("KinesisEventTraceExtractor", () => { describe("extract", () => { beforeEach(() => { mockSpanContext = null; + mockDataStreamsCheckpointer = { + setConsumeCheckpoint: jest.fn(), + }; + process.env["DD_DATA_STREAMS_ENABLED"] = "true"; }); afterEach(() => { jest.resetModules(); + delete process.env["DD_DATA_STREAMS_ENABLED"]; }); it("extracts trace context with valid payload", () => { @@ -43,7 +57,7 @@ describe("KinesisEventTraceExtractor", () => { kinesisSchemaVersion: "1.0", partitionKey: "cdbfd750-cec0-4f0f-a4b0-82ae6152c7fb", sequenceNumber: "49625698045709644136382874226371117765484751339579768834", - data: "eyJJJ20gbWFkZSBvZiB3YXgsIExhcnJ5IjoiV2hhdCBhcmUgeW91IG1hZGUgb2Y/IiwiX2RhdGFkb2ciOnsieC1kYXRhZG9nLXRyYWNlLWlkIjoiNjY3MzA5NTE0MjIxMDM1NTM4IiwieC1kYXRhZG9nLXBhcmVudC1pZCI6IjEzNTA3MzUwMzU0OTc4MTE4MjgiLCJ4LWRhdGFkb2ctc2FtcGxlZCI6IjEiLCJ4LWRhdGFkb2ctc2FtcGxpbmctcHJpb3JpdHkiOiIxIn19", + data: "eyJJJ20gbWFkZSBvZiB3YXgsIExhcnJ5IjoiV2hhdCBhcmUgeW91IG1hZGUgb2Y/IiwiX2RhdGFkb2ciOnsieC1kYXRhZG9nLXRyYWNlLWlkIjoiNjY3MzA5NTE0MjIxMDM1NTM4IiwieC1kYXRhZG9nLXBhcmVudC1pZCI6IjEzNTA3MzUwMzU0OTc4MTE4MjgiLCJ4LWRhdGFkb2ctc2FtcGxlZCI6IjEiLCJ4LWRhdGFkb2ctc2FtcGxpbmctcHJpb3JpdHkiOiIxIiwiZGQtcGF0aHdheS1jdHgtYmFzZTY0Ijoic29tZS1iYXNlNjQtZW5jb2RlZC1jb250ZXh0In19Cg==", approximateArrivalTimestamp: 1642518727.248, }, eventSource: "aws:kinesis", @@ -67,25 +81,48 @@ describe("KinesisEventTraceExtractor", () => { "x-datadog-sampled": "1", "x-datadog-sampling-priority": "1", "x-datadog-trace-id": "667309514221035538", + "dd-pathway-ctx-base64" : "some-base64-encoded-context" }); expect(traceContext?.toTraceId()).toBe("667309514221035538"); expect(traceContext?.toSpanId()).toBe("1350735035497811828"); expect(traceContext?.sampleMode()).toBe("1"); expect(traceContext?.source).toBe("event"); + + expect(mockDataStreamsCheckpointer.setConsumeCheckpoint).toHaveBeenCalledWith( + "kinesis", + "arn:aws:kinesis:EXAMPLE", + { + "x-datadog-parent-id": "1350735035497811828", + "x-datadog-sampled": "1", + "x-datadog-sampling-priority": "1", + "x-datadog-trace-id": "667309514221035538", + "dd-pathway-ctx-base64": "some-base64-encoded-context", + } + ); }); it.each([ - ["Records", {}], - ["Records first entry", { Records: [] }], - ["valid data in kinesis", { Records: [{ kinesis: { data: "{" } }] }], // JSON.parse should fail - ["_datadog in data", { Records: [{ kinesis: { data: "e30=" } }] }], - ])("returns null and skips extracting when payload is missing '%s'", (_, payload) => { + ["Records", {}, 0], + ["Records first entry", { Records: [] }, 0], + ["valid data in kinesis", { Records: [{ kinesis: { data: "{" }, eventSourceARN: "arn:aws:kinesis:test" }] }, 1], // JSON.parse should fail + ["_datadog in data", { Records: [{ kinesis: { data: "e30=" }, eventSourceARN: "arn:aws:kinesis:test" }] }, 1], + ])("returns null and skips extracting when payload is missing '%s'", (_, payload, dsmCalls) => { const tracerWrapper = new TracerWrapper(); const extractor = new KinesisEventTraceExtractor(tracerWrapper); const traceContext = extractor.extract(payload as any); expect(traceContext).toBeNull(); + + expect(mockDataStreamsCheckpointer.setConsumeCheckpoint).toHaveBeenCalledTimes(dsmCalls); + + if (dsmCalls > 0) { + expect(mockDataStreamsCheckpointer.setConsumeCheckpoint).toHaveBeenCalledWith( + "kinesis", + "arn:aws:kinesis:test", + null + ); + } }); it("returns null when extracted span context by tracer is null", () => { diff --git a/src/trace/context/extractors/sns-sqs.spec.ts b/src/trace/context/extractors/sns-sqs.spec.ts index 33d673eb..8e08a4cf 100644 --- a/src/trace/context/extractors/sns-sqs.spec.ts +++ b/src/trace/context/extractors/sns-sqs.spec.ts @@ -2,6 +2,7 @@ import { TracerWrapper } from "../../tracer-wrapper"; import { SNSSQSEventTraceExtractor } from "./sns-sqs"; let mockSpanContext: any = null; +let mockDataStreamsCheckpointer: any = null; // Mocking extract is needed, due to dd-trace being a No-op // if the detected environment is testing. This is expected, since @@ -14,6 +15,14 @@ jest.mock("dd-trace", () => { extract: (_carrier: any, _headers: any) => mockSpanContext, }; }); +jest.mock("dd-trace/packages/dd-trace/src/datastreams/checkpointer", () => { + mockDataStreamsCheckpointer = { + setConsumeCheckpoint: jest.fn(), + }; + return { + DataStreamsCheckpointer: jest.fn().mockImplementation(() => mockDataStreamsCheckpointer), + }; +}); const spyTracerWrapper = jest.spyOn(TracerWrapper.prototype, "extract"); describe("SNSSQSEventTraceExtractor", () => { @@ -21,10 +30,16 @@ describe("SNSSQSEventTraceExtractor", () => { beforeEach(() => { mockSpanContext = null; spyTracerWrapper.mockClear(); + mockDataStreamsCheckpointer = { + setConsumeCheckpoint: jest.fn(), + }; + process.env["DD_DATA_STREAMS_ENABLED"] = "true"; + }); afterEach(() => { jest.resetModules(); + delete process.env["DD_DATA_STREAMS_ENABLED"]; }); it("extracts trace context with valid payload with String Value", () => { @@ -43,7 +58,7 @@ describe("SNSSQSEventTraceExtractor", () => { messageId: "64812b68-4d9b-4dca-b3fb-9b18f255ee51", receiptHandle: "AQEBER6aRkfG8092GvkL7FRwCwbQ7LLDW9Tlk/CembqHe+suS2kfFxXiukomvaIN61QoyQMoRgWuV52SDkiQno2u+5hP64BDbmw+e/KR9ayvIfHJ3M6RfyQLaWNWm3hDFBCKTnBMVIxtdx0N9epZZewyokjKcrNYtmCghFgTCvZzsQkowi5rnoHAVHJ3je1c3bDnQ1KLrZFgajDnootYXDwEPuMq5FIxrf4EzTe0S7S+rnRm+GaQfeBLBVAY6dASL9usV3/AFRqDtaI7GKI+0F2NCgLlqj49VlPRz4ldhkGknYlKTZTluAqALWLJS62/J1GQo53Cs3nneJcmu5ajB2zzmhhRXoXINEkLhCD5ujZfcsw9H4xqW69Or4ECvlqx14bUU2rtMIW0QM2p7pEeXnyocymQv6m1te113eYWTVmaJ4I=", - body: '{\n "Type" : "Notification",\n "MessageId" : "0a0ab23e-4861-5447-82b7-e8094ff3e332",\n "TopicArn" : "arn:aws:sns:eu-west-1:601427279990:js-library-test-dev-demoTopic-15WGUVRCBMPAA",\n "Message" : "{\\"hello\\":\\"harv\\",\\"nice of you to join us\\":\\"david\\",\\"anotherThing\\":{\\"foo\\":\\"bar\\",\\"blah\\":null,\\"harv\\":123},\\"vals\\":[{\\"thingOne\\":1},{\\"thingTwo\\":2}],\\"ajTimestamp\\":1639777617957}",\n "Timestamp" : "2021-12-17T21:46:58.040Z",\n "SignatureVersion" : "1",\n "Signature" : "FR35/7E8C3LHEVk/rC4XxXlXwV/5mNkFNPgDhHSnJ2I6hIoSrTROAm7h5xm1PuBkAeFDvq0zofw91ouk9zZyvhdrMLFIIgrjEyNayRmEffmoEAkzLFUsgtQX7MmTl644r4NuWiM0Oiz7jueRvIcKXcZr7Nc6GJcWV1ymec8oOmuHNMisnPMxI07LIQVYSyAfv6P9r2jEWMVIukRoCzwTnRk4bUUYhPSGHI7OC3AsxxXBbv8snqTrLM/4z2rXCf6jHCKNxWeLlm9/45PphCkEyx5BWS4/71KaoMWUWy8+6CCsy+uF3XTCVmvSEYLyEwTSzOY+vCUjazrRW93498i70g==",\n "SigningCertUrl" : "https://sns.eu-west-1.amazonaws.com/SimpleNotificationService-************************33ab7e69.pem",\n "UnsubscribeUrl" : "https://sns.eu-west-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:eu-west-1:601427279990:js-library-test-dev-demoTopic-15WGUVRCBMPAA:1290f550-9a8a-4e8f-a900-8f5f96dcddda",\n "MessageAttributes" : {\n "_datadog" : {"Type":"String","Value":"{\\"x-datadog-trace-id\\":\\"2776434475358637757\\",\\"x-datadog-parent-id\\":\\"4493917105238181843\\",\\"x-datadog-sampled\\":\\"1\\",\\"x-datadog-sampling-priority\\":\\"1\\"}"}\n }\n}', + body: '{\n "Type" : "Notification",\n "MessageId" : "0a0ab23e-4861-5447-82b7-e8094ff3e332",\n "TopicArn" : "arn:aws:sns:eu-west-1:601427279990:js-library-test-dev-demoTopic-15WGUVRCBMPAA",\n "Message" : "{\\"hello\\":\\"harv\\",\\"nice of you to join us\\":\\"david\\",\\"anotherThing\\":{\\"foo\\":\\"bar\\",\\"blah\\":null,\\"harv\\":123},\\"vals\\":[{\\"thingOne\\":1},{\\"thingTwo\\":2}],\\"ajTimestamp\\":1639777617957}",\n "Timestamp" : "2021-12-17T21:46:58.040Z",\n "SignatureVersion" : "1",\n "Signature" : "FR35/7E8C3LHEVk/rC4XxXlXwV/5mNkFNPgDhHSnJ2I6hIoSrTROAm7h5xm1PuBkAeFDvq0zofw91ouk9zZyvhdrMLFIIgrjEyNayRmEffmoEAkzLFUsgtQX7MmTl644r4NuWiM0Oiz7jueRvIcKXcZr7Nc6GJcWV1ymec8oOmuHNMisnPMxI07LIQVYSyAfv6P9r2jEWMVIukRoCzwTnRk4bUUYhPSGHI7OC3AsxxXBbv8snqTrLM/4z2rXCf6jHCKNxWeLlm9/45PphCkEyx5BWS4/71KaoMWUWy8+6CCsy+uF3XTCVmvSEYLyEwTSzOY+vCUjazrRW93498i70g==",\n "SigningCertUrl" : "https://sns.eu-west-1.amazonaws.com/SimpleNotificationService-************************33ab7e69.pem",\n "UnsubscribeUrl" : "https://sns.eu-west-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:eu-west-1:601427279990:js-library-test-dev-demoTopic-15WGUVRCBMPAA:1290f550-9a8a-4e8f-a900-8f5f96dcddda",\n "MessageAttributes" : {\n "_datadog" : {"Type":"String","Value":"{\\"x-datadog-trace-id\\":\\"2776434475358637757\\",\\"x-datadog-parent-id\\":\\"4493917105238181843\\",\\"x-datadog-sampled\\":\\"1\\",\\"x-datadog-sampling-priority\\":\\"1\\",\\"dd-pathway-ctx-base64\\":\\"some-base64-encoded-context\\"}"}\n }\n}', attributes: { ApproximateReceiveCount: "1", SentTimestamp: "1639777618130", @@ -69,12 +84,25 @@ describe("SNSSQSEventTraceExtractor", () => { "x-datadog-sampled": "1", "x-datadog-sampling-priority": "1", "x-datadog-trace-id": "2776434475358637757", + "dd-pathway-ctx-base64": "some-base64-encoded-context", }); expect(traceContext?.toTraceId()).toBe("2776434475358637757"); expect(traceContext?.toSpanId()).toBe("4493917105238181843"); expect(traceContext?.sampleMode()).toBe("1"); expect(traceContext?.source).toBe("event"); + + expect(mockDataStreamsCheckpointer.setConsumeCheckpoint).toHaveBeenCalledWith( + "sqs", + "arn:aws:sqs:eu-west-1:601427279990:aj-js-library-test-dev-demo-queue", + { + "x-datadog-parent-id": "4493917105238181843", + "x-datadog-sampled": "1", + "x-datadog-sampling-priority": "1", + "x-datadog-trace-id": "2776434475358637757", + "dd-pathway-ctx-base64": "some-base64-encoded-context", + } + ); }); it("extracts trace context with valid payload with Binary Value", () => { @@ -93,7 +121,7 @@ describe("SNSSQSEventTraceExtractor", () => { messageId: "64812b68-4d9b-4dca-b3fb-9b18f255ee51", receiptHandle: "AQEBER6aRkfG8092GvkL7FRwCwbQ7LLDW9Tlk/CembqHe+suS2kfFxXiukomvaIN61QoyQMoRgWuV52SDkiQno2u+5hP64BDbmw+e/KR9ayvIfHJ3M6RfyQLaWNWm3hDFBCKTnBMVIxtdx0N9epZZewyokjKcrNYtmCghFgTCvZzsQkowi5rnoHAVHJ3je1c3bDnQ1KLrZFgajDnootYXDwEPuMq5FIxrf4EzTe0S7S+rnRm+GaQfeBLBVAY6dASL9usV3/AFRqDtaI7GKI+0F2NCgLlqj49VlPRz4ldhkGknYlKTZTluAqALWLJS62/J1GQo53Cs3nneJcmu5ajB2zzmhhRXoXINEkLhCD5ujZfcsw9H4xqW69Or4ECvlqx14bUU2rtMIW0QM2p7pEeXnyocymQv6m1te113eYWTVmaJ4I=", - body: '{\n "Type" : "Notification",\n "MessageId" : "0a0ab23e-4861-5447-82b7-e8094ff3e332",\n "TopicArn" : "arn:aws:sns:eu-west-1:601427279990:js-library-test-dev-demoTopic-15WGUVRCBMPAA",\n "Message" : "{\\"hello\\":\\"harv\\",\\"nice of you to join us\\":\\"david\\",\\"anotherThing\\":{\\"foo\\":\\"bar\\",\\"blah\\":null,\\"harv\\":123},\\"vals\\":[{\\"thingOne\\":1},{\\"thingTwo\\":2}],\\"ajTimestamp\\":1639777617957}",\n "Timestamp" : "2021-12-17T21:46:58.040Z",\n "SignatureVersion" : "1",\n "Signature" : "FR35/7E8C3LHEVk/rC4XxXlXwV/5mNkFNPgDhHSnJ2I6hIoSrTROAm7h5xm1PuBkAeFDvq0zofw91ouk9zZyvhdrMLFIIgrjEyNayRmEffmoEAkzLFUsgtQX7MmTl644r4NuWiM0Oiz7jueRvIcKXcZr7Nc6GJcWV1ymec8oOmuHNMisnPMxI07LIQVYSyAfv6P9r2jEWMVIukRoCzwTnRk4bUUYhPSGHI7OC3AsxxXBbv8snqTrLM/4z2rXCf6jHCKNxWeLlm9/45PphCkEyx5BWS4/71KaoMWUWy8+6CCsy+uF3XTCVmvSEYLyEwTSzOY+vCUjazrRW93498i70g==",\n "SigningCertUrl" : "https://sns.eu-west-1.amazonaws.com/SimpleNotificationService-7ff5318490ec183fbaddaa2a969abfda.pem",\n "UnsubscribeUrl" : "https://sns.eu-west-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:eu-west-1:601427279990:js-library-test-dev-demoTopic-15WGUVRCBMPAA:1290f550-9a8a-4e8f-a900-8f5f96dcddda",\n "MessageAttributes" : {\n "_datadog" : {"Type":"Binary","Value":"eyJ4LWRhdGFkb2ctdHJhY2UtaWQiOiI3MTAyMjkxNjI4NDQzMTM0OTE5IiwieC1kYXRhZG9nLXBhcmVudC1pZCI6IjQyNDc1NTAxMDE2NDg2MTg2MTgiLCJ4LWRhdGFkb2ctc2FtcGxpbmctcHJpb3JpdHkiOiIxIn0="}\n }\n}', + body: '{\n "Type" : "Notification",\n "MessageId" : "0a0ab23e-4861-5447-82b7-e8094ff3e332",\n "TopicArn" : "arn:aws:sns:eu-west-1:601427279990:js-library-test-dev-demoTopic-15WGUVRCBMPAA",\n "Message" : "{\\"hello\\":\\"harv\\",\\"nice of you to join us\\":\\"david\\",\\"anotherThing\\":{\\"foo\\":\\"bar\\",\\"blah\\":null,\\"harv\\":123},\\"vals\\":[{\\"thingOne\\":1},{\\"thingTwo\\":2}],\\"ajTimestamp\\":1639777617957}",\n "Timestamp" : "2021-12-17T21:46:58.040Z",\n "SignatureVersion" : "1",\n "Signature" : "FR35/7E8C3LHEVk/rC4XxXlXwV/5mNkFNPgDhHSnJ2I6hIoSrTROAm7h5xm1PuBkAeFDvq0zofw91ouk9zZyvhdrMLFIIgrjEyNayRmEffmoEAkzLFUsgtQX7MmTl644r4NuWiM0Oiz7jueRvIcKXcZr7Nc6GJcWV1ymec8oOmuHNMisnPMxI07LIQVYSyAfv6P9r2jEWMVIukRoCzwTnRk4bUUYhPSGHI7OC3AsxxXBbv8snqTrLM/4z2rXCf6jHCKNxWeLlm9/45PphCkEyx5BWS4/71KaoMWUWy8+6CCsy+uF3XTCVmvSEYLyEwTSzOY+vCUjazrRW93498i70g==",\n "SigningCertUrl" : "https://sns.eu-west-1.amazonaws.com/SimpleNotificationService-7ff5318490ec183fbaddaa2a969abfda.pem",\n "UnsubscribeUrl" : "https://sns.eu-west-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:eu-west-1:601427279990:js-library-test-dev-demoTopic-15WGUVRCBMPAA:1290f550-9a8a-4e8f-a900-8f5f96dcddda",\n "MessageAttributes" : {\n "_datadog" : {"Type":"Binary","Value":"eyJ4LWRhdGFkb2ctdHJhY2UtaWQiOiI3MTAyMjkxNjI4NDQzMTM0OTE5IiwieC1kYXRhZG9nLXBhcmVudC1pZCI6IjQyNDc1NTAxMDE2NDg2MTg2MTgiLCJ4LWRhdGFkb2ctc2FtcGxpbmctcHJpb3JpdHkiOiIxIiwiZGQtcGF0aHdheS1jdHgtYmFzZTY0Ijoic29tZS1iYXNlNjQtZW5jb2RlZC1jb250ZXh0In0="}\n }\n}', attributes: { ApproximateReceiveCount: "1", SentTimestamp: "1639777618130", @@ -118,28 +146,50 @@ describe("SNSSQSEventTraceExtractor", () => { "x-datadog-parent-id": "4247550101648618618", "x-datadog-sampling-priority": "1", "x-datadog-trace-id": "7102291628443134919", + "dd-pathway-ctx-base64": "some-base64-encoded-context", }); expect(traceContext?.toTraceId()).toBe("7102291628443134919"); expect(traceContext?.toSpanId()).toBe("4247550101648618618"); expect(traceContext?.sampleMode()).toBe("1"); expect(traceContext?.source).toBe("event"); + + expect(mockDataStreamsCheckpointer.setConsumeCheckpoint).toHaveBeenCalledWith( + "sqs", + "arn:aws:sqs:eu-west-1:601427279990:aj-js-library-test-dev-demo-queue", + { + "x-datadog-parent-id": "4247550101648618618", + "x-datadog-sampling-priority": "1", + "x-datadog-trace-id": "7102291628443134919", + "dd-pathway-ctx-base64": "some-base64-encoded-context", + } + ); }); it.each([ - ["Records", {}], - ["Records first entry", { Records: [] }], - ["Records first entry body", { Records: [{}] }], - ["valid data in body", { Records: [{ body: "{" }] }], // JSON.parse should fail - ["MessageAttributes in body", { Records: [{ body: "{}" }] }], - ["_datadog in MessageAttributes", { Records: [{ body: '{"MessageAttributes":{"text":"Hello, world!"}}' }] }], - ["Value in _datadog", { Records: [{ body: '{"MessageAttributes":{"_datadog":{}}}' }] }], - ])("returns null and skips extracting when payload is missing '%s'", (_, payload) => { + ["Records", {}, 0], + ["Records first entry", { Records: [] }, 0], + ["Records first entry body", { Records: [{}] }, 0], + ["valid data in body", { Records: [{ body: "{" , eventSourceARN: "arn:aws:sqs:us-east-1:test"}] }, 1], // JSON.parse should fail + ["MessageAttributes in body", { Records: [{ body: "{}" , eventSourceARN: "arn:aws:sqs:us-east-1:test"}] }, 1], + ["_datadog in MessageAttributes", { Records: [{ body: '{"MessageAttributes":{"text":"Hello, world!"}}' , eventSourceARN: "arn:aws:sqs:us-east-1:test"}] }, 1], + ["Value in _datadog", { Records: [{ body: '{"MessageAttributes":{"_datadog":{}}}' , eventSourceARN: "arn:aws:sqs:us-east-1:test"}] }, 1], + ])("returns null and skips extracting when payload is missing '%s'", (_, payload, dsmCalls) => { const tracerWrapper = new TracerWrapper(); const extractor = new SNSSQSEventTraceExtractor(tracerWrapper); const traceContext = extractor.extract(payload as any); expect(traceContext).toBeNull(); + + expect(mockDataStreamsCheckpointer.setConsumeCheckpoint).toHaveBeenCalledTimes(dsmCalls); + + if (dsmCalls > 0) { + expect(mockDataStreamsCheckpointer.setConsumeCheckpoint).toHaveBeenCalledWith( + "sqs", + "arn:aws:sqs:us-east-1:test", + null + ); + } }); it("returns null when extracted span context by tracer is null", () => { diff --git a/src/trace/context/extractors/sns.spec.ts b/src/trace/context/extractors/sns.spec.ts index aadc29f6..fe7f83c7 100644 --- a/src/trace/context/extractors/sns.spec.ts +++ b/src/trace/context/extractors/sns.spec.ts @@ -3,7 +3,7 @@ import { TracerWrapper } from "../../tracer-wrapper"; import { SNSEventTraceExtractor } from "./sns"; let mockSpanContext: any = null; - +let mockDataStreamsCheckpointer: any = null; // Mocking extract is needed, due to dd-trace being a No-op // if the detected environment is testing. This is expected, since // we don't want to test dd-trace extraction, but our components. @@ -15,6 +15,14 @@ jest.mock("dd-trace", () => { extract: (_carrier: any, _headers: any) => mockSpanContext, }; }); +jest.mock("dd-trace/packages/dd-trace/src/datastreams/checkpointer", () => { + mockDataStreamsCheckpointer = { + setConsumeCheckpoint: jest.fn(), + }; + return { + DataStreamsCheckpointer: jest.fn().mockImplementation(() => mockDataStreamsCheckpointer), + }; +}); const spyTracerWrapper = jest.spyOn(TracerWrapper.prototype, "extract"); describe("SNSEventTraceExtractor", () => { @@ -22,10 +30,15 @@ describe("SNSEventTraceExtractor", () => { beforeEach(() => { mockSpanContext = null; spyTracerWrapper.mockClear(); + mockDataStreamsCheckpointer = { + setConsumeCheckpoint: jest.fn(), + }; + process.env["DD_DATA_STREAMS_ENABLED"] = "true"; }); afterEach(() => { jest.resetModules(); + delete process.env["DD_DATA_STREAMS_ENABLED"]; }); it("extracts trace context with valid payload with String Value", () => { @@ -63,7 +76,7 @@ describe("SNSEventTraceExtractor", () => { _datadog: { Type: "String", Value: - '{"x-datadog-trace-id":"6966585609680374559","x-datadog-parent-id":"4297634551783724228","x-datadog-sampled":"1","x-datadog-sampling-priority":"1"}', + '{"x-datadog-trace-id":"6966585609680374559","x-datadog-parent-id":"4297634551783724228","x-datadog-sampled":"1","x-datadog-sampling-priority":"1","dd-pathway-ctx-base64":"some-base64-encoded-context"}', }, }, }, @@ -81,12 +94,25 @@ describe("SNSEventTraceExtractor", () => { "x-datadog-sampled": "1", "x-datadog-sampling-priority": "1", "x-datadog-trace-id": "6966585609680374559", + "dd-pathway-ctx-base64": "some-base64-encoded-context", }); expect(traceContext?.toTraceId()).toBe("6966585609680374559"); expect(traceContext?.toSpanId()).toBe("4297634551783724228"); expect(traceContext?.sampleMode()).toBe("1"); expect(traceContext?.source).toBe("event"); + + expect(mockDataStreamsCheckpointer.setConsumeCheckpoint).toHaveBeenCalledWith( + "sns", + "arn:aws:sns:eu-west-1:601427279990:aj-js-library-test-dev-solo-topic", + { + "x-datadog-parent-id": "4297634551783724228", + "x-datadog-sampled": "1", + "x-datadog-sampling-priority": "1", + "x-datadog-trace-id": "6966585609680374559", + "dd-pathway-ctx-base64": "some-base64-encoded-context", + } + ); }); it("extracts trace context with valid payload with Binary Value", () => { @@ -124,7 +150,7 @@ describe("SNSEventTraceExtractor", () => { _datadog: { Type: "Binary", Value: - "eyJ4LWRhdGFkb2ctdHJhY2UtaWQiOiI3MTAyMjkxNjI4NDQzMTM0OTE5IiwieC1kYXRhZG9nLXBhcmVudC1pZCI6IjQyNDc1NTAxMDE2NDg2MTg2MTgiLCJ4LWRhdGFkb2ctc2FtcGxpbmctcHJpb3JpdHkiOiIxIn0=", + "eyJ4LWRhdGFkb2ctdHJhY2UtaWQiOiI3MTAyMjkxNjI4NDQzMTM0OTE5IiwieC1kYXRhZG9nLXBhcmVudC1pZCI6IjQyNDc1NTAxMDE2NDg2MTg2MTgiLCJ4LWRhdGFkb2ctc2FtcGxpbmctcHJpb3JpdHkiOiIxIiwiZGQtcGF0aHdheS1jdHgtYmFzZTY0Ijoic29tZS1iYXNlNjQtZW5jb2RlZC1jb250ZXh0In0=", }, }, }, @@ -141,27 +167,49 @@ describe("SNSEventTraceExtractor", () => { "x-datadog-parent-id": "4247550101648618618", "x-datadog-sampling-priority": "1", "x-datadog-trace-id": "7102291628443134919", + "dd-pathway-ctx-base64": "some-base64-encoded-context", }); expect(traceContext?.toTraceId()).toBe("7102291628443134919"); expect(traceContext?.toSpanId()).toBe("4247550101648618618"); expect(traceContext?.sampleMode()).toBe("1"); expect(traceContext?.source).toBe("event"); + + expect(mockDataStreamsCheckpointer.setConsumeCheckpoint).toHaveBeenCalledWith( + "sns", + "arn:aws:sns:eu-west-1:601427279990:aj-js-library-test-dev-solo-topic", + { + "x-datadog-parent-id": "4247550101648618618", + "x-datadog-sampling-priority": "1", + "x-datadog-trace-id": "7102291628443134919", + "dd-pathway-ctx-base64": "some-base64-encoded-context", + } + ); }); it.each([ - ["Records", {}], - ["Records first entry", { Records: [] }], - ["Records first entry Sns", { Records: [{}] }], - ["MessageAttributes in Sns", { Records: [{ Sns: "{}" }] }], - ["_datadog in MessageAttributes", { Records: [{ Sns: '{"MessageAttributes":{"text":"Hello, world!"}}' }] }], - ["Value in _datadog", { Records: [{ Sns: '{"MessageAttributes":{"_datadog":{}}}' }] }], - ])("returns null and skips extracting when payload is missing '%s'", (_, payload) => { + ["Records", {}, 0], + ["Records first entry", { Records: [] }, 0], + ["Records first entry Sns", { Records: [{}] }, 0], + ["MessageAttributes in Sns", { Records: [{ Sns: "{TopicArn: 'arn:aws:sns:eu-west-1:test'}" }] }, 0], + ["_datadog in MessageAttributes", { Records: [{ Sns: { MessageAttributes: { text: "Hello, world!" }, TopicArn: "arn:aws:sns:eu-west-1:test" } }] }, 1], + ["Value in _datadog", { Records: [{ Sns: { MessageAttributes: { _datadog: {} }, TopicArn: "arn:aws:sns:eu-west-1:test" } }] }, 1], + ])("returns null and skips extracting when payload is missing '%s'", (_, payload, dsmCalls) => { const tracerWrapper = new TracerWrapper(); const extractor = new SNSEventTraceExtractor(tracerWrapper); const traceContext = extractor.extract(payload as any); expect(traceContext).toBeNull(); + + expect(mockDataStreamsCheckpointer.setConsumeCheckpoint).toHaveBeenCalledTimes(dsmCalls); + + if (dsmCalls > 0) { + expect(mockDataStreamsCheckpointer.setConsumeCheckpoint).toHaveBeenCalledWith( + "sns", + "arn:aws:sns:eu-west-1:test", + null + ); + } }); it("returns null when extracted span context by tracer is null", () => { diff --git a/src/trace/context/extractors/sqs.spec.ts b/src/trace/context/extractors/sqs.spec.ts index 9c820a7e..5467956d 100644 --- a/src/trace/context/extractors/sqs.spec.ts +++ b/src/trace/context/extractors/sqs.spec.ts @@ -3,6 +3,7 @@ import { TracerWrapper } from "../../tracer-wrapper"; import { SQSEventTraceExtractor } from "./sqs"; let mockSpanContext: any = null; +let mockDataStreamsCheckpointer: any = null; // Mocking extract is needed, due to dd-trace being a No-op // if the detected environment is testing. This is expected, since @@ -15,6 +16,14 @@ jest.mock("dd-trace", () => { extract: (_carrier: any, _headers: any) => mockSpanContext, }; }); +jest.mock("dd-trace/packages/dd-trace/src/datastreams/checkpointer", () => { + mockDataStreamsCheckpointer = { + setConsumeCheckpoint: jest.fn(), + }; + return { + DataStreamsCheckpointer: jest.fn().mockImplementation(() => mockDataStreamsCheckpointer), + }; +}); const spyTracerWrapper = jest.spyOn(TracerWrapper.prototype, "extract"); describe("SQSEventTraceExtractor", () => { @@ -22,10 +31,15 @@ describe("SQSEventTraceExtractor", () => { beforeEach(() => { mockSpanContext = null; spyTracerWrapper.mockClear(); + mockDataStreamsCheckpointer = { + setConsumeCheckpoint: jest.fn(), + }; + process.env["DD_DATA_STREAMS_ENABLED"] = "true"; }); afterEach(() => { jest.resetModules(); + delete process.env["DD_DATA_STREAMS_ENABLED"]; }); it("extracts trace context with valid payload", () => { @@ -51,7 +65,7 @@ describe("SQSEventTraceExtractor", () => { messageAttributes: { _datadog: { stringValue: - '{"x-datadog-trace-id":"4555236104497098341","x-datadog-parent-id":"3369753143434738315","x-datadog-sampled":"1","x-datadog-sampling-priority":"1"}', + '{"x-datadog-trace-id":"4555236104497098341","x-datadog-parent-id":"3369753143434738315","x-datadog-sampled":"1","x-datadog-sampling-priority":"1","dd-pathway-ctx-base64":"some-base64-encoded-context"}', stringListValues: undefined, binaryListValues: undefined, dataType: "String", @@ -77,12 +91,25 @@ describe("SQSEventTraceExtractor", () => { "x-datadog-sampled": "1", "x-datadog-sampling-priority": "1", "x-datadog-trace-id": "4555236104497098341", + "dd-pathway-ctx-base64": "some-base64-encoded-context", }); expect(traceContext?.toTraceId()).toBe("4555236104497098341"); expect(traceContext?.toSpanId()).toBe("3369753143434738315"); expect(traceContext?.sampleMode()).toBe("1"); expect(traceContext?.source).toBe("event"); + + expect(mockDataStreamsCheckpointer.setConsumeCheckpoint).toHaveBeenCalledWith( + "sqs", + "arn:aws:sqs:eu-west-1:601427279990:metal-queue", + { + "x-datadog-parent-id": "3369753143434738315", + "x-datadog-sampled": "1", + "x-datadog-sampling-priority": "1", + "x-datadog-trace-id": "4555236104497098341", + "dd-pathway-ctx-base64": "some-base64-encoded-context", + } + ); }); it("extracts trace context from _datadog binaryValue when raw message delivery is used", () => { @@ -100,6 +127,7 @@ describe("SQSEventTraceExtractor", () => { "x-datadog-parent-id": "0987654321", "x-datadog-sampled": "1", "x-datadog-sampling-priority": "1", + "dd-pathway-ctx-base64": "some-base64-encoded-context", }; const ddHeadersString = JSON.stringify(ddHeaders); const ddHeadersBase64 = Buffer.from(ddHeadersString, "ascii").toString("base64"); @@ -140,20 +168,36 @@ describe("SQSEventTraceExtractor", () => { expect(traceContext?.toSpanId()).toBe("0987654321"); expect(traceContext?.sampleMode()).toBe("1"); expect(traceContext?.source).toBe("event"); + + expect(mockDataStreamsCheckpointer.setConsumeCheckpoint).toHaveBeenCalledWith( + "sqs", + "arn:aws:sqs:us-east-1:123456789012:MyQueue", + ddHeaders + ); }); it.each([ - ["Records", {}], - ["Records first entry", { Records: [] }], - ["messageAttributes in first entry", { Records: [{ messageAttributes: "{}" }] }], - ["_datadog in messageAttributes", { Records: [{ messageAttributes: {} }] }], - ["stringValue in _datadog", { Records: [{ messageAttributes: { _datadog: {} } }] }], - ])("returns null and skips extracting when payload is missing '%s'", (_, payload) => { + ["Records", {}, 0], + ["Records first entry", { Records: [] }, 0], + ["messageAttributes in first entry", { Records: [{ messageAttributes: "{}", eventSourceARN: "arn:aws:sqs:us-east-1:MyQueue" }] }, 1], + ["_datadog in messageAttributes", { Records: [{ messageAttributes: {}, eventSourceARN: "arn:aws:sqs:us-east-1:MyQueue" }] }, 1], + ["stringValue in _datadog", { Records: [{ messageAttributes: { _datadog: {} }, eventSourceARN: "arn:aws:sqs:us-east-1:MyQueue" }] }, 1], + ])("returns null and skips extracting when payload is missing '%s'", (_, payload, dsmCalls) => { const tracerWrapper = new TracerWrapper(); const extractor = new SQSEventTraceExtractor(tracerWrapper); const traceContext = extractor.extract(payload as any); expect(traceContext).toBeNull(); + + expect(mockDataStreamsCheckpointer.setConsumeCheckpoint).toHaveBeenCalledTimes(dsmCalls); + + if (dsmCalls > 0) { + expect(mockDataStreamsCheckpointer.setConsumeCheckpoint).toHaveBeenCalledWith( + "sqs", + "arn:aws:sqs:us-east-1:MyQueue", + null + ); + } }); it("returns null when extracted span context by tracer is null", () => { diff --git a/src/trace/tracer-wrapper.spec.ts b/src/trace/tracer-wrapper.spec.ts index acd43b22..0f3e98ea 100644 --- a/src/trace/tracer-wrapper.spec.ts +++ b/src/trace/tracer-wrapper.spec.ts @@ -3,6 +3,7 @@ import { TracerWrapper } from "./tracer-wrapper"; let mockNoTracer = false; let mockTracerInitialised = false; let mockSpan: any = null; +let mockDataStreamsCheckpointer: any = null; const mockSpanContext = { toTraceId: () => "1234", toSpanId: () => "45678", @@ -22,16 +23,28 @@ jest.mock("dd-trace", () => { } }); +jest.mock("dd-trace/packages/dd-trace/src/datastreams/checkpointer", () => { + mockDataStreamsCheckpointer = { + setConsumeCheckpoint: jest.fn(), + }; + return { + DataStreamsCheckpointer: jest.fn().mockImplementation(() => mockDataStreamsCheckpointer), + }; +}); describe("TracerWrapper", () => { beforeEach(() => { process.env["AWS_LAMBDA_FUNCTION_NAME"] = "my-lambda"; mockNoTracer = false; mockTracerInitialised = true; mockSpan = null; + mockDataStreamsCheckpointer = { + setConsumeCheckpoint: jest.fn(), + }; }); afterEach(() => { jest.resetModules(); delete process.env["AWS_LAMBDA_FUNCTION_NAME"]; + delete process.env["DD_DATA_STREAMS_ENABLED"]; }); it("isTracerAvailable should return true when dd-trace is present and initialised", () => { const wrapper = new TracerWrapper(); @@ -81,4 +94,36 @@ describe("TracerWrapper", () => { const traceContext = wrapper.traceContext(); expect(traceContext).toBeNull(); }); + it("should not call internal setConsumeCheckpoint when arn is not provided", () => { + process.env["DD_DATA_STREAMS_ENABLED"] = "true"; + const wrapper = new TracerWrapper(); + + wrapper.setConsumeCheckpoint({ test: "context" }, "kinesis", ""); + + expect(mockDataStreamsCheckpointer.setConsumeCheckpoint).not.toHaveBeenCalled(); + }); + + it("should call internal setConsumeCheckpoint when DD_DATA_STREAMS_ENABLED is on and arn is provided", () => { + process.env["DD_DATA_STREAMS_ENABLED"] = "true"; + const wrapper = new TracerWrapper(); + const contextJson = { test: "context" }; + const eventType = "kinesis"; + const arn = "arn:aws:kinesis:us-east-1:123456789:stream/test-stream"; + + wrapper.setConsumeCheckpoint(contextJson, eventType, arn); + + expect(mockDataStreamsCheckpointer.setConsumeCheckpoint).toHaveBeenCalledWith(eventType, arn, contextJson); + }); + + it("should not call internal setConsumeCheckpoint when DD_DATA_STREAMS_ENABLED is off", () => { + process.env["DD_DATA_STREAMS_ENABLED"] = "false"; + const wrapper = new TracerWrapper(); + const contextJson = { test: "context" }; + const eventType = "kinesis"; + const arn = "arn:aws:kinesis:us-east-1:123456789:stream/test-stream"; + + wrapper.setConsumeCheckpoint(contextJson, eventType, arn); + + expect(mockDataStreamsCheckpointer.setConsumeCheckpoint).not.toHaveBeenCalled(); + }); }); From ca9edb9fa479822613d448298ccf44a75eedaeb2 Mon Sep 17 00:00:00 2001 From: Michael Zhao Date: Mon, 28 Jul 2025 15:13:23 -0400 Subject: [PATCH 06/20] add the manual checkpoint parameter --- src/trace/context/extractors/kinesis.ts | 4 ++-- src/trace/context/extractors/sns-sqs.ts | 4 ++-- src/trace/context/extractors/sns.ts | 4 ++-- src/trace/context/extractors/sqs.ts | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/trace/context/extractors/kinesis.ts b/src/trace/context/extractors/kinesis.ts index fbf3b4ea..580584e7 100644 --- a/src/trace/context/extractors/kinesis.ts +++ b/src/trace/context/extractors/kinesis.ts @@ -20,7 +20,7 @@ export class KinesisEventTraceExtractor implements EventTraceExtractor { const headers = parsedBody?._datadog; if (headers) { const traceContext = this.tracerWrapper.extract(headers); - this.tracerWrapper.setConsumeCheckpoint(headers, "kinesis", sourceARN); + this.tracerWrapper.setConsumeCheckpoint(headers, "kinesis", sourceARN, false); if (traceContext === null) return null; logDebug(`Extracted trace context from Kinesis event`, { traceContext, headers }); @@ -33,7 +33,7 @@ export class KinesisEventTraceExtractor implements EventTraceExtractor { } // Still want to set a DSM checkpoint even if DSM context not propagated - this.tracerWrapper.setConsumeCheckpoint(null, "kinesis", sourceARN); + this.tracerWrapper.setConsumeCheckpoint(null, "kinesis", sourceARN, false); return null; } } diff --git a/src/trace/context/extractors/sns-sqs.ts b/src/trace/context/extractors/sns-sqs.ts index 8d7dacce..0b871329 100644 --- a/src/trace/context/extractors/sns-sqs.ts +++ b/src/trace/context/extractors/sns-sqs.ts @@ -27,7 +27,7 @@ export class SNSSQSEventTraceExtractor implements EventTraceExtractor { } const traceContext = this.tracerWrapper.extract(headers); - this.tracerWrapper.setConsumeCheckpoint(headers, "sqs", event.Records[0].eventSourceARN); + this.tracerWrapper.setConsumeCheckpoint(headers, "sqs", event.Records[0].eventSourceARN, false); if (traceContext) { logDebug("Extracted trace context from SNS-SQS event"); return traceContext; @@ -53,7 +53,7 @@ export class SNSSQSEventTraceExtractor implements EventTraceExtractor { } } // Still want to set a DSM checkpoint even if DSM context not propagated - this.tracerWrapper.setConsumeCheckpoint(null, "sqs", sourceARN); + this.tracerWrapper.setConsumeCheckpoint(null, "sqs", sourceARN, false); return null; } } diff --git a/src/trace/context/extractors/sns.ts b/src/trace/context/extractors/sns.ts index 3973763c..e509f447 100644 --- a/src/trace/context/extractors/sns.ts +++ b/src/trace/context/extractors/sns.ts @@ -23,7 +23,7 @@ export class SNSEventTraceExtractor implements EventTraceExtractor { const decodedValue = Buffer.from(messageAttribute.Value, "base64").toString("ascii"); headers = JSON.parse(decodedValue); } - this.tracerWrapper.setConsumeCheckpoint(headers, "sns", sourceARN); + this.tracerWrapper.setConsumeCheckpoint(headers, "sns", sourceARN, false); const traceContext = this.tracerWrapper.extract(headers); if (traceContext) { logDebug("Extracted trace context from SNS event"); @@ -50,7 +50,7 @@ export class SNSEventTraceExtractor implements EventTraceExtractor { } // Still want to set a DSM checkpoint even if DSM context not propagated - this.tracerWrapper.setConsumeCheckpoint(null, "sns", sourceARN); + this.tracerWrapper.setConsumeCheckpoint(null, "sns", sourceARN, false); return null; } } diff --git a/src/trace/context/extractors/sqs.ts b/src/trace/context/extractors/sqs.ts index 5e76b6f4..05cf8c2b 100644 --- a/src/trace/context/extractors/sqs.ts +++ b/src/trace/context/extractors/sqs.ts @@ -26,7 +26,7 @@ export class SQSEventTraceExtractor implements EventTraceExtractor { } if (headers !== undefined) { - this.tracerWrapper.setConsumeCheckpoint(JSON.parse(headers), "sqs", sourceARN); + this.tracerWrapper.setConsumeCheckpoint(JSON.parse(headers), "sqs", sourceARN, false); const traceContext = this.tracerWrapper.extract(JSON.parse(headers)); if (traceContext) { logDebug("Extracted trace context from SQS event messageAttributes"); @@ -53,7 +53,7 @@ export class SQSEventTraceExtractor implements EventTraceExtractor { } // Still want to set a DSM checkpoint even if DSM context not propagated - this.tracerWrapper.setConsumeCheckpoint(null, "sqs", sourceARN); + this.tracerWrapper.setConsumeCheckpoint(null, "sqs", sourceARN, false); return null; } } From d0e71e617f7de81a5eaa074cf5a19be0ba077a28 Mon Sep 17 00:00:00 2001 From: Michael Zhao Date: Mon, 28 Jul 2025 16:25:34 -0400 Subject: [PATCH 07/20] Correct approach to ensure public api is used --- src/trace/context/extractors/kinesis.spec.ts | 23 +++++++++---------- src/trace/context/extractors/kinesis.ts | 4 ++-- src/trace/context/extractors/sns-sqs.spec.ts | 23 +++++++++---------- src/trace/context/extractors/sns-sqs.ts | 4 ++-- src/trace/context/extractors/sns.spec.ts | 24 ++++++++++---------- src/trace/context/extractors/sns.ts | 4 ++-- src/trace/context/extractors/sqs.spec.ts | 23 +++++++++---------- src/trace/context/extractors/sqs.ts | 4 ++-- src/trace/tracer-wrapper.spec.ts | 24 ++++++++++---------- src/trace/tracer-wrapper.ts | 11 ++++----- 10 files changed, 70 insertions(+), 74 deletions(-) diff --git a/src/trace/context/extractors/kinesis.spec.ts b/src/trace/context/extractors/kinesis.spec.ts index 6a1f8d34..eed75225 100644 --- a/src/trace/context/extractors/kinesis.spec.ts +++ b/src/trace/context/extractors/kinesis.spec.ts @@ -2,7 +2,15 @@ import { TracerWrapper } from "../../tracer-wrapper"; import { KinesisEventTraceExtractor } from "./kinesis"; let mockSpanContext: any = null; -let mockDataStreamsCheckpointer: any = null; +let mockDataStreamsCheckpointer: any = { + setConsumeCheckpoint: jest.fn(), +}; + +jest.mock("dd-trace/packages/dd-trace/src/datastreams/checkpointer", () => { + return { + DataStreamsCheckpointer: jest.fn().mockImplementation(() => mockDataStreamsCheckpointer), + }; +}); // Mocking extract is needed, due to dd-trace being a No-op // if the detected environment is testing. This is expected, since @@ -13,14 +21,7 @@ jest.mock("dd-trace", () => { ...ddTrace, _tracer: { _service: {} }, extract: (_carrier: any, _headers: any) => mockSpanContext, - }; -}); -jest.mock("dd-trace/packages/dd-trace/src/datastreams/checkpointer", () => { - mockDataStreamsCheckpointer = { - setConsumeCheckpoint: jest.fn(), - }; - return { - DataStreamsCheckpointer: jest.fn().mockImplementation(() => mockDataStreamsCheckpointer), + dataStreamsCheckpointer: mockDataStreamsCheckpointer, }; }); const spyTracerWrapper = jest.spyOn(TracerWrapper.prototype, "extract"); @@ -29,9 +30,7 @@ describe("KinesisEventTraceExtractor", () => { describe("extract", () => { beforeEach(() => { mockSpanContext = null; - mockDataStreamsCheckpointer = { - setConsumeCheckpoint: jest.fn(), - }; + mockDataStreamsCheckpointer.setConsumeCheckpoint.mockClear(); process.env["DD_DATA_STREAMS_ENABLED"] = "true"; }); diff --git a/src/trace/context/extractors/kinesis.ts b/src/trace/context/extractors/kinesis.ts index 580584e7..fbf3b4ea 100644 --- a/src/trace/context/extractors/kinesis.ts +++ b/src/trace/context/extractors/kinesis.ts @@ -20,7 +20,7 @@ export class KinesisEventTraceExtractor implements EventTraceExtractor { const headers = parsedBody?._datadog; if (headers) { const traceContext = this.tracerWrapper.extract(headers); - this.tracerWrapper.setConsumeCheckpoint(headers, "kinesis", sourceARN, false); + this.tracerWrapper.setConsumeCheckpoint(headers, "kinesis", sourceARN); if (traceContext === null) return null; logDebug(`Extracted trace context from Kinesis event`, { traceContext, headers }); @@ -33,7 +33,7 @@ export class KinesisEventTraceExtractor implements EventTraceExtractor { } // Still want to set a DSM checkpoint even if DSM context not propagated - this.tracerWrapper.setConsumeCheckpoint(null, "kinesis", sourceARN, false); + this.tracerWrapper.setConsumeCheckpoint(null, "kinesis", sourceARN); return null; } } diff --git a/src/trace/context/extractors/sns-sqs.spec.ts b/src/trace/context/extractors/sns-sqs.spec.ts index 8e08a4cf..843f86ad 100644 --- a/src/trace/context/extractors/sns-sqs.spec.ts +++ b/src/trace/context/extractors/sns-sqs.spec.ts @@ -2,7 +2,15 @@ import { TracerWrapper } from "../../tracer-wrapper"; import { SNSSQSEventTraceExtractor } from "./sns-sqs"; let mockSpanContext: any = null; -let mockDataStreamsCheckpointer: any = null; +let mockDataStreamsCheckpointer: any = { + setConsumeCheckpoint: jest.fn(), +}; + +jest.mock("dd-trace/packages/dd-trace/src/datastreams/checkpointer", () => { + return { + DataStreamsCheckpointer: jest.fn().mockImplementation(() => mockDataStreamsCheckpointer), + }; +}); // Mocking extract is needed, due to dd-trace being a No-op // if the detected environment is testing. This is expected, since @@ -13,14 +21,7 @@ jest.mock("dd-trace", () => { ...ddTrace, _tracer: { _service: {} }, extract: (_carrier: any, _headers: any) => mockSpanContext, - }; -}); -jest.mock("dd-trace/packages/dd-trace/src/datastreams/checkpointer", () => { - mockDataStreamsCheckpointer = { - setConsumeCheckpoint: jest.fn(), - }; - return { - DataStreamsCheckpointer: jest.fn().mockImplementation(() => mockDataStreamsCheckpointer), + dataStreamsCheckpointer: mockDataStreamsCheckpointer, }; }); const spyTracerWrapper = jest.spyOn(TracerWrapper.prototype, "extract"); @@ -30,9 +31,7 @@ describe("SNSSQSEventTraceExtractor", () => { beforeEach(() => { mockSpanContext = null; spyTracerWrapper.mockClear(); - mockDataStreamsCheckpointer = { - setConsumeCheckpoint: jest.fn(), - }; + mockDataStreamsCheckpointer.setConsumeCheckpoint.mockClear(); process.env["DD_DATA_STREAMS_ENABLED"] = "true"; }); diff --git a/src/trace/context/extractors/sns-sqs.ts b/src/trace/context/extractors/sns-sqs.ts index 0b871329..8d7dacce 100644 --- a/src/trace/context/extractors/sns-sqs.ts +++ b/src/trace/context/extractors/sns-sqs.ts @@ -27,7 +27,7 @@ export class SNSSQSEventTraceExtractor implements EventTraceExtractor { } const traceContext = this.tracerWrapper.extract(headers); - this.tracerWrapper.setConsumeCheckpoint(headers, "sqs", event.Records[0].eventSourceARN, false); + this.tracerWrapper.setConsumeCheckpoint(headers, "sqs", event.Records[0].eventSourceARN); if (traceContext) { logDebug("Extracted trace context from SNS-SQS event"); return traceContext; @@ -53,7 +53,7 @@ export class SNSSQSEventTraceExtractor implements EventTraceExtractor { } } // Still want to set a DSM checkpoint even if DSM context not propagated - this.tracerWrapper.setConsumeCheckpoint(null, "sqs", sourceARN, false); + this.tracerWrapper.setConsumeCheckpoint(null, "sqs", sourceARN); return null; } } diff --git a/src/trace/context/extractors/sns.spec.ts b/src/trace/context/extractors/sns.spec.ts index fe7f83c7..3a1d2178 100644 --- a/src/trace/context/extractors/sns.spec.ts +++ b/src/trace/context/extractors/sns.spec.ts @@ -3,7 +3,16 @@ import { TracerWrapper } from "../../tracer-wrapper"; import { SNSEventTraceExtractor } from "./sns"; let mockSpanContext: any = null; -let mockDataStreamsCheckpointer: any = null; +let mockDataStreamsCheckpointer: any = { + setConsumeCheckpoint: jest.fn(), +}; + +jest.mock("dd-trace/packages/dd-trace/src/datastreams/checkpointer", () => { + return { + DataStreamsCheckpointer: jest.fn().mockImplementation(() => mockDataStreamsCheckpointer), + }; +}); + // Mocking extract is needed, due to dd-trace being a No-op // if the detected environment is testing. This is expected, since // we don't want to test dd-trace extraction, but our components. @@ -13,14 +22,7 @@ jest.mock("dd-trace", () => { ...ddTrace, _tracer: { _service: {} }, extract: (_carrier: any, _headers: any) => mockSpanContext, - }; -}); -jest.mock("dd-trace/packages/dd-trace/src/datastreams/checkpointer", () => { - mockDataStreamsCheckpointer = { - setConsumeCheckpoint: jest.fn(), - }; - return { - DataStreamsCheckpointer: jest.fn().mockImplementation(() => mockDataStreamsCheckpointer), + dataStreamsCheckpointer: mockDataStreamsCheckpointer, }; }); const spyTracerWrapper = jest.spyOn(TracerWrapper.prototype, "extract"); @@ -30,9 +32,7 @@ describe("SNSEventTraceExtractor", () => { beforeEach(() => { mockSpanContext = null; spyTracerWrapper.mockClear(); - mockDataStreamsCheckpointer = { - setConsumeCheckpoint: jest.fn(), - }; + mockDataStreamsCheckpointer.setConsumeCheckpoint.mockClear(); process.env["DD_DATA_STREAMS_ENABLED"] = "true"; }); diff --git a/src/trace/context/extractors/sns.ts b/src/trace/context/extractors/sns.ts index e509f447..3973763c 100644 --- a/src/trace/context/extractors/sns.ts +++ b/src/trace/context/extractors/sns.ts @@ -23,7 +23,7 @@ export class SNSEventTraceExtractor implements EventTraceExtractor { const decodedValue = Buffer.from(messageAttribute.Value, "base64").toString("ascii"); headers = JSON.parse(decodedValue); } - this.tracerWrapper.setConsumeCheckpoint(headers, "sns", sourceARN, false); + this.tracerWrapper.setConsumeCheckpoint(headers, "sns", sourceARN); const traceContext = this.tracerWrapper.extract(headers); if (traceContext) { logDebug("Extracted trace context from SNS event"); @@ -50,7 +50,7 @@ export class SNSEventTraceExtractor implements EventTraceExtractor { } // Still want to set a DSM checkpoint even if DSM context not propagated - this.tracerWrapper.setConsumeCheckpoint(null, "sns", sourceARN, false); + this.tracerWrapper.setConsumeCheckpoint(null, "sns", sourceARN); return null; } } diff --git a/src/trace/context/extractors/sqs.spec.ts b/src/trace/context/extractors/sqs.spec.ts index 5467956d..1ccb9a36 100644 --- a/src/trace/context/extractors/sqs.spec.ts +++ b/src/trace/context/extractors/sqs.spec.ts @@ -3,7 +3,15 @@ import { TracerWrapper } from "../../tracer-wrapper"; import { SQSEventTraceExtractor } from "./sqs"; let mockSpanContext: any = null; -let mockDataStreamsCheckpointer: any = null; +let mockDataStreamsCheckpointer: any = { + setConsumeCheckpoint: jest.fn(), +}; + +jest.mock("dd-trace/packages/dd-trace/src/datastreams/checkpointer", () => { + return { + DataStreamsCheckpointer: jest.fn().mockImplementation(() => mockDataStreamsCheckpointer), + }; +}); // Mocking extract is needed, due to dd-trace being a No-op // if the detected environment is testing. This is expected, since @@ -14,14 +22,7 @@ jest.mock("dd-trace", () => { ...ddTrace, _tracer: { _service: {} }, extract: (_carrier: any, _headers: any) => mockSpanContext, - }; -}); -jest.mock("dd-trace/packages/dd-trace/src/datastreams/checkpointer", () => { - mockDataStreamsCheckpointer = { - setConsumeCheckpoint: jest.fn(), - }; - return { - DataStreamsCheckpointer: jest.fn().mockImplementation(() => mockDataStreamsCheckpointer), + dataStreamsCheckpointer: mockDataStreamsCheckpointer, }; }); const spyTracerWrapper = jest.spyOn(TracerWrapper.prototype, "extract"); @@ -31,9 +32,7 @@ describe("SQSEventTraceExtractor", () => { beforeEach(() => { mockSpanContext = null; spyTracerWrapper.mockClear(); - mockDataStreamsCheckpointer = { - setConsumeCheckpoint: jest.fn(), - }; + mockDataStreamsCheckpointer.setConsumeCheckpoint.mockClear(); process.env["DD_DATA_STREAMS_ENABLED"] = "true"; }); diff --git a/src/trace/context/extractors/sqs.ts b/src/trace/context/extractors/sqs.ts index 05cf8c2b..5e76b6f4 100644 --- a/src/trace/context/extractors/sqs.ts +++ b/src/trace/context/extractors/sqs.ts @@ -26,7 +26,7 @@ export class SQSEventTraceExtractor implements EventTraceExtractor { } if (headers !== undefined) { - this.tracerWrapper.setConsumeCheckpoint(JSON.parse(headers), "sqs", sourceARN, false); + this.tracerWrapper.setConsumeCheckpoint(JSON.parse(headers), "sqs", sourceARN); const traceContext = this.tracerWrapper.extract(JSON.parse(headers)); if (traceContext) { logDebug("Extracted trace context from SQS event messageAttributes"); @@ -53,7 +53,7 @@ export class SQSEventTraceExtractor implements EventTraceExtractor { } // Still want to set a DSM checkpoint even if DSM context not propagated - this.tracerWrapper.setConsumeCheckpoint(null, "sqs", sourceARN, false); + this.tracerWrapper.setConsumeCheckpoint(null, "sqs", sourceARN); return null; } } diff --git a/src/trace/tracer-wrapper.spec.ts b/src/trace/tracer-wrapper.spec.ts index 0f3e98ea..4dbf1078 100644 --- a/src/trace/tracer-wrapper.spec.ts +++ b/src/trace/tracer-wrapper.spec.ts @@ -3,7 +3,15 @@ import { TracerWrapper } from "./tracer-wrapper"; let mockNoTracer = false; let mockTracerInitialised = false; let mockSpan: any = null; -let mockDataStreamsCheckpointer: any = null; +let mockDataStreamsCheckpointer: any = { + setConsumeCheckpoint: jest.fn(), +}; +jest.mock("dd-trace/packages/dd-trace/src/datastreams/checkpointer", () => { + return { + DataStreamsCheckpointer: jest.fn().mockImplementation(() => mockDataStreamsCheckpointer), + }; +}); + const mockSpanContext = { toTraceId: () => "1234", toSpanId: () => "45678", @@ -19,27 +27,19 @@ jest.mock("dd-trace", () => { scope: () => ({ active: () => mockSpan, }), + dataStreamsCheckpointer: mockDataStreamsCheckpointer, }; } }); -jest.mock("dd-trace/packages/dd-trace/src/datastreams/checkpointer", () => { - mockDataStreamsCheckpointer = { - setConsumeCheckpoint: jest.fn(), - }; - return { - DataStreamsCheckpointer: jest.fn().mockImplementation(() => mockDataStreamsCheckpointer), - }; -}); + describe("TracerWrapper", () => { beforeEach(() => { process.env["AWS_LAMBDA_FUNCTION_NAME"] = "my-lambda"; mockNoTracer = false; mockTracerInitialised = true; mockSpan = null; - mockDataStreamsCheckpointer = { - setConsumeCheckpoint: jest.fn(), - }; + mockDataStreamsCheckpointer.setConsumeCheckpoint.mockClear(); }); afterEach(() => { jest.resetModules(); diff --git a/src/trace/tracer-wrapper.ts b/src/trace/tracer-wrapper.ts index b695432e..0c1176d7 100644 --- a/src/trace/tracer-wrapper.ts +++ b/src/trace/tracer-wrapper.ts @@ -37,11 +37,6 @@ export class TracerWrapper { // and one in the user's code. const path = require.resolve("dd-trace", { paths: ["/var/task/node_modules", ...module.paths] }); this.tracer = require(path); - if (getEnvValue("DD_DATA_STREAMS_ENABLED", "false").toLowerCase() === "true") { - const DataStreamsCheckpointer = - require("dd-trace/packages/dd-trace/src/datastreams/checkpointer").DataStreamsCheckpointer; - this.dataStreamsCheckpointer = new DataStreamsCheckpointer(this.tracer._tracer); - } return; } catch (err) { if (err instanceof Object || err instanceof Error) { @@ -111,8 +106,12 @@ export class TracerWrapper { return; } + if (getEnvValue("DD_DATA_STREAMS_ENABLED", "false").toLowerCase() !== "true") { + return; + } + try { - this.dataStreamsCheckpointer.setConsumeCheckpoint(event_type, arn, context_json); + this.tracer.dataStreamsCheckpointer.setConsumeCheckpoint(event_type, arn, context_json); } catch (error) { logDebug( `DSM: Failed to set consume checkpoint for ${event_type} ${arn}:`, From 197abd02f7f058043d99c0cb64c493e354128ce9 Mon Sep 17 00:00:00 2001 From: Michael Zhao Date: Mon, 28 Jul 2025 17:35:15 -0400 Subject: [PATCH 08/20] remove unused variable --- src/trace/tracer-wrapper.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/trace/tracer-wrapper.ts b/src/trace/tracer-wrapper.ts index 0c1176d7..153f9267 100644 --- a/src/trace/tracer-wrapper.ts +++ b/src/trace/tracer-wrapper.ts @@ -28,7 +28,6 @@ export interface TraceOptions { // This lets a customer bring their own version of the tracer. export class TracerWrapper { private tracer: any; - private dataStreamsCheckpointer: any; constructor() { try { From c9d6c23a439b5639ff1f3a5bc21bef4935a976f7 Mon Sep 17 00:00:00 2001 From: Michael Zhao Date: Mon, 28 Jul 2025 17:39:02 -0400 Subject: [PATCH 09/20] match logging patterns of repo --- src/trace/tracer-wrapper.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/trace/tracer-wrapper.ts b/src/trace/tracer-wrapper.ts index 153f9267..f183b823 100644 --- a/src/trace/tracer-wrapper.ts +++ b/src/trace/tracer-wrapper.ts @@ -111,11 +111,10 @@ export class TracerWrapper { try { this.tracer.dataStreamsCheckpointer.setConsumeCheckpoint(event_type, arn, context_json); - } catch (error) { - logDebug( - `DSM: Failed to set consume checkpoint for ${event_type} ${arn}:`, - error instanceof Error ? error : new Error(String(error)), - ); + } catch (err) { + if (err instanceof Object || err instanceof Error) { + logDebug(`DSM: Failed to set consume checkpoint for ${event_type} ${arn}:`, err); + } } } } From 75e976f83c07d070f09f1b2def8438223f912309 Mon Sep 17 00:00:00 2001 From: Michael Zhao Date: Mon, 28 Jul 2025 17:47:01 -0400 Subject: [PATCH 10/20] lint --- src/trace/context/extractors/kinesis.spec.ts | 6 ++--- src/trace/context/extractors/sns-sqs.spec.ts | 27 ++++++++++++++------ src/trace/context/extractors/sns.spec.ts | 20 +++++++++++---- src/trace/context/extractors/sqs.spec.ts | 24 ++++++++++++----- src/trace/tracer-wrapper.spec.ts | 1 - 5 files changed, 55 insertions(+), 23 deletions(-) diff --git a/src/trace/context/extractors/kinesis.spec.ts b/src/trace/context/extractors/kinesis.spec.ts index eed75225..66504725 100644 --- a/src/trace/context/extractors/kinesis.spec.ts +++ b/src/trace/context/extractors/kinesis.spec.ts @@ -80,7 +80,7 @@ describe("KinesisEventTraceExtractor", () => { "x-datadog-sampled": "1", "x-datadog-sampling-priority": "1", "x-datadog-trace-id": "667309514221035538", - "dd-pathway-ctx-base64" : "some-base64-encoded-context" + "dd-pathway-ctx-base64": "some-base64-encoded-context", }); expect(traceContext?.toTraceId()).toBe("667309514221035538"); @@ -97,7 +97,7 @@ describe("KinesisEventTraceExtractor", () => { "x-datadog-sampling-priority": "1", "x-datadog-trace-id": "667309514221035538", "dd-pathway-ctx-base64": "some-base64-encoded-context", - } + }, ); }); @@ -119,7 +119,7 @@ describe("KinesisEventTraceExtractor", () => { expect(mockDataStreamsCheckpointer.setConsumeCheckpoint).toHaveBeenCalledWith( "kinesis", "arn:aws:kinesis:test", - null + null, ); } }); diff --git a/src/trace/context/extractors/sns-sqs.spec.ts b/src/trace/context/extractors/sns-sqs.spec.ts index 843f86ad..3ccd6a21 100644 --- a/src/trace/context/extractors/sns-sqs.spec.ts +++ b/src/trace/context/extractors/sns-sqs.spec.ts @@ -33,7 +33,6 @@ describe("SNSSQSEventTraceExtractor", () => { spyTracerWrapper.mockClear(); mockDataStreamsCheckpointer.setConsumeCheckpoint.mockClear(); process.env["DD_DATA_STREAMS_ENABLED"] = "true"; - }); afterEach(() => { @@ -100,7 +99,7 @@ describe("SNSSQSEventTraceExtractor", () => { "x-datadog-sampling-priority": "1", "x-datadog-trace-id": "2776434475358637757", "dd-pathway-ctx-base64": "some-base64-encoded-context", - } + }, ); }); @@ -161,7 +160,7 @@ describe("SNSSQSEventTraceExtractor", () => { "x-datadog-sampling-priority": "1", "x-datadog-trace-id": "7102291628443134919", "dd-pathway-ctx-base64": "some-base64-encoded-context", - } + }, ); }); @@ -169,10 +168,22 @@ describe("SNSSQSEventTraceExtractor", () => { ["Records", {}, 0], ["Records first entry", { Records: [] }, 0], ["Records first entry body", { Records: [{}] }, 0], - ["valid data in body", { Records: [{ body: "{" , eventSourceARN: "arn:aws:sqs:us-east-1:test"}] }, 1], // JSON.parse should fail - ["MessageAttributes in body", { Records: [{ body: "{}" , eventSourceARN: "arn:aws:sqs:us-east-1:test"}] }, 1], - ["_datadog in MessageAttributes", { Records: [{ body: '{"MessageAttributes":{"text":"Hello, world!"}}' , eventSourceARN: "arn:aws:sqs:us-east-1:test"}] }, 1], - ["Value in _datadog", { Records: [{ body: '{"MessageAttributes":{"_datadog":{}}}' , eventSourceARN: "arn:aws:sqs:us-east-1:test"}] }, 1], + ["valid data in body", { Records: [{ body: "{", eventSourceARN: "arn:aws:sqs:us-east-1:test" }] }, 1], // JSON.parse should fail + ["MessageAttributes in body", { Records: [{ body: "{}", eventSourceARN: "arn:aws:sqs:us-east-1:test" }] }, 1], + [ + "_datadog in MessageAttributes", + { + Records: [ + { body: '{"MessageAttributes":{"text":"Hello, world!"}}', eventSourceARN: "arn:aws:sqs:us-east-1:test" }, + ], + }, + 1, + ], + [ + "Value in _datadog", + { Records: [{ body: '{"MessageAttributes":{"_datadog":{}}}', eventSourceARN: "arn:aws:sqs:us-east-1:test" }] }, + 1, + ], ])("returns null and skips extracting when payload is missing '%s'", (_, payload, dsmCalls) => { const tracerWrapper = new TracerWrapper(); const extractor = new SNSSQSEventTraceExtractor(tracerWrapper); @@ -186,7 +197,7 @@ describe("SNSSQSEventTraceExtractor", () => { expect(mockDataStreamsCheckpointer.setConsumeCheckpoint).toHaveBeenCalledWith( "sqs", "arn:aws:sqs:us-east-1:test", - null + null, ); } }); diff --git a/src/trace/context/extractors/sns.spec.ts b/src/trace/context/extractors/sns.spec.ts index 3a1d2178..5c896268 100644 --- a/src/trace/context/extractors/sns.spec.ts +++ b/src/trace/context/extractors/sns.spec.ts @@ -111,7 +111,7 @@ describe("SNSEventTraceExtractor", () => { "x-datadog-sampling-priority": "1", "x-datadog-trace-id": "6966585609680374559", "dd-pathway-ctx-base64": "some-base64-encoded-context", - } + }, ); }); @@ -183,7 +183,7 @@ describe("SNSEventTraceExtractor", () => { "x-datadog-sampling-priority": "1", "x-datadog-trace-id": "7102291628443134919", "dd-pathway-ctx-base64": "some-base64-encoded-context", - } + }, ); }); @@ -192,8 +192,18 @@ describe("SNSEventTraceExtractor", () => { ["Records first entry", { Records: [] }, 0], ["Records first entry Sns", { Records: [{}] }, 0], ["MessageAttributes in Sns", { Records: [{ Sns: "{TopicArn: 'arn:aws:sns:eu-west-1:test'}" }] }, 0], - ["_datadog in MessageAttributes", { Records: [{ Sns: { MessageAttributes: { text: "Hello, world!" }, TopicArn: "arn:aws:sns:eu-west-1:test" } }] }, 1], - ["Value in _datadog", { Records: [{ Sns: { MessageAttributes: { _datadog: {} }, TopicArn: "arn:aws:sns:eu-west-1:test" } }] }, 1], + [ + "_datadog in MessageAttributes", + { + Records: [{ Sns: { MessageAttributes: { text: "Hello, world!" }, TopicArn: "arn:aws:sns:eu-west-1:test" } }], + }, + 1, + ], + [ + "Value in _datadog", + { Records: [{ Sns: { MessageAttributes: { _datadog: {} }, TopicArn: "arn:aws:sns:eu-west-1:test" } }] }, + 1, + ], ])("returns null and skips extracting when payload is missing '%s'", (_, payload, dsmCalls) => { const tracerWrapper = new TracerWrapper(); const extractor = new SNSEventTraceExtractor(tracerWrapper); @@ -207,7 +217,7 @@ describe("SNSEventTraceExtractor", () => { expect(mockDataStreamsCheckpointer.setConsumeCheckpoint).toHaveBeenCalledWith( "sns", "arn:aws:sns:eu-west-1:test", - null + null, ); } }); diff --git a/src/trace/context/extractors/sqs.spec.ts b/src/trace/context/extractors/sqs.spec.ts index 1ccb9a36..1afa2f63 100644 --- a/src/trace/context/extractors/sqs.spec.ts +++ b/src/trace/context/extractors/sqs.spec.ts @@ -107,7 +107,7 @@ describe("SQSEventTraceExtractor", () => { "x-datadog-sampling-priority": "1", "x-datadog-trace-id": "4555236104497098341", "dd-pathway-ctx-base64": "some-base64-encoded-context", - } + }, ); }); @@ -171,16 +171,28 @@ describe("SQSEventTraceExtractor", () => { expect(mockDataStreamsCheckpointer.setConsumeCheckpoint).toHaveBeenCalledWith( "sqs", "arn:aws:sqs:us-east-1:123456789012:MyQueue", - ddHeaders + ddHeaders, ); }); it.each([ ["Records", {}, 0], ["Records first entry", { Records: [] }, 0], - ["messageAttributes in first entry", { Records: [{ messageAttributes: "{}", eventSourceARN: "arn:aws:sqs:us-east-1:MyQueue" }] }, 1], - ["_datadog in messageAttributes", { Records: [{ messageAttributes: {}, eventSourceARN: "arn:aws:sqs:us-east-1:MyQueue" }] }, 1], - ["stringValue in _datadog", { Records: [{ messageAttributes: { _datadog: {} }, eventSourceARN: "arn:aws:sqs:us-east-1:MyQueue" }] }, 1], + [ + "messageAttributes in first entry", + { Records: [{ messageAttributes: "{}", eventSourceARN: "arn:aws:sqs:us-east-1:MyQueue" }] }, + 1, + ], + [ + "_datadog in messageAttributes", + { Records: [{ messageAttributes: {}, eventSourceARN: "arn:aws:sqs:us-east-1:MyQueue" }] }, + 1, + ], + [ + "stringValue in _datadog", + { Records: [{ messageAttributes: { _datadog: {} }, eventSourceARN: "arn:aws:sqs:us-east-1:MyQueue" }] }, + 1, + ], ])("returns null and skips extracting when payload is missing '%s'", (_, payload, dsmCalls) => { const tracerWrapper = new TracerWrapper(); const extractor = new SQSEventTraceExtractor(tracerWrapper); @@ -194,7 +206,7 @@ describe("SQSEventTraceExtractor", () => { expect(mockDataStreamsCheckpointer.setConsumeCheckpoint).toHaveBeenCalledWith( "sqs", "arn:aws:sqs:us-east-1:MyQueue", - null + null, ); } }); diff --git a/src/trace/tracer-wrapper.spec.ts b/src/trace/tracer-wrapper.spec.ts index 4dbf1078..f0019f15 100644 --- a/src/trace/tracer-wrapper.spec.ts +++ b/src/trace/tracer-wrapper.spec.ts @@ -32,7 +32,6 @@ jest.mock("dd-trace", () => { } }); - describe("TracerWrapper", () => { beforeEach(() => { process.env["AWS_LAMBDA_FUNCTION_NAME"] = "my-lambda"; From a12d0ac33ab34d51883ecafa9b57236f57d7fb3f Mon Sep 17 00:00:00 2001 From: Michael Zhao Date: Mon, 28 Jul 2025 17:50:31 -0400 Subject: [PATCH 11/20] lint --- src/trace/tracer-wrapper.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/trace/tracer-wrapper.ts b/src/trace/tracer-wrapper.ts index f183b823..78c9fb24 100644 --- a/src/trace/tracer-wrapper.ts +++ b/src/trace/tracer-wrapper.ts @@ -100,7 +100,7 @@ export class TracerWrapper { return dest; } - public setConsumeCheckpoint(context_json: any, event_type: string, arn: string): void { + public setConsumeCheckpoint(contextJson: any, eventType: string, arn: string): void { if (!arn) { return; } @@ -110,10 +110,10 @@ export class TracerWrapper { } try { - this.tracer.dataStreamsCheckpointer.setConsumeCheckpoint(event_type, arn, context_json); + this.tracer.dataStreamsCheckpointer.setConsumeCheckpoint(eventType, arn, contextJson); } catch (err) { if (err instanceof Object || err instanceof Error) { - logDebug(`DSM: Failed to set consume checkpoint for ${event_type} ${arn}:`, err); + logDebug(`DSM: Failed to set consume checkpoint for ${eventType} ${arn}:`, err); } } } From 99f1c67d5e61e3c53f2bbe1a26ced135ee15f63d Mon Sep 17 00:00:00 2001 From: Michael Zhao Date: Mon, 28 Jul 2025 23:56:13 -0400 Subject: [PATCH 12/20] add False manual_checkpoint false --- src/trace/context/extractors/kinesis.spec.ts | 2 ++ src/trace/context/extractors/sns-sqs.spec.ts | 3 +++ src/trace/context/extractors/sns.spec.ts | 3 +++ src/trace/context/extractors/sqs.spec.ts | 3 +++ src/trace/tracer-wrapper.spec.ts | 2 +- src/trace/tracer-wrapper.ts | 2 +- 6 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/trace/context/extractors/kinesis.spec.ts b/src/trace/context/extractors/kinesis.spec.ts index 66504725..17bf28a9 100644 --- a/src/trace/context/extractors/kinesis.spec.ts +++ b/src/trace/context/extractors/kinesis.spec.ts @@ -98,6 +98,7 @@ describe("KinesisEventTraceExtractor", () => { "x-datadog-trace-id": "667309514221035538", "dd-pathway-ctx-base64": "some-base64-encoded-context", }, + false, ); }); @@ -120,6 +121,7 @@ describe("KinesisEventTraceExtractor", () => { "kinesis", "arn:aws:kinesis:test", null, + false, ); } }); diff --git a/src/trace/context/extractors/sns-sqs.spec.ts b/src/trace/context/extractors/sns-sqs.spec.ts index 3ccd6a21..ee07804a 100644 --- a/src/trace/context/extractors/sns-sqs.spec.ts +++ b/src/trace/context/extractors/sns-sqs.spec.ts @@ -100,6 +100,7 @@ describe("SNSSQSEventTraceExtractor", () => { "x-datadog-trace-id": "2776434475358637757", "dd-pathway-ctx-base64": "some-base64-encoded-context", }, + false, ); }); @@ -161,6 +162,7 @@ describe("SNSSQSEventTraceExtractor", () => { "x-datadog-trace-id": "7102291628443134919", "dd-pathway-ctx-base64": "some-base64-encoded-context", }, + false, ); }); @@ -198,6 +200,7 @@ describe("SNSSQSEventTraceExtractor", () => { "sqs", "arn:aws:sqs:us-east-1:test", null, + false, ); } }); diff --git a/src/trace/context/extractors/sns.spec.ts b/src/trace/context/extractors/sns.spec.ts index 5c896268..48e944b2 100644 --- a/src/trace/context/extractors/sns.spec.ts +++ b/src/trace/context/extractors/sns.spec.ts @@ -112,6 +112,7 @@ describe("SNSEventTraceExtractor", () => { "x-datadog-trace-id": "6966585609680374559", "dd-pathway-ctx-base64": "some-base64-encoded-context", }, + false, ); }); @@ -184,6 +185,7 @@ describe("SNSEventTraceExtractor", () => { "x-datadog-trace-id": "7102291628443134919", "dd-pathway-ctx-base64": "some-base64-encoded-context", }, + false, ); }); @@ -218,6 +220,7 @@ describe("SNSEventTraceExtractor", () => { "sns", "arn:aws:sns:eu-west-1:test", null, + false, ); } }); diff --git a/src/trace/context/extractors/sqs.spec.ts b/src/trace/context/extractors/sqs.spec.ts index 1afa2f63..b6ee3e9b 100644 --- a/src/trace/context/extractors/sqs.spec.ts +++ b/src/trace/context/extractors/sqs.spec.ts @@ -108,6 +108,7 @@ describe("SQSEventTraceExtractor", () => { "x-datadog-trace-id": "4555236104497098341", "dd-pathway-ctx-base64": "some-base64-encoded-context", }, + false, ); }); @@ -172,6 +173,7 @@ describe("SQSEventTraceExtractor", () => { "sqs", "arn:aws:sqs:us-east-1:123456789012:MyQueue", ddHeaders, + false, ); }); @@ -207,6 +209,7 @@ describe("SQSEventTraceExtractor", () => { "sqs", "arn:aws:sqs:us-east-1:MyQueue", null, + false, ); } }); diff --git a/src/trace/tracer-wrapper.spec.ts b/src/trace/tracer-wrapper.spec.ts index f0019f15..5a388603 100644 --- a/src/trace/tracer-wrapper.spec.ts +++ b/src/trace/tracer-wrapper.spec.ts @@ -111,7 +111,7 @@ describe("TracerWrapper", () => { wrapper.setConsumeCheckpoint(contextJson, eventType, arn); - expect(mockDataStreamsCheckpointer.setConsumeCheckpoint).toHaveBeenCalledWith(eventType, arn, contextJson); + expect(mockDataStreamsCheckpointer.setConsumeCheckpoint).toHaveBeenCalledWith(eventType, arn, contextJson, false); }); it("should not call internal setConsumeCheckpoint when DD_DATA_STREAMS_ENABLED is off", () => { diff --git a/src/trace/tracer-wrapper.ts b/src/trace/tracer-wrapper.ts index 78c9fb24..f5ec2617 100644 --- a/src/trace/tracer-wrapper.ts +++ b/src/trace/tracer-wrapper.ts @@ -110,7 +110,7 @@ export class TracerWrapper { } try { - this.tracer.dataStreamsCheckpointer.setConsumeCheckpoint(eventType, arn, contextJson); + this.tracer.dataStreamsCheckpointer.setConsumeCheckpoint(eventType, arn, contextJson, false); } catch (err) { if (err instanceof Object || err instanceof Error) { logDebug(`DSM: Failed to set consume checkpoint for ${eventType} ${arn}:`, err); From 7b4c4aad677c186e7f45bd2b03a87484d33bfea5 Mon Sep 17 00:00:00 2001 From: Michael Zhao Date: Tue, 29 Jul 2025 00:03:23 -0400 Subject: [PATCH 13/20] fix formatting --- src/trace/context/extractors/sns-sqs.spec.ts | 16 ++-------------- src/trace/context/extractors/sns.spec.ts | 14 ++------------ src/trace/context/extractors/sqs.spec.ts | 18 +++--------------- 3 files changed, 7 insertions(+), 41 deletions(-) diff --git a/src/trace/context/extractors/sns-sqs.spec.ts b/src/trace/context/extractors/sns-sqs.spec.ts index ee07804a..bdcf6b23 100644 --- a/src/trace/context/extractors/sns-sqs.spec.ts +++ b/src/trace/context/extractors/sns-sqs.spec.ts @@ -172,20 +172,8 @@ describe("SNSSQSEventTraceExtractor", () => { ["Records first entry body", { Records: [{}] }, 0], ["valid data in body", { Records: [{ body: "{", eventSourceARN: "arn:aws:sqs:us-east-1:test" }] }, 1], // JSON.parse should fail ["MessageAttributes in body", { Records: [{ body: "{}", eventSourceARN: "arn:aws:sqs:us-east-1:test" }] }, 1], - [ - "_datadog in MessageAttributes", - { - Records: [ - { body: '{"MessageAttributes":{"text":"Hello, world!"}}', eventSourceARN: "arn:aws:sqs:us-east-1:test" }, - ], - }, - 1, - ], - [ - "Value in _datadog", - { Records: [{ body: '{"MessageAttributes":{"_datadog":{}}}', eventSourceARN: "arn:aws:sqs:us-east-1:test" }] }, - 1, - ], + ["_datadog in MessageAttributes", { Records: [{ body: '{"MessageAttributes":{"text":"Hello, world!"}}', eventSourceARN: "arn:aws:sqs:us-east-1:test" }] }, 1], + ["Value in _datadog", { Records: [{ body: '{"MessageAttributes":{"_datadog":{}}}', eventSourceARN: "arn:aws:sqs:us-east-1:test" }] }, 1], ])("returns null and skips extracting when payload is missing '%s'", (_, payload, dsmCalls) => { const tracerWrapper = new TracerWrapper(); const extractor = new SNSSQSEventTraceExtractor(tracerWrapper); diff --git a/src/trace/context/extractors/sns.spec.ts b/src/trace/context/extractors/sns.spec.ts index 48e944b2..8ffc58d7 100644 --- a/src/trace/context/extractors/sns.spec.ts +++ b/src/trace/context/extractors/sns.spec.ts @@ -194,18 +194,8 @@ describe("SNSEventTraceExtractor", () => { ["Records first entry", { Records: [] }, 0], ["Records first entry Sns", { Records: [{}] }, 0], ["MessageAttributes in Sns", { Records: [{ Sns: "{TopicArn: 'arn:aws:sns:eu-west-1:test'}" }] }, 0], - [ - "_datadog in MessageAttributes", - { - Records: [{ Sns: { MessageAttributes: { text: "Hello, world!" }, TopicArn: "arn:aws:sns:eu-west-1:test" } }], - }, - 1, - ], - [ - "Value in _datadog", - { Records: [{ Sns: { MessageAttributes: { _datadog: {} }, TopicArn: "arn:aws:sns:eu-west-1:test" } }] }, - 1, - ], + ["_datadog in MessageAttributes", { Records: [{ Sns: { MessageAttributes: { text: "Hello, world!" }, TopicArn: "arn:aws:sns:eu-west-1:test" } }] }, 1], + ["Value in _datadog", { Records: [{ Sns: { MessageAttributes: { _datadog: {} }, TopicArn: "arn:aws:sns:eu-west-1:test" } }] }, 1], ])("returns null and skips extracting when payload is missing '%s'", (_, payload, dsmCalls) => { const tracerWrapper = new TracerWrapper(); const extractor = new SNSEventTraceExtractor(tracerWrapper); diff --git a/src/trace/context/extractors/sqs.spec.ts b/src/trace/context/extractors/sqs.spec.ts index b6ee3e9b..d88958c6 100644 --- a/src/trace/context/extractors/sqs.spec.ts +++ b/src/trace/context/extractors/sqs.spec.ts @@ -180,21 +180,9 @@ describe("SQSEventTraceExtractor", () => { it.each([ ["Records", {}, 0], ["Records first entry", { Records: [] }, 0], - [ - "messageAttributes in first entry", - { Records: [{ messageAttributes: "{}", eventSourceARN: "arn:aws:sqs:us-east-1:MyQueue" }] }, - 1, - ], - [ - "_datadog in messageAttributes", - { Records: [{ messageAttributes: {}, eventSourceARN: "arn:aws:sqs:us-east-1:MyQueue" }] }, - 1, - ], - [ - "stringValue in _datadog", - { Records: [{ messageAttributes: { _datadog: {} }, eventSourceARN: "arn:aws:sqs:us-east-1:MyQueue" }] }, - 1, - ], + ["messageAttributes in first entry", { Records: [{ messageAttributes: "{}", eventSourceARN: "arn:aws:sqs:us-east-1:MyQueue" }] }, 1], + ["_datadog in messageAttributes", { Records: [{ messageAttributes: {}, eventSourceARN: "arn:aws:sqs:us-east-1:MyQueue" }] }, 1], + ["stringValue in _datadog", { Records: [{ messageAttributes: { _datadog: {} }, eventSourceARN: "arn:aws:sqs:us-east-1:MyQueue" }] }, 1], ])("returns null and skips extracting when payload is missing '%s'", (_, payload, dsmCalls) => { const tracerWrapper = new TracerWrapper(); const extractor = new SQSEventTraceExtractor(tracerWrapper); From 5059c5c0f135e8d221c84e425fbd41dd2eb66c86 Mon Sep 17 00:00:00 2001 From: Michael Zhao Date: Tue, 29 Jul 2025 00:07:21 -0400 Subject: [PATCH 14/20] optimization --- src/trace/context/extractors/sqs.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/trace/context/extractors/sqs.ts b/src/trace/context/extractors/sqs.ts index 5e76b6f4..54d0d755 100644 --- a/src/trace/context/extractors/sqs.ts +++ b/src/trace/context/extractors/sqs.ts @@ -26,8 +26,9 @@ export class SQSEventTraceExtractor implements EventTraceExtractor { } if (headers !== undefined) { - this.tracerWrapper.setConsumeCheckpoint(JSON.parse(headers), "sqs", sourceARN); - const traceContext = this.tracerWrapper.extract(JSON.parse(headers)); + const parsedHeaders = JSON.parse(headers); + this.tracerWrapper.setConsumeCheckpoint(parsedHeaders, "sqs", sourceARN); + const traceContext = this.tracerWrapper.extract(parsedHeaders); if (traceContext) { logDebug("Extracted trace context from SQS event messageAttributes"); return traceContext; From 7d3fdbad858a558a24d1c3de89fc5f543307cf41 Mon Sep 17 00:00:00 2001 From: Michael Zhao Date: Tue, 29 Jul 2025 00:08:52 -0400 Subject: [PATCH 15/20] clean --- src/trace/context/extractors/sns-sqs.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/trace/context/extractors/sns-sqs.ts b/src/trace/context/extractors/sns-sqs.ts index 8d7dacce..356e7d50 100644 --- a/src/trace/context/extractors/sns-sqs.ts +++ b/src/trace/context/extractors/sns-sqs.ts @@ -27,7 +27,7 @@ export class SNSSQSEventTraceExtractor implements EventTraceExtractor { } const traceContext = this.tracerWrapper.extract(headers); - this.tracerWrapper.setConsumeCheckpoint(headers, "sqs", event.Records[0].eventSourceARN); + this.tracerWrapper.setConsumeCheckpoint(headers, "sqs", sourceARN); if (traceContext) { logDebug("Extracted trace context from SNS-SQS event"); return traceContext; From c49e96f435942d62360084686ab3da5e819e7228 Mon Sep 17 00:00:00 2001 From: Michael Zhao Date: Tue, 29 Jul 2025 00:24:08 -0400 Subject: [PATCH 16/20] add prettier ignore --- src/trace/context/extractors/sns-sqs.spec.ts | 1 + src/trace/context/extractors/sns.spec.ts | 1 + src/trace/context/extractors/sqs.spec.ts | 1 + 3 files changed, 3 insertions(+) diff --git a/src/trace/context/extractors/sns-sqs.spec.ts b/src/trace/context/extractors/sns-sqs.spec.ts index bdcf6b23..3c927a06 100644 --- a/src/trace/context/extractors/sns-sqs.spec.ts +++ b/src/trace/context/extractors/sns-sqs.spec.ts @@ -166,6 +166,7 @@ describe("SNSSQSEventTraceExtractor", () => { ); }); + // prettier-ignore it.each([ ["Records", {}, 0], ["Records first entry", { Records: [] }, 0], diff --git a/src/trace/context/extractors/sns.spec.ts b/src/trace/context/extractors/sns.spec.ts index 8ffc58d7..845a4f7c 100644 --- a/src/trace/context/extractors/sns.spec.ts +++ b/src/trace/context/extractors/sns.spec.ts @@ -189,6 +189,7 @@ describe("SNSEventTraceExtractor", () => { ); }); + // prettier-ignore it.each([ ["Records", {}, 0], ["Records first entry", { Records: [] }, 0], diff --git a/src/trace/context/extractors/sqs.spec.ts b/src/trace/context/extractors/sqs.spec.ts index d88958c6..1bbd088f 100644 --- a/src/trace/context/extractors/sqs.spec.ts +++ b/src/trace/context/extractors/sqs.spec.ts @@ -177,6 +177,7 @@ describe("SQSEventTraceExtractor", () => { ); }); + // prettier-ignore it.each([ ["Records", {}, 0], ["Records first entry", { Records: [] }, 0], From e422375f34d348f08a0701be9dfca6f15b7ea6bc Mon Sep 17 00:00:00 2001 From: Michael Zhao Date: Tue, 29 Jul 2025 13:55:32 -0400 Subject: [PATCH 17/20] add optional chaining --- src/trace/context/extractors/kinesis.ts | 2 +- src/trace/context/extractors/sns-sqs.ts | 2 +- src/trace/context/extractors/sns.ts | 2 +- src/trace/context/extractors/sqs.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/trace/context/extractors/kinesis.ts b/src/trace/context/extractors/kinesis.ts index fbf3b4ea..71bd6d3c 100644 --- a/src/trace/context/extractors/kinesis.ts +++ b/src/trace/context/extractors/kinesis.ts @@ -12,7 +12,7 @@ export class KinesisEventTraceExtractor implements EventTraceExtractor { const kinesisData = event?.Records?.[0]?.kinesis.data; if (kinesisData === undefined) return null; - sourceARN = event.Records[0].eventSourceARN; + sourceARN = event?.Records?.[0]?.eventSourceARN; try { const decodedData = Buffer.from(kinesisData, "base64").toString("ascii"); diff --git a/src/trace/context/extractors/sns-sqs.ts b/src/trace/context/extractors/sns-sqs.ts index 356e7d50..83b1e315 100644 --- a/src/trace/context/extractors/sns-sqs.ts +++ b/src/trace/context/extractors/sns-sqs.ts @@ -14,7 +14,7 @@ export class SNSSQSEventTraceExtractor implements EventTraceExtractor { try { // First try to extract trace context from message attributes if (event?.Records?.[0]?.body) { - sourceARN = event.Records[0].eventSourceARN; + sourceARN = event?.Records?.[0]?.eventSourceARN; const parsedBody = JSON.parse(event?.Records?.[0]?.body) as SNSMessage; const messageAttribute = parsedBody?.MessageAttributes?._datadog; if (messageAttribute?.Value) { diff --git a/src/trace/context/extractors/sns.ts b/src/trace/context/extractors/sns.ts index 3973763c..ed3f5f22 100644 --- a/src/trace/context/extractors/sns.ts +++ b/src/trace/context/extractors/sns.ts @@ -13,7 +13,7 @@ export class SNSEventTraceExtractor implements EventTraceExtractor { try { // First try to extract trace context from message attributes const messageAttribute = event?.Records?.[0]?.Sns?.MessageAttributes?._datadog; - sourceARN = event.Records[0].Sns.TopicArn; + sourceARN = event?.Records?.[0]?.Sns?.TopicArn; if (messageAttribute?.Value) { let headers; if (messageAttribute.Type === "String") { diff --git a/src/trace/context/extractors/sqs.ts b/src/trace/context/extractors/sqs.ts index 54d0d755..48e488b4 100644 --- a/src/trace/context/extractors/sqs.ts +++ b/src/trace/context/extractors/sqs.ts @@ -13,7 +13,7 @@ export class SQSEventTraceExtractor implements EventTraceExtractor { try { // First try to extract trace context from message attributes let headers = event?.Records?.[0]?.messageAttributes?._datadog?.stringValue; - sourceARN = event.Records[0].eventSourceARN; + sourceARN = event?.Records?.[0]?.eventSourceARN; if (!headers) { // Then try to get from binary value. This happens when SNS->SQS, but SNS has raw message delivery enabled. From 893d39149c886c9f8cd45d4fb29ae8b4cf3cc1bf Mon Sep 17 00:00:00 2001 From: Michael Zhao Date: Tue, 29 Jul 2025 13:57:10 -0400 Subject: [PATCH 18/20] add log debug for no arn case --- src/trace/tracer-wrapper.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/trace/tracer-wrapper.ts b/src/trace/tracer-wrapper.ts index f5ec2617..dd9f4c9d 100644 --- a/src/trace/tracer-wrapper.ts +++ b/src/trace/tracer-wrapper.ts @@ -102,6 +102,7 @@ export class TracerWrapper { public setConsumeCheckpoint(contextJson: any, eventType: string, arn: string): void { if (!arn) { + logDebug("DSM: No ARN provided, skipping setConsumeCheckpoint"); return; } From bc8cb73fb9e82ed7e6aa86349032233ed98c5efa Mon Sep 17 00:00:00 2001 From: Michael Zhao Date: Wed, 13 Aug 2025 12:44:23 -0400 Subject: [PATCH 19/20] merge commit --- .../snapshots/logs/esm_node18.log | 18 +- .../snapshots/logs/esm_node20.log | 18 +- .../snapshots/logs/esm_node22.log | 18 +- .../logs/process-input-traced_node18.log | 18 +- .../logs/process-input-traced_node20.log | 18 +- .../logs/process-input-traced_node22.log | 18 +- .../logs/status-code-500s_node18.log | 18 +- .../logs/status-code-500s_node20.log | 18 +- .../logs/status-code-500s_node22.log | 18 +- package.json | 2 +- src/trace/context/extractor-utils.spec.ts | 164 +++++++ src/trace/context/extractor-utils.ts | 62 +++ src/trace/context/extractor.spec.ts | 448 +++++++----------- src/trace/context/extractor.ts | 12 +- .../extractors/event-bridge-sqs.spec.ts | 43 +- .../context/extractors/event-bridge-sqs.ts | 30 +- .../context/extractors/event-bridge.spec.ts | 62 +++ src/trace/context/extractors/event-bridge.ts | 22 +- src/trace/context/extractors/sns-sqs.spec.ts | 50 ++ src/trace/context/extractors/sns.spec.ts | 53 +++ src/trace/context/extractors/sqs.spec.ts | 50 ++ .../context/extractors/step-function.spec.ts | 73 +++ src/trace/context/extractors/step-function.ts | 13 +- src/trace/listener.spec.ts | 235 ++++++++- src/trace/listener.ts | 24 +- src/trace/span-inferrer.spec.ts | 216 +++++++++ src/trace/span-inferrer.ts | 32 +- src/trace/tracer-wrapper.ts | 1 + yarn.lock | 92 +++- 29 files changed, 1417 insertions(+), 429 deletions(-) create mode 100644 src/trace/context/extractor-utils.spec.ts create mode 100644 src/trace/context/extractor-utils.ts diff --git a/integration_tests/snapshots/logs/esm_node18.log b/integration_tests/snapshots/logs/esm_node18.log index 89e3b8dc..7e97d980 100644 --- a/integration_tests/snapshots/logs/esm_node18.log +++ b/integration_tests/snapshots/logs/esm_node18.log @@ -72,6 +72,7 @@ START "service": "integration-tests-js-XXXX-esm_node18", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "true", "function_arn":"XXXX_node18", "function_version": "$LATEST", "request_id":"XXXX", @@ -88,7 +89,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 1, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -178,6 +178,7 @@ START "service": "integration-tests-js-XXXX-esm_node18", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node18", "function_version": "$LATEST", "request_id":"XXXX", @@ -191,7 +192,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -281,6 +281,7 @@ START "service": "integration-tests-js-XXXX-esm_node18", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node18", "function_version": "$LATEST", "request_id":"XXXX", @@ -294,7 +295,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -384,6 +384,7 @@ START "service": "integration-tests-js-XXXX-esm_node18", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node18", "function_version": "$LATEST", "request_id":"XXXX", @@ -397,7 +398,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -486,6 +486,7 @@ START "service": "integration-tests-js-XXXX-esm_node18", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node18", "function_version": "$LATEST", "request_id":"XXXX", @@ -499,7 +500,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -588,6 +588,7 @@ START "service": "integration-tests-js-XXXX-esm_node18", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node18", "function_version": "$LATEST", "request_id":"XXXX", @@ -601,7 +602,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -690,6 +690,7 @@ START "service": "integration-tests-js-XXXX-esm_node18", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node18", "function_version": "$LATEST", "request_id":"XXXX", @@ -703,7 +704,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -791,6 +791,7 @@ START "service": "integration-tests-js-XXXX-esm_node18", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node18", "function_version": "$LATEST", "request_id":"XXXX", @@ -804,7 +805,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -891,6 +891,7 @@ START "service": "integration-tests-js-XXXX-esm_node18", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node18", "function_version": "$LATEST", "request_id":"XXXX", @@ -904,7 +905,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 diff --git a/integration_tests/snapshots/logs/esm_node20.log b/integration_tests/snapshots/logs/esm_node20.log index e20dd2d4..5ba9f569 100644 --- a/integration_tests/snapshots/logs/esm_node20.log +++ b/integration_tests/snapshots/logs/esm_node20.log @@ -72,6 +72,7 @@ START "service": "integration-tests-js-XXXX-esm_node20", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "true", "function_arn":"XXXX_node20", "function_version": "$LATEST", "request_id":"XXXX", @@ -88,7 +89,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 1, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -178,6 +178,7 @@ START "service": "integration-tests-js-XXXX-esm_node20", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node20", "function_version": "$LATEST", "request_id":"XXXX", @@ -191,7 +192,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -281,6 +281,7 @@ START "service": "integration-tests-js-XXXX-esm_node20", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node20", "function_version": "$LATEST", "request_id":"XXXX", @@ -294,7 +295,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -384,6 +384,7 @@ START "service": "integration-tests-js-XXXX-esm_node20", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node20", "function_version": "$LATEST", "request_id":"XXXX", @@ -397,7 +398,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -486,6 +486,7 @@ START "service": "integration-tests-js-XXXX-esm_node20", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node20", "function_version": "$LATEST", "request_id":"XXXX", @@ -499,7 +500,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -588,6 +588,7 @@ START "service": "integration-tests-js-XXXX-esm_node20", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node20", "function_version": "$LATEST", "request_id":"XXXX", @@ -601,7 +602,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -690,6 +690,7 @@ START "service": "integration-tests-js-XXXX-esm_node20", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node20", "function_version": "$LATEST", "request_id":"XXXX", @@ -703,7 +704,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -791,6 +791,7 @@ START "service": "integration-tests-js-XXXX-esm_node20", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node20", "function_version": "$LATEST", "request_id":"XXXX", @@ -804,7 +805,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -891,6 +891,7 @@ START "service": "integration-tests-js-XXXX-esm_node20", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node20", "function_version": "$LATEST", "request_id":"XXXX", @@ -904,7 +905,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 diff --git a/integration_tests/snapshots/logs/esm_node22.log b/integration_tests/snapshots/logs/esm_node22.log index abc1d77c..24cc90b7 100644 --- a/integration_tests/snapshots/logs/esm_node22.log +++ b/integration_tests/snapshots/logs/esm_node22.log @@ -72,6 +72,7 @@ START "service": "integration-tests-js-XXXX-esm_node22", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "true", "function_arn":"XXXX_node22", "function_version": "$LATEST", "request_id":"XXXX", @@ -88,7 +89,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 1, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -178,6 +178,7 @@ START "service": "integration-tests-js-XXXX-esm_node22", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node22", "function_version": "$LATEST", "request_id":"XXXX", @@ -191,7 +192,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -281,6 +281,7 @@ START "service": "integration-tests-js-XXXX-esm_node22", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node22", "function_version": "$LATEST", "request_id":"XXXX", @@ -294,7 +295,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -384,6 +384,7 @@ START "service": "integration-tests-js-XXXX-esm_node22", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node22", "function_version": "$LATEST", "request_id":"XXXX", @@ -397,7 +398,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -486,6 +486,7 @@ START "service": "integration-tests-js-XXXX-esm_node22", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node22", "function_version": "$LATEST", "request_id":"XXXX", @@ -499,7 +500,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -588,6 +588,7 @@ START "service": "integration-tests-js-XXXX-esm_node22", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node22", "function_version": "$LATEST", "request_id":"XXXX", @@ -601,7 +602,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -690,6 +690,7 @@ START "service": "integration-tests-js-XXXX-esm_node22", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node22", "function_version": "$LATEST", "request_id":"XXXX", @@ -703,7 +704,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -791,6 +791,7 @@ START "service": "integration-tests-js-XXXX-esm_node22", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node22", "function_version": "$LATEST", "request_id":"XXXX", @@ -804,7 +805,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -891,6 +891,7 @@ START "service": "integration-tests-js-XXXX-esm_node22", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node22", "function_version": "$LATEST", "request_id":"XXXX", @@ -904,7 +905,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 diff --git a/integration_tests/snapshots/logs/process-input-traced_node18.log b/integration_tests/snapshots/logs/process-input-traced_node18.log index cb54b2ba..e09dca39 100644 --- a/integration_tests/snapshots/logs/process-input-traced_node18.log +++ b/integration_tests/snapshots/logs/process-input-traced_node18.log @@ -79,6 +79,7 @@ START "service": "integration-tests-js-XXXX-process-input-traced_node18", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "true", "function_arn":"XXXX_node18", "function_version": "$LATEST", "request_id":"XXXX", @@ -97,7 +98,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 1, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -244,6 +244,7 @@ START "service": "integration-tests-js-XXXX-process-input-traced_node18", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node18", "function_version": "$LATEST", "request_id":"XXXX", @@ -257,7 +258,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -402,6 +402,7 @@ START "service": "integration-tests-js-XXXX-process-input-traced_node18", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node18", "function_version": "$LATEST", "request_id":"XXXX", @@ -415,7 +416,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -560,6 +560,7 @@ START "service": "integration-tests-js-XXXX-process-input-traced_node18", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node18", "function_version": "$LATEST", "request_id":"XXXX", @@ -573,7 +574,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -717,6 +717,7 @@ START "service": "integration-tests-js-XXXX-process-input-traced_node18", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node18", "function_version": "$LATEST", "request_id":"XXXX", @@ -731,7 +732,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -853,6 +853,7 @@ START "service": "integration-tests-js-XXXX-process-input-traced_node18", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node18", "function_version": "$LATEST", "request_id":"XXXX", @@ -867,7 +868,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -989,6 +989,7 @@ START "service": "integration-tests-js-XXXX-process-input-traced_node18", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node18", "function_version": "$LATEST", "request_id":"XXXX", @@ -1003,7 +1004,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -1124,6 +1124,7 @@ START "service": "integration-tests-js-XXXX-process-input-traced_node18", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node18", "function_version": "$LATEST", "request_id":"XXXX", @@ -1138,7 +1139,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -1258,6 +1258,7 @@ START "service": "integration-tests-js-XXXX-process-input-traced_node18", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node18", "function_version": "$LATEST", "request_id":"XXXX", @@ -1272,7 +1273,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 diff --git a/integration_tests/snapshots/logs/process-input-traced_node20.log b/integration_tests/snapshots/logs/process-input-traced_node20.log index 29407e07..5bb896f7 100644 --- a/integration_tests/snapshots/logs/process-input-traced_node20.log +++ b/integration_tests/snapshots/logs/process-input-traced_node20.log @@ -79,6 +79,7 @@ START "service": "integration-tests-js-XXXX-process-input-traced_node20", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "true", "function_arn":"XXXX_node20", "function_version": "$LATEST", "request_id":"XXXX", @@ -97,7 +98,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 1, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -244,6 +244,7 @@ START "service": "integration-tests-js-XXXX-process-input-traced_node20", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node20", "function_version": "$LATEST", "request_id":"XXXX", @@ -257,7 +258,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -402,6 +402,7 @@ START "service": "integration-tests-js-XXXX-process-input-traced_node20", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node20", "function_version": "$LATEST", "request_id":"XXXX", @@ -415,7 +416,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -560,6 +560,7 @@ START "service": "integration-tests-js-XXXX-process-input-traced_node20", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node20", "function_version": "$LATEST", "request_id":"XXXX", @@ -573,7 +574,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -717,6 +717,7 @@ START "service": "integration-tests-js-XXXX-process-input-traced_node20", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node20", "function_version": "$LATEST", "request_id":"XXXX", @@ -731,7 +732,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -853,6 +853,7 @@ START "service": "integration-tests-js-XXXX-process-input-traced_node20", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node20", "function_version": "$LATEST", "request_id":"XXXX", @@ -867,7 +868,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -989,6 +989,7 @@ START "service": "integration-tests-js-XXXX-process-input-traced_node20", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node20", "function_version": "$LATEST", "request_id":"XXXX", @@ -1003,7 +1004,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -1124,6 +1124,7 @@ START "service": "integration-tests-js-XXXX-process-input-traced_node20", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node20", "function_version": "$LATEST", "request_id":"XXXX", @@ -1138,7 +1139,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -1258,6 +1258,7 @@ START "service": "integration-tests-js-XXXX-process-input-traced_node20", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node20", "function_version": "$LATEST", "request_id":"XXXX", @@ -1272,7 +1273,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 diff --git a/integration_tests/snapshots/logs/process-input-traced_node22.log b/integration_tests/snapshots/logs/process-input-traced_node22.log index 128ed8e6..c2ff6932 100644 --- a/integration_tests/snapshots/logs/process-input-traced_node22.log +++ b/integration_tests/snapshots/logs/process-input-traced_node22.log @@ -79,6 +79,7 @@ START "service": "integration-tests-js-XXXX-process-input-traced_node22", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "true", "function_arn":"XXXX_node22", "function_version": "$LATEST", "request_id":"XXXX", @@ -97,7 +98,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 1, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -244,6 +244,7 @@ START "service": "integration-tests-js-XXXX-process-input-traced_node22", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node22", "function_version": "$LATEST", "request_id":"XXXX", @@ -257,7 +258,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -402,6 +402,7 @@ START "service": "integration-tests-js-XXXX-process-input-traced_node22", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node22", "function_version": "$LATEST", "request_id":"XXXX", @@ -415,7 +416,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -560,6 +560,7 @@ START "service": "integration-tests-js-XXXX-process-input-traced_node22", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node22", "function_version": "$LATEST", "request_id":"XXXX", @@ -573,7 +574,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -717,6 +717,7 @@ START "service": "integration-tests-js-XXXX-process-input-traced_node22", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node22", "function_version": "$LATEST", "request_id":"XXXX", @@ -731,7 +732,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -853,6 +853,7 @@ START "service": "integration-tests-js-XXXX-process-input-traced_node22", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node22", "function_version": "$LATEST", "request_id":"XXXX", @@ -867,7 +868,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -989,6 +989,7 @@ START "service": "integration-tests-js-XXXX-process-input-traced_node22", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node22", "function_version": "$LATEST", "request_id":"XXXX", @@ -1003,7 +1004,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -1124,6 +1124,7 @@ START "service": "integration-tests-js-XXXX-process-input-traced_node22", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node22", "function_version": "$LATEST", "request_id":"XXXX", @@ -1138,7 +1139,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -1258,6 +1258,7 @@ START "service": "integration-tests-js-XXXX-process-input-traced_node22", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node22", "function_version": "$LATEST", "request_id":"XXXX", @@ -1272,7 +1273,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 diff --git a/integration_tests/snapshots/logs/status-code-500s_node18.log b/integration_tests/snapshots/logs/status-code-500s_node18.log index ea151942..7476d1b1 100644 --- a/integration_tests/snapshots/logs/status-code-500s_node18.log +++ b/integration_tests/snapshots/logs/status-code-500s_node18.log @@ -85,6 +85,7 @@ START "service": "integration-tests-js-XXXX-status-code-500s_node18", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "true", "function_arn":"XXXX_node18", "function_version": "$LATEST", "request_id":"XXXX", @@ -101,7 +102,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 1, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -189,6 +189,7 @@ START "service": "integration-tests-js-XXXX-status-code-500s_node18", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node18", "function_version": "$LATEST", "request_id":"XXXX", @@ -202,7 +203,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -290,6 +290,7 @@ START "service": "integration-tests-js-XXXX-status-code-500s_node18", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node18", "function_version": "$LATEST", "request_id":"XXXX", @@ -303,7 +304,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -391,6 +391,7 @@ START "service": "integration-tests-js-XXXX-status-code-500s_node18", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node18", "function_version": "$LATEST", "request_id":"XXXX", @@ -404,7 +405,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -491,6 +491,7 @@ START "service": "integration-tests-js-XXXX-status-code-500s_node18", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node18", "function_version": "$LATEST", "request_id":"XXXX", @@ -504,7 +505,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -591,6 +591,7 @@ START "service": "integration-tests-js-XXXX-status-code-500s_node18", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node18", "function_version": "$LATEST", "request_id":"XXXX", @@ -604,7 +605,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -691,6 +691,7 @@ START "service": "integration-tests-js-XXXX-status-code-500s_node18", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node18", "function_version": "$LATEST", "request_id":"XXXX", @@ -704,7 +705,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -790,6 +790,7 @@ START "service": "integration-tests-js-XXXX-status-code-500s_node18", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node18", "function_version": "$LATEST", "request_id":"XXXX", @@ -803,7 +804,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -888,6 +888,7 @@ START "service": "integration-tests-js-XXXX-status-code-500s_node18", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node18", "function_version": "$LATEST", "request_id":"XXXX", @@ -901,7 +902,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 diff --git a/integration_tests/snapshots/logs/status-code-500s_node20.log b/integration_tests/snapshots/logs/status-code-500s_node20.log index dad2a923..913972aa 100644 --- a/integration_tests/snapshots/logs/status-code-500s_node20.log +++ b/integration_tests/snapshots/logs/status-code-500s_node20.log @@ -85,6 +85,7 @@ START "service": "integration-tests-js-XXXX-status-code-500s_node20", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "true", "function_arn":"XXXX_node20", "function_version": "$LATEST", "request_id":"XXXX", @@ -101,7 +102,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 1, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -189,6 +189,7 @@ START "service": "integration-tests-js-XXXX-status-code-500s_node20", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node20", "function_version": "$LATEST", "request_id":"XXXX", @@ -202,7 +203,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -290,6 +290,7 @@ START "service": "integration-tests-js-XXXX-status-code-500s_node20", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node20", "function_version": "$LATEST", "request_id":"XXXX", @@ -303,7 +304,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -391,6 +391,7 @@ START "service": "integration-tests-js-XXXX-status-code-500s_node20", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node20", "function_version": "$LATEST", "request_id":"XXXX", @@ -404,7 +405,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -491,6 +491,7 @@ START "service": "integration-tests-js-XXXX-status-code-500s_node20", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node20", "function_version": "$LATEST", "request_id":"XXXX", @@ -504,7 +505,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -591,6 +591,7 @@ START "service": "integration-tests-js-XXXX-status-code-500s_node20", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node20", "function_version": "$LATEST", "request_id":"XXXX", @@ -604,7 +605,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -691,6 +691,7 @@ START "service": "integration-tests-js-XXXX-status-code-500s_node20", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node20", "function_version": "$LATEST", "request_id":"XXXX", @@ -704,7 +705,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -790,6 +790,7 @@ START "service": "integration-tests-js-XXXX-status-code-500s_node20", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node20", "function_version": "$LATEST", "request_id":"XXXX", @@ -803,7 +804,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -888,6 +888,7 @@ START "service": "integration-tests-js-XXXX-status-code-500s_node20", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node20", "function_version": "$LATEST", "request_id":"XXXX", @@ -901,7 +902,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 diff --git a/integration_tests/snapshots/logs/status-code-500s_node22.log b/integration_tests/snapshots/logs/status-code-500s_node22.log index 51768009..5a2badea 100644 --- a/integration_tests/snapshots/logs/status-code-500s_node22.log +++ b/integration_tests/snapshots/logs/status-code-500s_node22.log @@ -85,6 +85,7 @@ START "service": "integration-tests-js-XXXX-status-code-500s_node22", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "true", "function_arn":"XXXX_node22", "function_version": "$LATEST", "request_id":"XXXX", @@ -101,7 +102,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 1, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -189,6 +189,7 @@ START "service": "integration-tests-js-XXXX-status-code-500s_node22", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node22", "function_version": "$LATEST", "request_id":"XXXX", @@ -202,7 +203,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -290,6 +290,7 @@ START "service": "integration-tests-js-XXXX-status-code-500s_node22", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node22", "function_version": "$LATEST", "request_id":"XXXX", @@ -303,7 +304,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -391,6 +391,7 @@ START "service": "integration-tests-js-XXXX-status-code-500s_node22", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node22", "function_version": "$LATEST", "request_id":"XXXX", @@ -404,7 +405,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -491,6 +491,7 @@ START "service": "integration-tests-js-XXXX-status-code-500s_node22", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node22", "function_version": "$LATEST", "request_id":"XXXX", @@ -504,7 +505,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -591,6 +591,7 @@ START "service": "integration-tests-js-XXXX-status-code-500s_node22", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node22", "function_version": "$LATEST", "request_id":"XXXX", @@ -604,7 +605,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -691,6 +691,7 @@ START "service": "integration-tests-js-XXXX-status-code-500s_node22", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node22", "function_version": "$LATEST", "request_id":"XXXX", @@ -704,7 +705,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -790,6 +790,7 @@ START "service": "integration-tests-js-XXXX-status-code-500s_node22", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node22", "function_version": "$LATEST", "request_id":"XXXX", @@ -803,7 +804,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 @@ -888,6 +888,7 @@ START "service": "integration-tests-js-XXXX-status-code-500s_node22", "version": "1.0.0", "runtime-id":"XXXX", + "cold_start": "false", "function_arn":"XXXX_node22", "function_version": "$LATEST", "request_id":"XXXX", @@ -901,7 +902,6 @@ START "language": "javascript" }, "metrics": { - "cold_start": 0, "_dd.measured": 1, "process_id":XXXX, "_sampling_priority_v1": 1 diff --git a/package.json b/package.json index 4900090d..cb806f9f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "datadog-lambda-js", - "version": "11.126.0", + "version": "12.127.0", "description": "Lambda client library that supports hybrid tracing in node js", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/src/trace/context/extractor-utils.spec.ts b/src/trace/context/extractor-utils.spec.ts new file mode 100644 index 00000000..afe577e5 --- /dev/null +++ b/src/trace/context/extractor-utils.spec.ts @@ -0,0 +1,164 @@ +import { TracerWrapper } from "../tracer-wrapper"; +import { extractTraceContext, extractFromAWSTraceHeader } from "./extractor-utils"; +import { StepFunctionContextService } from "../step-function-service"; + +describe("extractor-utils", () => { + beforeEach(() => { + StepFunctionContextService["_instance"] = undefined as any; + }); + describe("extractTraceContext", () => { + it("returns span context when tracer wrapper successfully extracts from headers", () => { + const legacyStepFunctionEvent = { + Execution: { + Id: "arn:aws:states:sa-east-1:425362996713:express:logs-to-traces-sequential:85a9933e-9e11-83dc-6a61-b92367b6c3be:3f7ef5c7-c8b8-4c88-90a1-d54aa7e7e2bf", + Input: { + MyInput: "MyValue", + }, + Name: "85a9933e-9e11-83dc-6a61-b92367b6c3be", + RoleArn: "arn:aws:iam::425362996713:role/service-role/StepFunctions-logs-to-traces-sequential-role-ccd69c03", + RedriveCount: 0, + StartTime: "2022-12-08T21:08:17.924Z", + }, + State: { + Name: "step-one", + EnteredTime: "2022-12-08T21:08:19.224Z", + RetryCount: 2, + }, + StateMachine: { + Id: "arn:aws:states:sa-east-1:425362996713:stateMachine:logs-to-traces-sequential", + Name: "my-state-machine", + }, + }; + + const tracerWrapper = new TracerWrapper(); + const result = extractTraceContext(legacyStepFunctionEvent, tracerWrapper); + + // Should return a span context from Step Function context since headers extraction fails + expect(result).not.toBeNull(); + }); + + it("returns null when no trace context can be extracted", () => { + const emptyEvent = { + someOtherProperty: "value", + }; + + const tracerWrapper = new TracerWrapper(); + const result = extractTraceContext(emptyEvent, tracerWrapper); + + expect(result).toBeNull(); + }); + + it("extracts context from LambdaRootStepFunctionContext", () => { + const lambdaRootStepFunctionEvent = { + _datadog: { + Execution: { + Id: "arn:aws:states:sa-east-1:425362996713:express:logs-to-traces-sequential:85a9933e-9e11-83dc-6a61-b92367b6c3be:3f7ef5c7-c8b8-4c88-90a1-d54aa7e7e2bf", + Input: { + MyInput: "MyValue", + }, + Name: "85a9933e-9e11-83dc-6a61-b92367b6c3be", + RoleArn: + "arn:aws:iam::425362996713:role/service-role/StepFunctions-logs-to-traces-sequential-role-ccd69c03", + RedriveCount: 0, + StartTime: "2022-12-08T21:08:17.924Z", + }, + State: { + Name: "step-one", + EnteredTime: "2022-12-08T21:08:19.224Z", + RetryCount: 2, + }, + StateMachine: { + Id: "arn:aws:states:sa-east-1:425362996713:stateMachine:logs-to-traces-sequential", + Name: "my-state-machine", + }, + "x-datadog-trace-id": "10593586103637578129", + "x-datadog-tags": "_dd.p.dm=-0,_dd.p.tid=6734e7c300000000", + "serverless-version": "v1", + }, + }; + + const tracerWrapper = new TracerWrapper(); + const result = extractTraceContext(lambdaRootStepFunctionEvent, tracerWrapper); + + expect(result).not.toBeNull(); + }); + + it("extracts context from NestedStepFunctionContext", () => { + const nestedStepFunctionEvent = { + _datadog: { + Execution: { + Id: "arn:aws:states:sa-east-1:425362996713:express:logs-to-traces-sequential:85a9933e-9e11-83dc-6a61-b92367b6c3be:3f7ef5c7-c8b8-4c88-90a1-d54aa7e7e2bf", + Input: { + MyInput: "MyValue", + }, + Name: "85a9933e-9e11-83dc-6a61-b92367b6c3be", + RoleArn: + "arn:aws:iam::425362996713:role/service-role/StepFunctions-logs-to-traces-sequential-role-ccd69c03", + RedriveCount: 0, + StartTime: "2022-12-08T21:08:17.924Z", + }, + State: { + Name: "step-one", + EnteredTime: "2022-12-08T21:08:19.224Z", + RetryCount: 2, + }, + StateMachine: { + Id: "arn:aws:states:sa-east-1:425362996713:stateMachine:logs-to-traces-sequential", + Name: "my-state-machine", + }, + RootExecutionId: + "arn:aws:states:sa-east-1:425362996713:express:logs-to-traces-sequential:a1b2c3d4-e5f6-7890-1234-56789abcdef0:9f8e7d6c-5b4a-3c2d-1e0f-123456789abc", + "serverless-version": "v1", + }, + }; + + const tracerWrapper = new TracerWrapper(); + const result = extractTraceContext(nestedStepFunctionEvent, tracerWrapper); + + expect(result).not.toBeNull(); + }); + + it("extracts context from legacy lambda StepFunctionContext", () => { + const event = { + Payload: { + Execution: { + Id: "arn:aws:states:sa-east-1:425362996713:express:logs-to-traces-sequential:85a9933e-9e11-83dc-6a61-b92367b6c3be:3f7ef5c7-c8b8-4c88-90a1-d54aa7e7e2bf", + Input: { + MyInput: "MyValue", + }, + Name: "85a9933e-9e11-83dc-6a61-b92367b6c3be", + RoleArn: + "arn:aws:iam::425362996713:role/service-role/StepFunctions-logs-to-traces-sequential-role-ccd69c03", + RedriveCount: 0, + StartTime: "2022-12-08T21:08:17.924Z", + }, + State: { + Name: "step-one", + EnteredTime: "2022-12-08T21:08:19.224Z", + RetryCount: 2, + }, + StateMachine: { + Id: "arn:aws:states:sa-east-1:425362996713:stateMachine:logs-to-traces-sequential", + Name: "my-state-machine", + }, + }, + }; + + const tracerWrapper = new TracerWrapper(); + const result = extractTraceContext(event, tracerWrapper); + + expect(result).not.toBeNull(); + }); + }); + + describe("extractFromAWSTraceHeader", () => { + it("returns null when AWS trace header is invalid", () => { + const invalidHeader = "invalid-header"; + const eventType = "SQS"; + + const result = extractFromAWSTraceHeader(invalidHeader, eventType); + + expect(result).toBeNull(); + }); + }); +}); diff --git a/src/trace/context/extractor-utils.ts b/src/trace/context/extractor-utils.ts new file mode 100644 index 00000000..5f0f1e17 --- /dev/null +++ b/src/trace/context/extractor-utils.ts @@ -0,0 +1,62 @@ +import { logDebug } from "../../utils"; +import { StepFunctionContextService } from "../step-function-service"; +import { SpanContextWrapper } from "../span-context-wrapper"; +import { TracerWrapper } from "../tracer-wrapper"; +import { XrayService } from "../xray-service"; + +/** + * Common utility functions for trace context extraction + */ + +/** + * Attempts to extract trace context from headers, falling back to Step Function context if needed + * @param headers The headers object to extract from + * @param tracerWrapper The tracer wrapper instance + * @returns SpanContextWrapper or null + */ +export function extractTraceContext(headers: any, tracerWrapper: TracerWrapper): SpanContextWrapper | null { + // First try to extract as regular trace headers + const traceContext = tracerWrapper.extract(headers); + if (traceContext) { + return traceContext; + } + + // If that fails, check if this is a Step Function context + const stepFunctionInstance = StepFunctionContextService.instance(headers); + + if (stepFunctionInstance.context !== undefined) { + if (stepFunctionInstance.spanContext !== null) { + return stepFunctionInstance.spanContext; + } + } + + return null; +} + +/** + * Extracts trace context from AWS Trace Header + * @param awsTraceHeader The AWS trace header string + * @param eventType The type of event (for logging) + * @returns SpanContextWrapper or null + */ +export function extractFromAWSTraceHeader(awsTraceHeader: string, eventType: string): SpanContextWrapper | null { + const traceContext = XrayService.extraceDDContextFromAWSTraceHeader(awsTraceHeader); + if (traceContext) { + logDebug(`Extracted trace context from ${eventType} event attributes AWSTraceHeader`); + return traceContext; + } else { + logDebug(`No Datadog trace context found from ${eventType} event attributes AWSTraceHeader`); + return null; + } +} + +/** + * Common error handler for extraction operations + * @param error The error that occurred + * @param eventType The type of event (for logging) + */ +export function handleExtractionError(error: unknown, eventType: string): void { + if (error instanceof Error) { + logDebug(`Unable to extract trace context from ${eventType} event`, error); + } +} diff --git a/src/trace/context/extractor.spec.ts b/src/trace/context/extractor.spec.ts index cb47b6f6..37cae4a3 100644 --- a/src/trace/context/extractor.spec.ts +++ b/src/trace/context/extractor.spec.ts @@ -17,7 +17,6 @@ import { SNSEventTraceExtractor, SNSSQSEventTraceExtractor, SQSEventTraceExtractor, - StepFunctionEventTraceExtractor, } from "./extractors"; import { StepFunctionContextService } from "../step-function-service"; import { SpanContextWrapper } from "../span-context-wrapper"; @@ -694,6 +693,54 @@ describe("TraceContextExtractor", () => { expect(traceContext?.sampleMode()).toBe("1"); expect(traceContext?.source).toBe("event"); }); + + it("extracts and applies deterministic trace ID from StepFunction event end-to-end", async () => { + // This test verifies the complete flow from Step Function event to trace ID assignment + const event = { + Execution: { + Id: "arn:aws:states:us-east-1:123456789012:execution:MyStateMachine:test-execution-id", + Name: "test-execution-id", + StartTime: "2024-01-01T00:00:00.000Z", + }, + State: { + Name: "ProcessData", + EnteredTime: "2024-01-01T00:00:01.000Z", + RetryCount: 0, + }, + StateMachine: { + Id: "arn:aws:states:us-east-1:123456789012:stateMachine:MyStateMachine", + Name: "MyStateMachine", + }, + }; + + const tracerWrapper = new TracerWrapper(); + const extractor = new TraceContextExtractor(tracerWrapper, {} as TraceConfig); + + // Extract trace context through the full pipeline + const traceContext = await extractor.extract(event, {} as Context); + expect(traceContext).not.toBeNull(); + + // Verify the trace context was extracted from Step Function + expect(traceContext?.source).toBe("event"); + + // Verify the trace ID is deterministic based on execution ID + // The trace ID should be generated from SHA256 hash of the execution ID + const traceId = traceContext?.toTraceId(); + expect(traceId).toBeDefined(); + expect(traceId).not.toBe("0"); // Should not be zero + expect(traceId).toMatch(/^\d+$/); // Should be numeric string + + // Verify the span ID is deterministic based on execution ID, state name, and entered time + const spanId = traceContext?.toSpanId(); + expect(spanId).toBeDefined(); + expect(spanId).not.toBe("0"); // Should not be zero + expect(spanId).toMatch(/^\d+$/); // Should be numeric string + + // Verify that extracting the same event produces the same trace IDs (deterministic) + const traceContext2 = await extractor.extract(event, {} as Context); + expect(traceContext2?.toTraceId()).toBe(traceId); + expect(traceContext2?.toSpanId()).toBe(spanId); + }); }); describe("lambda context", () => { @@ -734,301 +781,146 @@ describe("TraceContextExtractor", () => { expect(traceContext?.source).toBe("event"); }); }); - - describe("xray", () => { - // Fallback, event and context don't contain any trace context - it("extracts trace context from Xray", async () => { - process.env["_X_AMZN_TRACE_ID"] = "Root=1-5ce31dc2-2c779014b90ce44db5e03875;Parent=0b11cc4230d3e09e;Sampled=1"; - - const tracerWrapper = new TracerWrapper(); - const extractor = new TraceContextExtractor(tracerWrapper, {} as TraceConfig); - - const traceContext = await extractor.extract({}, {} as Context); - expect(traceContext).not.toBeNull(); - - expect(traceContext?.toTraceId()).toBe("4110911582297405557"); - expect(traceContext?.toSpanId()).toBe("797643193680388254"); - expect(traceContext?.sampleMode()).toBe("2"); - expect(traceContext?.source).toBe("xray"); - }); - }); }); - describe("getTraceEventExtractor", () => { - beforeEach(() => { - StepFunctionContextService["_instance"] = undefined as any; - }); - it.each([ - ["null", null], - ["undefined", undefined], - ["a string", "some-value"], - ["a number", 1234], - ["an object which doesn't match any expected event", { custom: "event" }], - ])("returns undefined when event is '%s'", (_, event) => { - const tracerWrapper = new TracerWrapper(); - const traceContextExtractor = new TraceContextExtractor(tracerWrapper, {} as TraceConfig); + describe("xray", () => { + // Fallback, event and context don't contain any trace context + it("extracts trace context from Xray", async () => { + process.env["_X_AMZN_TRACE_ID"] = "Root=1-5ce31dc2-2c779014b90ce44db5e03875;Parent=0b11cc4230d3e09e;Sampled=1"; - const extractor = traceContextExtractor["getTraceEventExtractor"](event); - - expect(extractor).toBeUndefined(); - }); - - // Returns the expected event extractor when payload is from that event - it.each([ - [ - "HTTPEventTraceExtractor", - "headers", - HTTPEventTraceExtractor, - { - headers: {}, - }, - ], - [ - "SNSEventTraceExtractor", - "SNS event", - SNSEventTraceExtractor, - { - Records: [ - { - Sns: {}, - }, - ], - }, - ], - [ - "SNSSQSventTraceExtractor", - "SNS to SQS event", - SNSSQSEventTraceExtractor, - { - Records: [ - { - eventSource: "aws:sqs", - body: '{"Type":"Notification", "TopicArn":"some-topic"}', - }, - ], - }, - ], - [ - "EventBridgeSQSTraceExtractor", - "EventBridge to SQS event", - EventBridgeSQSEventTraceExtractor, - { - Records: [ - { - eventSource: "aws:sqs", - body: '{"detail-type":"some-detail-type"}', - }, - ], - }, - ], - [ - "AppSyncEventTraceExtractor", - "AppSync event", - AppSyncEventTraceExtractor, - { - info: { - selectionSetGraphQL: "some-selection", - }, - request: { - headers: {}, - }, - }, - ], - [ - "SQSEventTraceExtractor", - "SQS event", - SQSEventTraceExtractor, - { - Records: [ - { - eventSource: "aws:sqs", - }, - ], - }, - ], - [ - "KinesisEventTraceExtractor", - "Kinesis stream event", - KinesisEventTraceExtractor, - { - Records: [ - { - kinesis: {}, - }, - ], - }, - ], - [ - "EventBridgeEventTraceExtractor", - "EventBridge event", - EventBridgeEventTraceExtractor, - { - "detail-type": "some-detail-type", - }, - ], - ])("returns %s when event contains %s", (_, __, _class, event) => { const tracerWrapper = new TracerWrapper(); - const traceContextExtractor = new TraceContextExtractor(tracerWrapper, {} as TraceConfig); + const extractor = new TraceContextExtractor(tracerWrapper, {} as TraceConfig); - const extractor = traceContextExtractor["getTraceEventExtractor"](event); + const traceContext = await extractor.extract({}, {} as Context); + expect(traceContext).not.toBeNull(); - expect(extractor).toBeInstanceOf(_class); + expect(traceContext?.toTraceId()).toBe("4110911582297405557"); + expect(traceContext?.toSpanId()).toBe("797643193680388254"); + expect(traceContext?.sampleMode()).toBe("2"); + expect(traceContext?.source).toBe("xray"); }); + }); +}); - it("returns StepFunctionEventTraceExtractor when event contains LegacyStepFunctionContext", () => { - const legacyStepFunctionEvent = { - Execution: { - Id: "arn:aws:states:sa-east-1:425362996713:express:logs-to-traces-sequential:85a9933e-9e11-83dc-6a61-b92367b6c3be:3f7ef5c7-c8b8-4c88-90a1-d54aa7e7e2bf", - Input: { - MyInput: "MyValue", - }, - Name: "85a9933e-9e11-83dc-6a61-b92367b6c3be", - RoleArn: "arn:aws:iam::425362996713:role/service-role/StepFunctions-logs-to-traces-sequential-role-ccd69c03", - RedriveCount: 0, - StartTime: "2022-12-08T21:08:17.924Z", - }, - State: { - Name: "step-one", - EnteredTime: "2022-12-08T21:08:19.224Z", - RetryCount: 2, - }, - StateMachine: { - Id: "arn:aws:states:sa-east-1:425362996713:stateMachine:logs-to-traces-sequential", - Name: "my-state-machine", - }, - }; - - const tracerWrapper = new TracerWrapper(); - const traceContextExtractor = new TraceContextExtractor(tracerWrapper, {} as TraceConfig); - - // Mimick TraceContextService.extract initialization - const instance = StepFunctionContextService.instance(legacyStepFunctionEvent); - traceContextExtractor["stepFunctionContextService"] = instance; - - const extractor = traceContextExtractor["getTraceEventExtractor"](legacyStepFunctionEvent); - - expect(extractor).toBeInstanceOf(StepFunctionEventTraceExtractor); - }); +describe("getTraceEventExtractor", () => { + beforeEach(() => { + StepFunctionContextService["_instance"] = undefined as any; + }); + it.each([ + ["null", null], + ["undefined", undefined], + ["a string", "some-value"], + ["a number", 1234], + ["an object which doesn't match any expected event", { custom: "event" }], + ])("returns undefined when event is '%s'", (_, event) => { + const tracerWrapper = new TracerWrapper(); + const traceContextExtractor = new TraceContextExtractor(tracerWrapper, {} as TraceConfig); + + const extractor = traceContextExtractor["getTraceEventExtractor"](event); + + expect(extractor).toBeUndefined(); + }); - it("returns StepFunctionEventTraceExtractor when event contains LambdaRootStepFunctionContext", () => { - const lambdaRootStepFunctionEvent = { - _datadog: { - Execution: { - Id: "arn:aws:states:sa-east-1:425362996713:express:logs-to-traces-sequential:85a9933e-9e11-83dc-6a61-b92367b6c3be:3f7ef5c7-c8b8-4c88-90a1-d54aa7e7e2bf", - Input: { - MyInput: "MyValue", - }, - Name: "85a9933e-9e11-83dc-6a61-b92367b6c3be", - RoleArn: - "arn:aws:iam::425362996713:role/service-role/StepFunctions-logs-to-traces-sequential-role-ccd69c03", - RedriveCount: 0, - StartTime: "2022-12-08T21:08:17.924Z", + // Returns the expected event extractor when payload is from that event + it.each([ + [ + "HTTPEventTraceExtractor", + "headers", + HTTPEventTraceExtractor, + { + headers: {}, + }, + ], + [ + "SNSEventTraceExtractor", + "SNS event", + SNSEventTraceExtractor, + { + Records: [ + { + Sns: {}, }, - State: { - Name: "step-one", - EnteredTime: "2022-12-08T21:08:19.224Z", - RetryCount: 2, + ], + }, + ], + [ + "SNSSQSventTraceExtractor", + "SNS to SQS event", + SNSSQSEventTraceExtractor, + { + Records: [ + { + eventSource: "aws:sqs", + body: '{"Type":"Notification", "TopicArn":"some-topic"}', }, - StateMachine: { - Id: "arn:aws:states:sa-east-1:425362996713:stateMachine:logs-to-traces-sequential", - Name: "my-state-machine", + ], + }, + ], + [ + "EventBridgeSQSTraceExtractor", + "EventBridge to SQS event", + EventBridgeSQSEventTraceExtractor, + { + Records: [ + { + eventSource: "aws:sqs", + body: '{"detail-type":"some-detail-type"}', }, - "x-datadog-trace-id": "10593586103637578129", - "x-datadog-tags": "_dd.p.dm=-0,_dd.p.tid=6734e7c300000000", - "serverless-version": "v1", + ], + }, + ], + [ + "AppSyncEventTraceExtractor", + "AppSync event", + AppSyncEventTraceExtractor, + { + info: { + selectionSetGraphQL: "some-selection", }, - }; - - const tracerWrapper = new TracerWrapper(); - const traceContextExtractor = new TraceContextExtractor(tracerWrapper, {} as TraceConfig); - - // Mimick TraceContextService.extract initialization - const instance = StepFunctionContextService.instance(lambdaRootStepFunctionEvent); - traceContextExtractor["stepFunctionContextService"] = instance; - - const extractor = traceContextExtractor["getTraceEventExtractor"](lambdaRootStepFunctionEvent); - - expect(extractor).toBeInstanceOf(StepFunctionEventTraceExtractor); - }); - - it("returns StepFunctionEventTraceExtractor when event contains NestedStepFunctionContext", () => { - const nestedStepFunctionEvent = { - _datadog: { - Execution: { - Id: "arn:aws:states:sa-east-1:425362996713:express:logs-to-traces-sequential:85a9933e-9e11-83dc-6a61-b92367b6c3be:3f7ef5c7-c8b8-4c88-90a1-d54aa7e7e2bf", - Input: { - MyInput: "MyValue", - }, - Name: "85a9933e-9e11-83dc-6a61-b92367b6c3be", - RoleArn: - "arn:aws:iam::425362996713:role/service-role/StepFunctions-logs-to-traces-sequential-role-ccd69c03", - RedriveCount: 0, - StartTime: "2022-12-08T21:08:17.924Z", - }, - State: { - Name: "step-one", - EnteredTime: "2022-12-08T21:08:19.224Z", - RetryCount: 2, - }, - StateMachine: { - Id: "arn:aws:states:sa-east-1:425362996713:stateMachine:logs-to-traces-sequential", - Name: "my-state-machine", - }, - RootExecutionId: - "arn:aws:states:sa-east-1:425362996713:express:logs-to-traces-sequential:a1b2c3d4-e5f6-7890-1234-56789abcdef0:9f8e7d6c-5b4a-3c2d-1e0f-123456789abc", - "serverless-version": "v1", + request: { + headers: {}, }, - }; - - const tracerWrapper = new TracerWrapper(); - const traceContextExtractor = new TraceContextExtractor(tracerWrapper, {} as TraceConfig); - - // Mimick TraceContextService.extract initialization - const instance = StepFunctionContextService.instance(nestedStepFunctionEvent); - traceContextExtractor["stepFunctionContextService"] = instance; - - const extractor = traceContextExtractor["getTraceEventExtractor"](nestedStepFunctionEvent); - - expect(extractor).toBeInstanceOf(StepFunctionEventTraceExtractor); - }); - - it("returns StepFunctionEventTraceExtractor when event contains legacy lambda StepFunctionContext", () => { - const event = { - Payload: { - Execution: { - Id: "arn:aws:states:sa-east-1:425362996713:express:logs-to-traces-sequential:85a9933e-9e11-83dc-6a61-b92367b6c3be:3f7ef5c7-c8b8-4c88-90a1-d54aa7e7e2bf", - Input: { - MyInput: "MyValue", - }, - Name: "85a9933e-9e11-83dc-6a61-b92367b6c3be", - RoleArn: - "arn:aws:iam::425362996713:role/service-role/StepFunctions-logs-to-traces-sequential-role-ccd69c03", - RedriveCount: 0, - StartTime: "2022-12-08T21:08:17.924Z", - }, - State: { - Name: "step-one", - EnteredTime: "2022-12-08T21:08:19.224Z", - RetryCount: 2, + }, + ], + [ + "SQSEventTraceExtractor", + "SQS event", + SQSEventTraceExtractor, + { + Records: [ + { + eventSource: "aws:sqs", }, - StateMachine: { - Id: "arn:aws:states:sa-east-1:425362996713:stateMachine:logs-to-traces-sequential", - Name: "my-state-machine", + ], + }, + ], + [ + "KinesisEventTraceExtractor", + "Kinesis stream event", + KinesisEventTraceExtractor, + { + Records: [ + { + kinesis: {}, }, - }, - }; - - const tracerWrapper = new TracerWrapper(); - const traceContextExtractor = new TraceContextExtractor(tracerWrapper, {} as TraceConfig); - - // Mimick TraceContextService.extract initialization - const instance = StepFunctionContextService.instance(event); - traceContextExtractor["stepFunctionContextService"] = instance; + ], + }, + ], + [ + "EventBridgeEventTraceExtractor", + "EventBridge event", + EventBridgeEventTraceExtractor, + { + "detail-type": "some-detail-type", + }, + ], + ])("returns %s when event contains %s", (_, __, _class, event) => { + const tracerWrapper = new TracerWrapper(); + const traceContextExtractor = new TraceContextExtractor(tracerWrapper, {} as TraceConfig); - const extractor = traceContextExtractor["getTraceEventExtractor"](event); + const extractor = traceContextExtractor["getTraceEventExtractor"](event); - expect(extractor).toBeInstanceOf(StepFunctionEventTraceExtractor); - }); + expect(extractor).toBeInstanceOf(_class); }); describe("addTraceContextToXray", () => { diff --git a/src/trace/context/extractor.ts b/src/trace/context/extractor.ts index fb690835..35150a2c 100644 --- a/src/trace/context/extractor.ts +++ b/src/trace/context/extractor.ts @@ -43,8 +43,6 @@ export class TraceContextExtractor { } async extract(event: any, context: Context): Promise { - this.stepFunctionContextService = StepFunctionContextService.instance(event); - let spanContext: SpanContextWrapper | null = null; if (this.config.traceExtractor) { const customExtractor = new CustomTraceExtractor(this.config.traceExtractor); @@ -58,6 +56,14 @@ export class TraceContextExtractor { } } + if (spanContext === null) { + this.stepFunctionContextService = StepFunctionContextService.instance(event); + if (this.stepFunctionContextService?.context) { + const extractor = new StepFunctionEventTraceExtractor(); + spanContext = extractor?.extract(event); + } + } + if (spanContext === null) { const contextExtractor = new LambdaContextTraceExtractor(this.tracerWrapper); spanContext = contextExtractor.extract(context); @@ -88,8 +94,6 @@ export class TraceContextExtractor { if (EventValidator.isKinesisStreamEvent(event)) return new KinesisEventTraceExtractor(this.tracerWrapper); if (EventValidator.isEventBridgeEvent(event)) return new EventBridgeEventTraceExtractor(this.tracerWrapper); - if (this.stepFunctionContextService?.context) return new StepFunctionEventTraceExtractor(); - return; } diff --git a/src/trace/context/extractors/event-bridge-sqs.spec.ts b/src/trace/context/extractors/event-bridge-sqs.spec.ts index 70aecac5..24ca16fa 100644 --- a/src/trace/context/extractors/event-bridge-sqs.spec.ts +++ b/src/trace/context/extractors/event-bridge-sqs.spec.ts @@ -1,5 +1,6 @@ import { TracerWrapper } from "../../tracer-wrapper"; import { EventBridgeSQSEventTraceExtractor } from "./event-bridge-sqs"; +import { StepFunctionContextService } from "../../step-function-service"; let mockSpanContext: any = null; @@ -42,7 +43,7 @@ describe("EventBridgeSQSEventTraceExtractor", () => { messageId: "e995e54f-1724-41fa-82c0-8b81821f854e", receiptHandle: "AQEB4mIfRcyqtzn1X5Ss+ConhTejVGc+qnAcmu3/Z9ZvbNkaPcpuDLX/bzvPD/ZkAXJUXZcemGSJmd7L3snZHKMP2Ck8runZiyl4mubiLb444pZvdiNPuGRJ6a3FvgS/GQPzho/9nNMyOi66m8Viwh70v4EUCPGO4JmD3TTDAUrrcAnqU4WSObjfC/NAp9bI6wH2CEyAYEfex6Nxplbl/jBf9ZUG0I3m3vQd0Q4l4gd4jIR4oxQUglU2Tldl4Kx5fMUAhTRLAENri6HsY81avBkKd9FAuxONlsITB5uj02kOkvLlRGEcalqsKyPJ7AFaDLrOLaL3U+yReroPEJ5R5nwhLOEbeN5HROlZRXeaAwZOIN8BjqdeooYTIOrtvMEVb7a6OPLMdH1XB+ddevtKAH8K9Tm2ZjpaA7dtBGh1zFVHzBk=", - body: '{"version":"0","id":"af718b2a-b987-e8c0-7a2b-a188fad2661a","detail-type":"my.Detail","source":"my.Source","account":"425362996713","time":"2023-08-03T22:49:03Z","region":"us-east-1","resources":[],"detail":{"text":"Hello, world!","_datadog":{"x-datadog-trace-id":"7379586022458917877","x-datadog-parent-id":"2644033662113726488","x-datadog-sampling-priority":"1","x-datadog-tags":"_dd.p.dm=-0"}}}', + body: '{"version":"0","id":"af718b2a-b987-e8c0-7a2b-a188fad2661a","detail-type":"my.Detail","source":"my.Source","account":"123456123456","time":"2023-08-03T22:49:03Z","region":"us-east-1","resources":[],"detail":{"text":"Hello, world!","_datadog":{"x-datadog-trace-id":"7379586022458917877","x-datadog-parent-id":"2644033662113726488","x-datadog-sampling-priority":"1","x-datadog-tags":"_dd.p.dm=-0"}}}', attributes: { ApproximateReceiveCount: "1", AWSTraceHeader: "Root=1-64cc2edd-112fbf1701d1355973a11d57;Parent=7d5a9776024b2d42;Sampled=0", @@ -124,5 +125,45 @@ describe("EventBridgeSQSEventTraceExtractor", () => { const traceContext = extractor.extract(payload); expect(traceContext).toBeNull(); }); + + it("extracts trace context from Step Function EventBridge-SQS event", () => { + // Reset StepFunctionContextService instance + StepFunctionContextService["_instance"] = undefined as any; + + const tracerWrapper = new TracerWrapper(); + + const payload = { + Records: [ + { + messageId: "0fc0e02f-ab25-4fde-b5ff-22aba9a9f20e", + receiptHandle: + "AQEBROlXUgqqRdo/j0GcfxBNldIKy8FO6Ee0ZnP5YeAp4pwQ+v9XovX47vSzNHAZooCa0r8D7Uoow0y4bhGiH/Tt5HXAseDUlvWHB6bULonzAdvRmLd1W1OCY9D1uH3TpHZYn6JdoQd6Koxndx5wDwhv5UKxcbOwDjlc6X/30OKkTm4gcr7Otzu4GxCt6N/FmDxcRIDogZk80UE1kN6Q5EHI9LB6V+oleqqCbQwg5FYmbVc+DjwPBY4/5NI6x1/XZLFZA0TdezOdOuNq4+4DGK8e35Bafg4hXp+06zg8E5XPdMQV5V4iDzJhenPEdXusGL36byBHyC4aDunTSpeIND0/0ctqyH1vEJHo09LJ1jztPj05hBQeDU5QXCIKRpuo5+nEHE+Jm1ZLrUWUoIg1uAIamDzQ0CWNtPjGkNn3POiTGpD2e0aqrE5VpXZ8N30HKKFM", + body: '{"version":"0","id":"1aec4f0c-35e7-934c-9928-a5db3e526bca","detail-type":"ProcessEvent","source":"demo.stepfunction","account":"123456123456","time":"2025-07-14T19:33:03Z","region":"sa-east-1","resources":["arn:aws:states:sa-east-1:123456123456:stateMachine:rstrat-sfn-evb-sqs-demo-dev-state-machine","arn:aws:states:sa-east-1:123456123456:execution:rstrat-sfn-evb-sqs-demo-dev-state-machine:aa2f4ded-196f-4d6b-b41d-aa64f3193f2d"],"detail":{"message":"Event from Step Functions","timestamp":"2025-07-14T19:33:03.483Z","executionName":"aa2f4ded-196f-4d6b-b41d-aa64f3193f2d","stateMachineName":"rstrat-sfn-evb-sqs-demo-dev-state-machine","input":{"testData":"Hello with SQS integration"},"_datadog":{"Execution":{"Id":"arn:aws:states:sa-east-1:123456123456:execution:rstrat-sfn-evb-sqs-demo-dev-state-machine:aa2f4ded-196f-4d6b-b41d-aa64f3193f2d","StartTime":"2025-07-14T19:33:03.446Z","Name":"aa2f4ded-196f-4d6b-b41d-aa64f3193f2d","RoleArn":"arn:aws:iam::123456123456:role/rstrat-sfn-evb-sqs-demo-d-StepFunctionsExecutionRol-mAumgN9x07FQ","RedriveCount":0},"StateMachine":{"Id":"arn:aws:states:sa-east-1:123456123456:stateMachine:rstrat-sfn-evb-sqs-demo-dev-state-machine","Name":"rstrat-sfn-evb-sqs-demo-dev-state-machine"},"State":{"Name":"PublishToEventBridge","EnteredTime":"2025-07-14T19:33:03.483Z","RetryCount":0},"RootExecutionId":"arn:aws:states:sa-east-1:123456123456:execution:rstrat-sfn-evb-sqs-demo-dev-state-machine:aa2f4ded-196f-4d6b-b41d-aa64f3193f2d","serverless-version":"v1"}}}', + attributes: { + ApproximateReceiveCount: "1", + SentTimestamp: "1752521583745", + SenderId: "AIDAIELDKKY42PBA6I2NG", + ApproximateFirstReceiveTimestamp: "1752521583758", + }, + messageAttributes: {}, + md5OfBody: "957cded00b7b10a6e1b79864f24a7b5f", + eventSource: "aws:sqs", + eventSourceARN: "arn:aws:sqs:sa-east-1:123456123456:rstrat-sfn-evb-sqs-demo-dev-process-event-queue", + awsRegion: "sa-east-1", + }, + ], + }; + + const extractor = new EventBridgeSQSEventTraceExtractor(tracerWrapper); + + const traceContext = extractor.extract(payload); + expect(traceContext).not.toBeNull(); + + // The trace IDs are deterministically generated from the Step Function execution context + expect(traceContext?.toTraceId()).toBe("7858567057595668526"); + expect(traceContext?.toSpanId()).toBe("3674709292670593712"); + expect(traceContext?.sampleMode()).toBe("1"); + expect(traceContext?.source).toBe("event"); + }); }); }); diff --git a/src/trace/context/extractors/event-bridge-sqs.ts b/src/trace/context/extractors/event-bridge-sqs.ts index 1fd93201..45428478 100644 --- a/src/trace/context/extractors/event-bridge-sqs.ts +++ b/src/trace/context/extractors/event-bridge-sqs.ts @@ -1,30 +1,24 @@ -import { EventBridgeEvent, SQSEvent } from "aws-lambda"; -import { EventTraceExtractor } from "../extractor"; -import { logDebug } from "../../../utils"; +import { SQSEvent } from "aws-lambda"; import { TracerWrapper } from "../../tracer-wrapper"; +import { EventTraceExtractor } from "../extractor"; import { SpanContextWrapper } from "../../span-context-wrapper"; +import { extractTraceContext, handleExtractionError } from "../extractor-utils"; export class EventBridgeSQSEventTraceExtractor implements EventTraceExtractor { constructor(private tracerWrapper: TracerWrapper) {} extract(event: SQSEvent): SpanContextWrapper | null { - const body = event?.Records?.[0]?.body; - if (body === undefined) return null; - try { - const parsedBody = JSON.parse(body) as EventBridgeEvent; - const headers = parsedBody?.detail?._datadog; - if (headers === undefined) return null; - - const traceContext = this.tracerWrapper.extract(headers); - if (traceContext === null) return null; - - logDebug("Extracted trace context from EventBridge-SQS event", { traceContext, event }); - return traceContext; - } catch (error) { - if (error instanceof Error) { - logDebug("Unable to extract trace context from EventBridge-SQS event", error); + const body = event?.Records?.[0]?.body; + if (body) { + const parsedBody = JSON.parse(body); + const headers = parsedBody?.detail?._datadog; + if (headers) { + return extractTraceContext(headers, this.tracerWrapper); + } } + } catch (error) { + handleExtractionError(error, "EventBridge-SQS"); } return null; diff --git a/src/trace/context/extractors/event-bridge.spec.ts b/src/trace/context/extractors/event-bridge.spec.ts index 27e10306..346b89c4 100644 --- a/src/trace/context/extractors/event-bridge.spec.ts +++ b/src/trace/context/extractors/event-bridge.spec.ts @@ -1,5 +1,6 @@ import { TracerWrapper } from "../../tracer-wrapper"; import { EventBridgeEventTraceExtractor } from "./event-bridge"; +import { StepFunctionContextService } from "../../step-function-service"; let mockSpanContext: any = null; @@ -106,5 +107,66 @@ describe("EventBridgeEventTraceExtractor", () => { const traceContext = extractor.extract(payload); expect(traceContext).toBeNull(); }); + + it("extracts trace context from Step Function EventBridge event", () => { + // Reset StepFunctionContextService instance + StepFunctionContextService["_instance"] = undefined as any; + + const tracerWrapper = new TracerWrapper(); + + const payload = { + version: "0", + id: "af718b2a-b987-e8c0-7a2b-a188fad2661a", + "detail-type": "ProcessEvent", + source: "demo.stepfunction", + account: "123456123456", + time: "2025-07-11T14:59:35Z", + region: "us-east-2", + resources: [ + "arn:aws:states:us-east-2:123456123456:stateMachine:rstrat-sfn-evb-demo-dev-state-machine", + "arn:aws:states:us-east-2:123456123456:execution:rstrat-sfn-evb-demo-dev-state-machine:6c190e7b-eb77-46db-af26-9066d353b105", + ], + detail: { + message: "Event from Step Functions", + timestamp: "2025-07-11T14:59:35.830Z", + executionName: "6c190e7b-eb77-46db-af26-9066d353b105", + stateMachineName: "rstrat-sfn-evb-demo-dev-state-machine", + input: { + testData: "Hello with Datadog tracing", + }, + _datadog: { + Execution: { + Id: "arn:aws:states:us-east-2:123456123456:execution:rstrat-sfn-evb-demo-dev-state-machine:6c190e7b-eb77-46db-af26-9066d353b105", + StartTime: "2025-07-11T14:59:35.806Z", + Name: "6c190e7b-eb77-46db-af26-9066d353b105", + RoleArn: "arn:aws:iam::123456123456:role/rstrat-sfn-evb-demo-dev-StepFunctionsExecutionRole-8maJHu01fhZZ", + RedriveCount: 0, + }, + StateMachine: { + Id: "arn:aws:states:us-east-2:123456123456:stateMachine:rstrat-sfn-evb-demo-dev-state-machine", + Name: "rstrat-sfn-evb-demo-dev-state-machine", + }, + State: { + Name: "PublishToEventBridge", + EnteredTime: "2025-07-11T14:59:35.830Z", + RetryCount: 0, + }, + RootExecutionId: + "arn:aws:states:us-east-2:123456123456:execution:rstrat-sfn-evb-demo-dev-state-machine:6c190e7b-eb77-46db-af26-9066d353b105", + "serverless-version": "v1", + }, + }, + }; + + const extractor = new EventBridgeEventTraceExtractor(tracerWrapper); + + const traceContext = extractor.extract(payload); + expect(traceContext).not.toBeNull(); + + expect(traceContext?.toTraceId()).toBe("1503104665848096006"); + expect(traceContext?.toSpanId()).toBe("159267866761498620"); + expect(traceContext?.sampleMode()).toBe("1"); + expect(traceContext?.source).toBe("event"); + }); }); }); diff --git a/src/trace/context/extractors/event-bridge.ts b/src/trace/context/extractors/event-bridge.ts index c589c929..41086f6a 100644 --- a/src/trace/context/extractors/event-bridge.ts +++ b/src/trace/context/extractors/event-bridge.ts @@ -1,26 +1,20 @@ -import { EventTraceExtractor } from "../extractor"; import { EventBridgeEvent } from "aws-lambda"; -import { logDebug } from "../../../utils"; import { TracerWrapper } from "../../tracer-wrapper"; +import { EventTraceExtractor } from "../extractor"; import { SpanContextWrapper } from "../../span-context-wrapper"; +import { extractTraceContext, handleExtractionError } from "../extractor-utils"; export class EventBridgeEventTraceExtractor implements EventTraceExtractor { constructor(private tracerWrapper: TracerWrapper) {} - extract(event: EventBridgeEvent): SpanContextWrapper | null { - const headers = event?.detail?._datadog; - if (headers === undefined) return null; - + extract(event: EventBridgeEvent): SpanContextWrapper | null { try { - const traceContext = this.tracerWrapper.extract(headers); - if (traceContext === null) return null; - - logDebug(`Extracted trace context from Eventbridge event`, { traceContext, event }); - return traceContext; - } catch (error) { - if (error instanceof Error) { - logDebug("Unable to extract trace context from EventBridge event", error); + const headers = event?.detail?._datadog; + if (headers) { + return extractTraceContext(headers, this.tracerWrapper); } + } catch (error) { + handleExtractionError(error, "EventBridge"); } return null; diff --git a/src/trace/context/extractors/sns-sqs.spec.ts b/src/trace/context/extractors/sns-sqs.spec.ts index 3c927a06..f3f7c1b1 100644 --- a/src/trace/context/extractors/sns-sqs.spec.ts +++ b/src/trace/context/extractors/sns-sqs.spec.ts @@ -1,5 +1,6 @@ import { TracerWrapper } from "../../tracer-wrapper"; import { SNSSQSEventTraceExtractor } from "./sns-sqs"; +import { StepFunctionContextService } from "../../step-function-service"; let mockSpanContext: any = null; let mockDataStreamsCheckpointer: any = { @@ -264,5 +265,54 @@ describe("SNSSQSEventTraceExtractor", () => { expect(traceContext?.sampleMode()).toBe("1"); expect(traceContext?.source).toBe("event"); }); + + it("extracts trace context from Step Function SNS-SQS event", () => { + // Reset StepFunctionContextService instance + StepFunctionContextService["_instance"] = undefined as any; + + const tracerWrapper = new TracerWrapper(); + + const payload = { + Records: [ + { + messageId: "43a5f138-f166-40f1-b7e4-a7e0af9d633d", + receiptHandle: + "AQEBrhzl4RiITHp/ui07Y0DlDIdrmYHveKjqDIsx2gG7Z3fvrDohnfnpy/r4esh/ZsilJUR/C3uohYe6HUqvixymhx+io9S/MYNoA1zjmSVd1V4ZKe6saMs6L7aSW5TgrLpuxOtNGWvmNijdlQlOoYW1xRlkzkBywFkELfazExJHrThbxpxXcHbcAoh1Vz77EvlcAQNbc11vTccoUcMcdczvoLd/wgyrsIf0z8qdUQHaspWYoWOlZOsoflDMddYwqWO3LNRphAGMp5ISTDVbVqo1/U+wOqBj+b3dOYP9k0vS9Mj+36t+EJ8+KETFXRPNk4mZ+7hvG+UCYBN582gT502MnQitxylHKWOlH77nIokfk43FjhjsybLE48KdWdO49O2WKslXwCpPLQWnbKWlUl05/12tIk41MolVyfiWywW9R/S7hgcSr51tEBcjZTW8GR8r", + body: '{"testData":"Hello from SQS integration test","timestamp":"2025-07-15T18:16:27Z"}', + attributes: { + ApproximateReceiveCount: "1", + SentTimestamp: "1752603390964", + SenderId: "AIDAIOA2GYWSHW4E2VXIO", + ApproximateFirstReceiveTimestamp: "1752603390980", + }, + messageAttributes: { + _datadog: { + stringValue: + '{"Execution":{"Id":"arn:aws:states:sa-east-1:123456123456:execution:rstrat-sfn-sns-sqs-demo-dev-state-machine:c363b975-c342-4e40-815a-8dd2496f5e81","StartTime":"2025-07-15T18:16:30.746Z","Name":"c363b975-c342-4e40-815a-8dd2496f5e81","RoleArn":"arn:aws:iam::123456123456:role/rstrat-sfn-sns-sqs-demo-d-StepFunctionsExecutionRol-T2O3igeuSihu","RedriveCount":0},"StateMachine":{"Id":"arn:aws:states:sa-east-1:123456123456:stateMachine:rstrat-sfn-sns-sqs-demo-dev-state-machine","Name":"rstrat-sfn-sns-sqs-demo-dev-state-machine"},"State":{"Name":"PublishToSNS","EnteredTime":"2025-07-15T18:16:30.776Z","RetryCount":0},"RootExecutionId":"arn:aws:states:sa-east-1:123456123456:execution:rstrat-sfn-sns-sqs-demo-dev-state-machine:c363b975-c342-4e40-815a-8dd2496f5e81","serverless-version":"v1"}', + stringListValues: [], + binaryListValues: [], + dataType: "String", + }, + }, + md5OfBody: "1e832c0d0aa5188dc5e3f2e85c9cb5e7", + md5OfMessageAttributes: "64e36d01aec95ca5a2160c13299e9c3b", + eventSource: "aws:sqs", + eventSourceARN: "arn:aws:sqs:sa-east-1:123456123456:rstrat-sfn-sns-sqs-demo-dev-process-event-queue", + awsRegion: "sa-east-1", + }, + ], + }; + + const extractor = new SNSSQSEventTraceExtractor(tracerWrapper); + + const traceContext = extractor.extract(payload); + expect(traceContext).not.toBeNull(); + + // The StepFunctionContextService generates deterministic trace IDs + expect(traceContext?.toTraceId()).toBe("1657966791618574655"); + expect(traceContext?.toSpanId()).toBe("5100002956473485303"); + expect(traceContext?.sampleMode()).toBe("1"); + expect(traceContext?.source).toBe("event"); + }); }); }); diff --git a/src/trace/context/extractors/sns.spec.ts b/src/trace/context/extractors/sns.spec.ts index 845a4f7c..7bbd6ab6 100644 --- a/src/trace/context/extractors/sns.spec.ts +++ b/src/trace/context/extractors/sns.spec.ts @@ -1,6 +1,7 @@ import { SNSEvent } from "aws-lambda"; import { TracerWrapper } from "../../tracer-wrapper"; import { SNSEventTraceExtractor } from "./sns"; +import { StepFunctionContextService } from "../../step-function-service"; let mockSpanContext: any = null; let mockDataStreamsCheckpointer: any = { @@ -308,5 +309,57 @@ describe("SNSEventTraceExtractor", () => { expect(traceContext?.sampleMode()).toBe("1"); expect(traceContext?.source).toBe("event"); }); + + it("extracts trace context from Step Function SNS event", () => { + // Reset StepFunctionContextService instance + StepFunctionContextService["_instance"] = undefined as any; + + const tracerWrapper = new TracerWrapper(); + + const payload: SNSEvent = { + Records: [ + { + EventSource: "aws:sns", + EventVersion: "1.0", + EventSubscriptionArn: + "arn:aws:sns:sa-east-1:123456123456:rstrat-sfn-sns-demo-dev-process-event-topic:f18241f8-a4f7-4586-80db-97bd1939a557", + Sns: { + Type: "Notification", + MessageId: "46d2665c-7ee2-50ba-a4cd-06acf35f5d5f", + TopicArn: "arn:aws:sns:sa-east-1:123456123456:rstrat-sfn-sns-demo-dev-process-event-topic", + Message: + '{"source":"demo.stepfunction","detailType":"ProcessEvent","message":"Test event from Step Functions","customData":{"userId":"12345","action":"test"}}', + Timestamp: "2025-07-15T17:10:21.503Z", + SignatureVersion: "1", + Signature: + "fHeJta0GWCs/lHhI6wesXiT+66i1SZ+XH58pyd8mKHKD8bepXsnWvfQdDsOkO2AVv2CqPBF58sAWQae6yob2aMawe/vo8eeahJCaguK8a/3HLj7kP+nXGjgSGvzQm4CdYEyAUco453/mfE/BSf0SkdctxW0rjMs27T2l964Lt2Y/vJeiXVibs/AqEIu3ImekbM8+EIfNMOLBdRBVE47650vawazMkcpPtg5o/8LCA/jNUNj9VCTJrvzep8/vVJEcuHbZ3pcmajA9UJmP3000G0+to0cXwZ5YaakOxQTv81I+cfC99yQJoogLklbgiu+4bqEeNWbwW9KdQz1U+79NgA==", + SigningCertUrl: + "https://sns.sa-east-1.amazonaws.com/SimpleNotificationService-9c6465fa7f48f5cacd23014631ec1136.pem", + Subject: "Event from Step Functions", + UnsubscribeUrl: + "https://sns.sa-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:sa-east-1:123456123456:rstrat-sfn-sns-demo-dev-process-event-topic:f18241f8-a4f7-4586-80db-97bd1939a557", + MessageAttributes: { + _datadog: { + Type: "String", + Value: + '{"Execution":{"Id":"arn:aws:states:sa-east-1:123456123456:execution:rstrat-sfn-sns-demo-dev-state-machine:79049e80-5cc6-49da-9dc0-f19ba2921772","StartTime":"2025-07-15T17:10:21.328Z","Name":"79049e80-5cc6-49da-9dc0-f19ba2921772","RoleArn":"arn:aws:iam::123456123456:role/rstrat-sfn-sns-demo-dev-StepFunctionsExecutionRole-LrsdDm6wMmBh","RedriveCount":0},"StateMachine":{"Id":"arn:aws:states:sa-east-1:123456123456:stateMachine:rstrat-sfn-sns-demo-dev-state-machine","Name":"rstrat-sfn-sns-demo-dev-state-machine"},"State":{"Name":"PublishToSNS","EnteredTime":"2025-07-15T17:10:21.354Z","RetryCount":0},"RootExecutionId":"arn:aws:states:sa-east-1:123456123456:execution:rstrat-sfn-sns-demo-dev-state-machine:79049e80-5cc6-49da-9dc0-f19ba2921772","serverless-version":"v1"}', + }, + }, + }, + }, + ], + }; + + const extractor = new SNSEventTraceExtractor(tracerWrapper); + + const traceContext = extractor.extract(payload); + expect(traceContext).not.toBeNull(); + + // The StepFunctionContextService generates deterministic trace IDs + expect(traceContext?.toTraceId()).toBe("3995810302240690842"); + expect(traceContext?.toSpanId()).toBe("8347071195300897803"); + expect(traceContext?.sampleMode()).toBe("1"); + expect(traceContext?.source).toBe("event"); + }); }); }); diff --git a/src/trace/context/extractors/sqs.spec.ts b/src/trace/context/extractors/sqs.spec.ts index 1bbd088f..2ddf8c13 100644 --- a/src/trace/context/extractors/sqs.spec.ts +++ b/src/trace/context/extractors/sqs.spec.ts @@ -1,6 +1,7 @@ import { SQSEvent } from "aws-lambda"; import { TracerWrapper } from "../../tracer-wrapper"; import { SQSEventTraceExtractor } from "./sqs"; +import { StepFunctionContextService } from "../../step-function-service"; let mockSpanContext: any = null; let mockDataStreamsCheckpointer: any = { @@ -270,5 +271,54 @@ describe("SQSEventTraceExtractor", () => { expect(traceContext?.sampleMode()).toBe("1"); expect(traceContext?.source).toBe("event"); }); + + it("extracts trace context from Step Function SQS event", () => { + // Reset StepFunctionContextService instance + StepFunctionContextService["_instance"] = undefined as any; + + const tracerWrapper = new TracerWrapper(); + + const payload: SQSEvent = { + Records: [ + { + messageId: "4ead33f3-51c8-4094-87bd-5325dc143cbd", + receiptHandle: + "AQEBrGtLZCUS1POUEZtdZRoB0zXgT14OQC48A4Xk4Qbnv/v4d0ib5rFI1wEah823t2hE9haPm6nNN1aGsJmYkqa9Y8qaBQscp9f7HKJyybT5hpdKEn07fY0VRv/Of63u1RN1YdFdY5uhI8XGWRc4w7t62lQwMMFY5Ahy7XLVwnav81KRjGFdgxzITrtx3YKxmISNvXzPiiHNKb7jT+ClfXi91bEYHi3Od3ji5xGajAofgYrj2VBDULyohsfMkwlvAanD2wfj2x++wL5LSpFEtMFnvThzt7Dh5FEZChVMzWV+fRFpljivHX58ZeuGv4yIIjLVuuDGn5uAY5ES4CsdINrBAru6K5gDSPUajRzE3TktNgAq5Niqfky1x0srLRAJjTDdmZK8/CXU0sRT/MCT99vkCHa0bC17S/9au5bCbrB4k/T9J8W39AA6kIYhebkq3IQr", + body: '{"testData":"Hello from Step Functions to SQS"}', + attributes: { + ApproximateReceiveCount: "1", + SentTimestamp: "1752594520503", + SenderId: "AROAWGCM4HXU73A4V34AJ:EcGTcmgJbwwOwXPbloVwgSaDOmwhYBLH", + ApproximateFirstReceiveTimestamp: "1752594520516", + }, + messageAttributes: { + _datadog: { + stringValue: + '{"Execution":{"Id":"arn:aws:states:sa-east-1:123456123456:execution:rstrat-sfn-sqs-demo-dev-state-machine:a4912895-93a3-4803-a712-69fecb55c025","StartTime":"2025-07-15T15:48:40.302Z","Name":"a4912895-93a3-4803-a712-69fecb55c025","RoleArn":"arn:aws:iam::123456123456:role/rstrat-sfn-sqs-demo-dev-StepFunctionsExecutionRole-s6ozc2dVrvLH","RedriveCount":0},"StateMachine":{"Id":"arn:aws:states:sa-east-1:123456123456:stateMachine:rstrat-sfn-sqs-demo-dev-state-machine","Name":"rstrat-sfn-sqs-demo-dev-state-machine"},"State":{"Name":"SendToSQS","EnteredTime":"2025-07-15T15:48:40.333Z","RetryCount":0},"RootExecutionId":"arn:aws:states:sa-east-1:123456123456:execution:rstrat-sfn-sqs-demo-dev-state-machine:a4912895-93a3-4803-a712-69fecb55c025","serverless-version":"v1"}', + stringListValues: [], + binaryListValues: [], + dataType: "String", + }, + }, + md5OfMessageAttributes: "5469b8f90bb6ab27e95816c1fa178680", + md5OfBody: "f0c0ddb2ed09a09e8791013f142e8d7e", + eventSource: "aws:sqs", + eventSourceARN: "arn:aws:sqs:sa-east-1:123456123456:rstrat-sfn-sqs-demo-dev-process-event-queue", + awsRegion: "sa-east-1", + }, + ], + }; + + const extractor = new SQSEventTraceExtractor(tracerWrapper); + + const traceContext = extractor.extract(payload); + expect(traceContext).not.toBeNull(); + + // The StepFunctionContextService generates deterministic trace IDs + expect(traceContext?.toTraceId()).toBe("7148114900282288397"); + expect(traceContext?.toSpanId()).toBe("6711327198021343353"); + expect(traceContext?.sampleMode()).toBe("1"); + expect(traceContext?.source).toBe("event"); + }); }); }); diff --git a/src/trace/context/extractors/step-function.spec.ts b/src/trace/context/extractors/step-function.spec.ts index 1465ab61..6007daeb 100644 --- a/src/trace/context/extractors/step-function.spec.ts +++ b/src/trace/context/extractors/step-function.spec.ts @@ -5,6 +5,7 @@ describe("StepFunctionEventTraceExtractor", () => { beforeEach(() => { StepFunctionContextService["_instance"] = undefined as any; }); + describe("extract", () => { const payload = { Execution: { @@ -112,5 +113,77 @@ describe("StepFunctionEventTraceExtractor", () => { const traceContext = extractor.extract({}); expect(traceContext).toBeNull(); }); + + it("extracts trace context end-to-end with deterministic IDs for v1 nested context", () => { + const nestedPayload = { + _datadog: { + Execution: { + Id: "arn:aws:states:us-east-1:123456789012:execution:parent-machine:parent-execution-id", + Name: "parent-execution-id", + StartTime: "2024-01-01T00:00:00.000Z", + }, + State: { + Name: "InvokeNestedStateMachine", + EnteredTime: "2024-01-01T00:00:05.000Z", + RetryCount: 0, + }, + "serverless-version": "v1", + RootExecutionId: "arn:aws:states:us-east-1:123456789012:execution:root-machine:root-execution-id", + }, + }; + + const extractor = new StepFunctionEventTraceExtractor(); + const traceContext = extractor.extract(nestedPayload); + + expect(traceContext).not.toBeNull(); + expect(traceContext?.source).toBe("event"); + + // Verify trace ID is based on root execution ID (deterministic) + const traceId = traceContext?.toTraceId(); + expect(traceId).toBeDefined(); + expect(traceId).not.toBe("0"); + + // Extract again to verify deterministic behavior + const traceContext2 = extractor.extract(nestedPayload); + expect(traceContext2?.toTraceId()).toBe(traceId); + }); + + it("extracts trace context end-to-end with propagated IDs for v1 lambda root context", () => { + const lambdaRootPayload = { + _datadog: { + Execution: { + Id: "arn:aws:states:us-east-1:123456789012:execution:machine:execution-id", + Name: "execution-id", + StartTime: "2024-01-01T00:00:00.000Z", + }, + State: { + Name: "ProcessLambda", + EnteredTime: "2024-01-01T00:00:02.000Z", + RetryCount: 0, + }, + "serverless-version": "v1", + "x-datadog-trace-id": "1234567890123456789", + "x-datadog-tags": "_dd.p.tid=fedcba9876543210,_dd.p.dm=-0", + }, + }; + + const extractor = new StepFunctionEventTraceExtractor(); + const traceContext = extractor.extract(lambdaRootPayload); + + expect(traceContext).not.toBeNull(); + expect(traceContext?.source).toBe("event"); + + // Verify trace ID comes from propagated headers, not deterministic hash + expect(traceContext?.toTraceId()).toBe("1234567890123456789"); + + // Verify deterministic span ID based on execution details + const spanId = traceContext?.toSpanId(); + expect(spanId).toBeDefined(); + expect(spanId).not.toBe("0"); + + // Extract again to verify deterministic span ID + const traceContext2 = extractor.extract(lambdaRootPayload); + expect(traceContext2?.toSpanId()).toBe(spanId); + }); }); }); diff --git a/src/trace/context/extractors/step-function.ts b/src/trace/context/extractors/step-function.ts index 6197e5c1..3fb0b0f9 100644 --- a/src/trace/context/extractors/step-function.ts +++ b/src/trace/context/extractors/step-function.ts @@ -5,11 +5,16 @@ import { EventTraceExtractor } from "../extractor"; export class StepFunctionEventTraceExtractor implements EventTraceExtractor { extract(event: any): SpanContextWrapper | null { // Probably StepFunctionContextService hasn't been called - const instance = StepFunctionContextService.instance(event); - const context = instance.context; + const stepFunctionInstance = StepFunctionContextService.instance(event); + const stepFunctionContext = stepFunctionInstance.context; - if (context === undefined) return null; + if (stepFunctionContext !== undefined) { + const spanContext = stepFunctionInstance.spanContext; + if (spanContext !== null) { + return spanContext; + } + } - return instance.spanContext; + return null; } } diff --git a/src/trace/listener.spec.ts b/src/trace/listener.spec.ts index 8745a55b..5b27fb6c 100644 --- a/src/trace/listener.spec.ts +++ b/src/trace/listener.spec.ts @@ -33,6 +33,15 @@ jest.mock("./tracer-wrapper", () => { return mockExtract(event); } + startSpan(name: any, options: any): any { + return { + toSpanId: () => "mockSpanId", + toTraceId: () => "mockTraceId", + finish: jest.fn(), + setTag: jest.fn(), + }; + } + injectSpan(span: any): any { return { [DATADOG_PARENT_ID_HEADER]: span.toSpanId(), @@ -67,6 +76,7 @@ jest.mock("./trace-context-service", () => { }); describe("TraceListener", () => { + let oldEnv: any; const defaultConfig = { autoPatchHTTP: true, captureLambdaPayload: false, @@ -99,6 +109,13 @@ describe("TraceListener", () => { mockSpanContext = undefined; mockSpanContextWrapper = undefined; mockTraceSource = undefined; + oldEnv = process.env; + process.env = { ...oldEnv }; + delete process.env.DD_SERVICE; + }); + + afterEach(() => { + process.env = oldEnv; }); it("wraps dd-trace span around invocation", async () => { @@ -115,7 +132,7 @@ describe("TraceListener", () => { resource: "my-Lambda", service: "my-Lambda", tags: { - cold_start: true, + cold_start: "true", function_arn: "arn:aws:lambda:us-east-1:123456789101:function:my-lambda", function_version: "$LATEST", request_id: "1234", @@ -155,7 +172,7 @@ describe("TraceListener", () => { resource: "my-Lambda", service: "my-Lambda", tags: { - cold_start: true, + cold_start: "true", function_arn: "arn:aws:lambda:us-east-1:123456789101:function:my-lambda", function_version: "$LATEST", request_id: "1234", @@ -188,7 +205,7 @@ describe("TraceListener", () => { resource: "my-Lambda", service: "my-Lambda", tags: { - cold_start: true, + cold_start: "true", function_arn: "arn:aws:lambda:us-east-1:123456789101:function:my-lambda", function_version: "$LATEST", request_id: "1234", @@ -229,7 +246,7 @@ describe("TraceListener", () => { resource: "my-Lambda", service: "my-Lambda", tags: { - cold_start: true, + cold_start: "true", function_arn: "arn:aws:lambda:us-east-1:123456789101:function:my-lambda", function_version: "$LATEST", request_id: "1234", @@ -260,7 +277,7 @@ describe("TraceListener", () => { resource: "my-Lambda", service: "my-Lambda", tags: { - cold_start: true, + cold_start: "true", function_arn: "arn:aws:lambda:us-east-1:123456789101:function:my-lambda", function_version: "alias", request_id: "1234", @@ -289,7 +306,7 @@ describe("TraceListener", () => { resource: "my-Lambda", service: "my-Lambda", tags: { - cold_start: true, + cold_start: "true", function_arn: "arn:aws:lambda:us-east-1:123456789101:function:my-lambda", function_version: "1", request_id: "1234", @@ -304,6 +321,88 @@ describe("TraceListener", () => { ); }); + it("wraps dd-trace span around invocation with Step Function context", async () => { + const listener = new TraceListener(defaultConfig); + mockTraceSource = TraceSource.Event; + + // Mock Step Function context with deterministic trace IDs + mockSpanContext = { + toTraceId: () => "512d06a10e5e34cb", // Hex converted to decimal would be different + toSpanId: () => "7069a031ef9ad2cc", + _sampling: { + priority: "1", + }, + }; + mockSpanContextWrapper = { + spanContext: mockSpanContext, + }; + + const stepFunctionSQSEvent = { + Records: [ + { + messageId: "4ead33f3-51c8-4094-87bd-5325dc143cbd", + receiptHandle: + "AQEBrGtLZCUS1POUEZtdZRoB0zXgT14OQC48A4Xk4Qbnv/v4d0ib5rFI1wEah823t2hE9haPm6nNN1aGsJmYkqa9Y8qaBQscp9f7HKJyybT5hpdKEn07fY0VRv/Of63u1RN1YdFdY5uhI8XGWRc4w7t62lQwMMFY5Ahy7XLVwnav81KRjGFdgxzITrtx3YKxmISNvXzPiiHNKb7jT+ClfXi91bEYHi3Od3ji5xGajAofgYrj2VBDULyohsfMkwlvAanD2wfj2x++wL5LSpFEtMFnvThzt7Dh5FEZChVMzWV+fRFpljivHX58ZeuGv4yIIjLVuuDGn5uAY5ES4CsdINrBAru6K5gDSPUajRzE3TktNgAq5Niqfky1x0srLRAJjTDdmZK8/CXU0sRT/MCT99vkCHa0bC17S/9au5bCbrB4k/T9J8W39AA6kIYhebkq3IQr", + body: '{"testData":"Hello from Step Functions to SQS"}', + attributes: { + ApproximateReceiveCount: "1", + SentTimestamp: "1752594520503", + SenderId: "AROAWGCM4HXU73A4V34AJ:EcGTcmgJbwwOwXPbloVwgSaDOmwhYBLH", + ApproximateFirstReceiveTimestamp: "1752594520516", + }, + messageAttributes: { + _datadog: { + stringValue: + '{"Execution":{"Id":"arn:aws:states:sa-east-1:123456123456:execution:rstrat-sfn-sqs-demo-dev-state-machine:a4912895-93a3-4803-a712-69fecb55c025","StartTime":"2025-07-15T15:48:40.302Z","Name":"a4912895-93a3-4803-a712-69fecb55c025","RoleArn":"arn:aws:iam::123456123456:role/rstrat-sfn-sqs-demo-dev-StepFunctionsExecutionRole-s6ozc2dVrvLH","RedriveCount":0},"StateMachine":{"Id":"arn:aws:states:sa-east-1:123456123456:stateMachine:rstrat-sfn-sqs-demo-dev-state-machine","Name":"rstrat-sfn-sqs-demo-dev-state-machine"},"State":{"Name":"SendToSQS","EnteredTime":"2025-07-15T15:48:40.333Z","RetryCount":0},"RootExecutionId":"arn:aws:states:sa-east-1:123456123456:execution:rstrat-sfn-sqs-demo-dev-state-machine:a4912895-93a3-4803-a712-69fecb55c025","serverless-version":"v1"}', + stringListValues: [], + binaryListValues: [], + dataType: "String", + }, + }, + md5OfMessageAttributes: "5469b8f90bb6ab27e95816c1fa178680", + md5OfBody: "f0c0ddb2ed09a09e8791013f142e8d7e", + eventSource: "aws:sqs", + eventSourceARN: "arn:aws:sqs:sa-east-1:123456123456:rstrat-sfn-sqs-demo-dev-process-event-queue", + awsRegion: "sa-east-1", + }, + ], + }; + + await listener.onStartInvocation(stepFunctionSQSEvent, context as any); + const unwrappedFunc = () => {}; + const wrappedFunc = listener.onWrap(unwrappedFunc); + wrappedFunc(); + await listener.onCompleteInvocation(); + + expect(mockWrap).toHaveBeenCalledWith( + "aws.lambda", + { + resource: "my-Lambda", + service: "my-Lambda", + tags: { + cold_start: "true", + function_arn: "arn:aws:lambda:us-east-1:123456789101:function:my-lambda", + function_version: "$LATEST", + request_id: "1234", + resource_names: "my-Lambda", + functionname: "my-lambda", + "_dd.parent_source": "event", + "function_trigger.event_source": "sqs", + "function_trigger.event_source_arn": + "arn:aws:sqs:sa-east-1:123456123456:rstrat-sfn-sqs-demo-dev-process-event-queue", + datadog_lambda: datadogLambdaVersion, + dd_trace: ddtraceVersion, + }, + type: "serverless", + childOf: expect.objectContaining({ + toSpanId: expect.any(Function), + toTraceId: expect.any(Function), + }), + }, + unwrappedFunc, + ); + }); + it("injects authorizer context if it exists", async () => { const listener = new TraceListener(defaultConfig); mockTraceSource = TraceSource.Event; @@ -333,4 +432,128 @@ describe("TraceListener", () => { "eyJ4LWRhdGFkb2ctcGFyZW50LWlkIjoiNzk3NjQzMTkzNjgwMzg4MjUxIiwieC1kYXRhZG9nLXRyYWNlLWlkIjoiNDExMDkxMTU4MjI5NzQwNTU1MSIsIngtZGF0YWRvZy1zYW1wbGluZy1wcmlvcml0eSI6MSwieC1kYXRhZG9nLXBhcmVudC1zcGFuLWZpbmlzaC10aW1lIjoxNjYxMTg5OTM2OTgxMDAwMDAwLCJ4LWRhdGFkb2ctYXV0aG9yaXppbmctcmVxdWVzdGlkIjoicmFuZG9tSWQifQ==", ); }); + it("sets service name from DD_SERVICE environment variable", async () => { + process.env.DD_SERVICE = "my-custom-service"; + const listener = new TraceListener(defaultConfig); + await listener.onStartInvocation({}, context as any); + const unwrappedFunc = () => {}; + const wrappedFunc = listener.onWrap(unwrappedFunc); + wrappedFunc(); + await listener.onCompleteInvocation(); + + expect(mockWrap).toHaveBeenCalledWith( + "aws.lambda", + { + resource: "my-Lambda", + service: "my-custom-service", + tags: { + cold_start: "true", + function_arn: "arn:aws:lambda:us-east-1:123456789101:function:my-lambda", + function_version: "$LATEST", + request_id: "1234", + resource_names: "my-Lambda", + functionname: "my-lambda", + datadog_lambda: datadogLambdaVersion, + dd_trace: ddtraceVersion, + }, + type: "serverless", + }, + unwrappedFunc, + ); + }); + + describe("DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED tests for aws.lambda service name", () => { + const lambdaContext = { + invokedFunctionArn: "arn:aws:lambda:us-east-1:123456789101:function:my-lambda", + awsRequestId: "1234", + functionName: "my-Lambda", + }; + + beforeEach(() => { + mockWrap.mockClear(); + mockExtract.mockClear(); + mockSpanContext = undefined; + mockSpanContextWrapper = undefined; + mockTraceSource = undefined; + process.env = { ...oldEnv }; // Restore original environment variables + delete process.env.DD_SERVICE; // Ensure DD_SERVICE doesn't interfere + delete process.env.DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED; + }); + + afterEach(() => { + process.env = oldEnv; + }); + + it("uses 'aws.lambda' when DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED is 'false'", async () => { + process.env.DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED = "false"; + const listener = new TraceListener(defaultConfig); + await listener.onStartInvocation({}, lambdaContext as any); + const unwrappedFunc = () => {}; + const wrappedFunc = listener.onWrap(unwrappedFunc); + wrappedFunc(); + await listener.onCompleteInvocation(); + + expect(mockWrap).toHaveBeenCalledWith( + "aws.lambda", + expect.objectContaining({ + service: "aws.lambda", + }), + unwrappedFunc, + ); + }); + + it("uses 'aws.lambda' when DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED is '0'", async () => { + process.env.DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED = "0"; + const listener = new TraceListener(defaultConfig); + await listener.onStartInvocation({}, lambdaContext as any); + const unwrappedFunc = () => {}; + const wrappedFunc = listener.onWrap(unwrappedFunc); + wrappedFunc(); + await listener.onCompleteInvocation(); + + expect(mockWrap).toHaveBeenCalledWith( + "aws.lambda", + expect.objectContaining({ + service: "aws.lambda", + }), + unwrappedFunc, + ); + }); + + it("uses function name when DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED is not set", async () => { + delete process.env.DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED; + const listener = new TraceListener(defaultConfig); + await listener.onStartInvocation({}, lambdaContext as any); + const unwrappedFunc = () => {}; + const wrappedFunc = listener.onWrap(unwrappedFunc); + wrappedFunc(); + await listener.onCompleteInvocation(); + + expect(mockWrap).toHaveBeenCalledWith( + "aws.lambda", + expect.objectContaining({ + service: lambdaContext.functionName, + }), + unwrappedFunc, + ); + }); + + it("uses function name when DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED is 'true'", async () => { + process.env.DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED = "true"; + const listener = new TraceListener(defaultConfig); + await listener.onStartInvocation({}, lambdaContext as any); + const unwrappedFunc = () => {}; + const wrappedFunc = listener.onWrap(unwrappedFunc); + wrappedFunc(); + await listener.onCompleteInvocation(); + + expect(mockWrap).toHaveBeenCalledWith( + "aws.lambda", + expect.objectContaining({ + service: lambdaContext.functionName, + }), + unwrappedFunc, + ); + }); + }); }); diff --git a/src/trace/listener.ts b/src/trace/listener.ts index 873a45db..bc2cb647 100644 --- a/src/trace/listener.ts +++ b/src/trace/listener.ts @@ -7,7 +7,7 @@ import { ColdStartTracerConfig, ColdStartTracer } from "./cold-start-tracer"; import { logDebug, tagObject } from "../utils"; import { didFunctionColdStart, isProactiveInitialization } from "../utils/cold-start"; import { datadogLambdaVersion } from "../constants"; -import { ddtraceVersion, parentSpanFinishTimeHeader } from "./constants"; +import { ddtraceVersion, parentSpanFinishTimeHeader, DD_SERVICE_ENV_VAR } from "./constants"; import { patchConsole } from "./patch-console"; import { SpanContext, TraceOptions, TracerWrapper } from "./tracer-wrapper"; import { SpanInferrer } from "./span-inferrer"; @@ -286,7 +286,7 @@ export class TraceListener { const functionArn = (this.context.invokedFunctionArn ?? "").toLowerCase(); const tk = functionArn.split(":"); options.tags = { - cold_start: didFunctionColdStart(), + cold_start: String(didFunctionColdStart()).toLowerCase(), function_arn: tk.length > 7 ? tk.slice(0, 7).join(":") : functionArn, function_version: tk.length > 7 ? tk[7] : "$LATEST", request_id: this.context.awsRequestId, @@ -319,11 +319,27 @@ export class TraceListener { options.childOf = this.lambdaSpanParentContext; } options.type = "serverless"; - options.service = "aws.lambda"; + if (this.context) { options.resource = this.context.functionName; - options.service = this.context.functionName; } + + const resolvedServiceName = (() => { + const envService = process.env[DD_SERVICE_ENV_VAR]; + if (envService && envService.trim().length > 0) { + return envService.trim(); + } + if ( + process.env.DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED === "false" || + process.env.DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED === "0" + ) { + return "aws.lambda"; + } + return this.context ? this.context.functionName : "aws.lambda"; + })(); + + options.service = resolvedServiceName; + return this.tracerWrapper.wrap("aws.lambda", options, func); } } diff --git a/src/trace/span-inferrer.spec.ts b/src/trace/span-inferrer.spec.ts index 4ed993fd..19eb5709 100644 --- a/src/trace/span-inferrer.spec.ts +++ b/src/trace/span-inferrer.spec.ts @@ -109,6 +109,222 @@ describe("SpanInferrer", () => { expect(serviceName).toBe("fallback"); }); + describe("when DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED is set to 'true'", () => { + beforeEach(() => { + process.env.DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED = "true"; + }); + + it("uses instance-level service name for SNS events", () => { + const inferrer = new SpanInferrer(mockWrapper as unknown as TracerWrapper); + inferrer.createInferredSpan(snsEvent, {} as any, {} as SpanContext); + expect(getStartSpanServiceTag(1)).toBe("ExampleTopic"); + }); + + it("uses instance-level service name for SQS events", () => { + const inferrer = new SpanInferrer(mockWrapper as unknown as TracerWrapper); + inferrer.createInferredSpan(sqsEvent, {} as any, {} as SpanContext); + expect(getStartSpanServiceTag(1)).toBe("MyQueue"); + }); + + it("uses instance-level service name for DDB events", () => { + const inferrer = new SpanInferrer(mockWrapper as unknown as TracerWrapper); + inferrer.createInferredSpan(ddbEvent, {} as any, {} as SpanContext); + expect(getStartSpanServiceTag(1)).toBe("ExampleTableWithStream"); + }); + + it("uses instance-level service name for Kinesis events", () => { + const inferrer = new SpanInferrer(mockWrapper as unknown as TracerWrapper); + inferrer.createInferredSpan(kinesisEvent, {} as any, {} as SpanContext); + expect(getStartSpanServiceTag(1)).toBe("EXAMPLE"); + }); + + it("uses instance-level service name for EventBridge events", () => { + const inferrer = new SpanInferrer(mockWrapper as unknown as TracerWrapper); + inferrer.createInferredSpan(eventBridgeEvent, {} as any, {} as SpanContext); + expect(getStartSpanServiceTag(1)).toBe("my.event"); + }); + + it("uses instance-level service name for API Gateway events", () => { + const inferrer = new SpanInferrer(mockWrapper as unknown as TracerWrapper); + inferrer.createInferredSpan(apiGatewayV1, {} as any, {} as SpanContext); + expect(getStartSpanServiceTag(1)).toBe("id.execute-api.us-east-1.amazonaws.com"); + }); + + it("uses instance-level service name for Lambda Function URL events", () => { + const inferrer = new SpanInferrer(mockWrapper as unknown as TracerWrapper); + inferrer.createInferredSpan(functionUrlEvent, {} as any, {} as SpanContext); + expect(getStartSpanServiceTag(1)).toBe("a8hyhsshac.lambda-url.eu-south-1.amazonaws.com"); + }); + + it("uses instance-level service name for S3 events", () => { + const inferrer = new SpanInferrer(mockWrapper as unknown as TracerWrapper); + inferrer.createInferredSpan(s3Event, {} as any, {} as SpanContext); + expect(getStartSpanServiceTag(1)).toBe("example-bucket"); + }); + }); + + describe("when DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED is set to 'false'", () => { + beforeEach(() => { + process.env.DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED = "false"; + }); + + it("uses generic service name for SNS events", () => { + const inferrer = new SpanInferrer(mockWrapper as unknown as TracerWrapper); + inferrer.createInferredSpan(snsEvent, {} as any, {} as SpanContext); + expect(getStartSpanServiceTag(1)).toBe("sns"); + }); + + it("uses generic service name for SQS events", () => { + const inferrer = new SpanInferrer(mockWrapper as unknown as TracerWrapper); + inferrer.createInferredSpan(sqsEvent, {} as any, {} as SpanContext); + expect(getStartSpanServiceTag(1)).toBe("sqs"); + }); + + it("uses generic service name for DDB events", () => { + const inferrer = new SpanInferrer(mockWrapper as unknown as TracerWrapper); + inferrer.createInferredSpan(ddbEvent, {} as any, {} as SpanContext); + expect(getStartSpanServiceTag(1)).toBe("aws.dynamodb"); + }); + + it("uses generic service name for Kinesis events", () => { + const inferrer = new SpanInferrer(mockWrapper as unknown as TracerWrapper); + inferrer.createInferredSpan(kinesisEvent, {} as any, {} as SpanContext); + expect(getStartSpanServiceTag(1)).toBe("kinesis"); + }); + + it("uses generic service name for EventBridge events", () => { + const inferrer = new SpanInferrer(mockWrapper as unknown as TracerWrapper); + inferrer.createInferredSpan(eventBridgeEvent, {} as any, {} as SpanContext); + expect(getStartSpanServiceTag(1)).toBe("eventbridge"); + }); + + it("uses generic service name for API Gateway events", () => { + const inferrer = new SpanInferrer(mockWrapper as unknown as TracerWrapper); + inferrer.createInferredSpan(apiGatewayV1, {} as any, {} as SpanContext); + expect(getStartSpanServiceTag(1)).toBe("id.execute-api.us-east-1.amazonaws.com"); + }); + + it("uses generic service name for Lambda Function URL events", () => { + const inferrer = new SpanInferrer(mockWrapper as unknown as TracerWrapper); + inferrer.createInferredSpan(functionUrlEvent, {} as any, {} as SpanContext); + expect(getStartSpanServiceTag(1)).toBe("a8hyhsshac.lambda-url.eu-south-1.amazonaws.com"); + }); + + it("uses generic service name for S3 events", () => { + const inferrer = new SpanInferrer(mockWrapper as unknown as TracerWrapper); + inferrer.createInferredSpan(s3Event, {} as any, {} as SpanContext); + expect(getStartSpanServiceTag(1)).toBe("s3"); + }); + }); + + describe("when DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED is set to '0'", () => { + beforeEach(() => { + process.env.DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED = "0"; + }); + + it("uses generic service name for SNS events", () => { + const inferrer = new SpanInferrer(mockWrapper as unknown as TracerWrapper); + inferrer.createInferredSpan(snsEvent, {} as any, {} as SpanContext); + expect(getStartSpanServiceTag(1)).toBe("sns"); + }); + + it("uses generic service name for SQS events", () => { + const inferrer = new SpanInferrer(mockWrapper as unknown as TracerWrapper); + inferrer.createInferredSpan(sqsEvent, {} as any, {} as SpanContext); + expect(getStartSpanServiceTag(1)).toBe("sqs"); + }); + + it("uses generic service name for DDB events", () => { + const inferrer = new SpanInferrer(mockWrapper as unknown as TracerWrapper); + inferrer.createInferredSpan(ddbEvent, {} as any, {} as SpanContext); + expect(getStartSpanServiceTag(1)).toBe("aws.dynamodb"); + }); + + it("uses generic service name for Kinesis events", () => { + const inferrer = new SpanInferrer(mockWrapper as unknown as TracerWrapper); + inferrer.createInferredSpan(kinesisEvent, {} as any, {} as SpanContext); + expect(getStartSpanServiceTag(1)).toBe("kinesis"); + }); + + it("uses generic service name for EventBridge events", () => { + const inferrer = new SpanInferrer(mockWrapper as unknown as TracerWrapper); + inferrer.createInferredSpan(eventBridgeEvent, {} as any, {} as SpanContext); + expect(getStartSpanServiceTag(1)).toBe("eventbridge"); + }); + + it("uses generic service name for API Gateway events", () => { + const inferrer = new SpanInferrer(mockWrapper as unknown as TracerWrapper); + inferrer.createInferredSpan(apiGatewayV1, {} as any, {} as SpanContext); + expect(getStartSpanServiceTag(1)).toBe("id.execute-api.us-east-1.amazonaws.com"); + }); + + it("uses generic service name for Lambda Function URL events", () => { + const inferrer = new SpanInferrer(mockWrapper as unknown as TracerWrapper); + inferrer.createInferredSpan(functionUrlEvent, {} as any, {} as SpanContext); + expect(getStartSpanServiceTag(1)).toBe("a8hyhsshac.lambda-url.eu-south-1.amazonaws.com"); + }); + + it("uses generic service name for S3 events", () => { + const inferrer = new SpanInferrer(mockWrapper as unknown as TracerWrapper); + inferrer.createInferredSpan(s3Event, {} as any, {} as SpanContext); + expect(getStartSpanServiceTag(1)).toBe("s3"); + }); + }); + + describe("when DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED is not set", () => { + beforeEach(() => { + delete process.env.DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED; + }); + + it("uses instance-level service name for SNS events", () => { + const inferrer = new SpanInferrer(mockWrapper as unknown as TracerWrapper); + inferrer.createInferredSpan(snsEvent, {} as any, {} as SpanContext); + expect(getStartSpanServiceTag(1)).toBe("ExampleTopic"); + }); + + it("uses instance-level service name for SQS events", () => { + const inferrer = new SpanInferrer(mockWrapper as unknown as TracerWrapper); + inferrer.createInferredSpan(sqsEvent, {} as any, {} as SpanContext); + expect(getStartSpanServiceTag(1)).toBe("MyQueue"); + }); + + it("uses instance-level service name for DDB events", () => { + const inferrer = new SpanInferrer(mockWrapper as unknown as TracerWrapper); + inferrer.createInferredSpan(ddbEvent, {} as any, {} as SpanContext); + expect(getStartSpanServiceTag(1)).toBe("ExampleTableWithStream"); + }); + + it("uses instance-level service name for Kinesis events", () => { + const inferrer = new SpanInferrer(mockWrapper as unknown as TracerWrapper); + inferrer.createInferredSpan(kinesisEvent, {} as any, {} as SpanContext); + expect(getStartSpanServiceTag(1)).toBe("EXAMPLE"); + }); + + it("uses instance-level service name for EventBridge events", () => { + const inferrer = new SpanInferrer(mockWrapper as unknown as TracerWrapper); + inferrer.createInferredSpan(eventBridgeEvent, {} as any, {} as SpanContext); + expect(getStartSpanServiceTag(1)).toBe("my.event"); + }); + + it("uses instance-level service name for API Gateway events", () => { + const inferrer = new SpanInferrer(mockWrapper as unknown as TracerWrapper); + inferrer.createInferredSpan(apiGatewayV1, {} as any, {} as SpanContext); + expect(getStartSpanServiceTag(1)).toBe("id.execute-api.us-east-1.amazonaws.com"); + }); + + it("uses instance-level service name for Lambda Function URL events", () => { + const inferrer = new SpanInferrer(mockWrapper as unknown as TracerWrapper); + inferrer.createInferredSpan(functionUrlEvent, {} as any, {} as SpanContext); + expect(getStartSpanServiceTag(1)).toBe("a8hyhsshac.lambda-url.eu-south-1.amazonaws.com"); + }); + + it("uses instance-level service name for S3 events", () => { + const inferrer = new SpanInferrer(mockWrapper as unknown as TracerWrapper); + inferrer.createInferredSpan(s3Event, {} as any, {} as SpanContext); + expect(getStartSpanServiceTag(1)).toBe("example-bucket"); + }); + }); + it("extracts service name from event when service mapping has incorrect delimiters", () => { process.env.DD_SERVICE_MAPPING = "key1-value1,key2=value2"; const inferrer = new SpanInferrer(mockWrapper as unknown as TracerWrapper); diff --git a/src/trace/span-inferrer.ts b/src/trace/span-inferrer.ts index 2ef63b9a..34466006 100644 --- a/src/trace/span-inferrer.ts +++ b/src/trace/span-inferrer.ts @@ -82,11 +82,19 @@ export class SpanInferrer { } static determineServiceName(specificKey: string, genericKey: string, extractedKey: string, fallback: string): string { - return ( - this.serviceMapping[specificKey] || - this.serviceMapping[genericKey] || - (extractedKey?.trim() ? extractedKey : fallback) - ); + const mappedService = this.serviceMapping[specificKey] || this.serviceMapping[genericKey]; + if (mappedService) { + return mappedService; + } + + if ( + process.env.DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED === "false" || + process.env.DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED === "0" + ) { + return fallback; + } + + return extractedKey?.trim() ? extractedKey : fallback; } createInferredSpanForApiGateway( @@ -109,7 +117,7 @@ export class SpanInferrer { } const resourceName = [method || domain, resourcePath].join(" "); const apiId = event.requestContext.apiId || ""; - const serviceName = SpanInferrer.determineServiceName(apiId, "lambda_api_gateway", domain, "aws.apigateway"); + const serviceName = SpanInferrer.determineServiceName(apiId, "lambda_api_gateway", domain, domain); options.tags = { operation_name: "aws.apigateway", @@ -208,7 +216,7 @@ export class SpanInferrer { } const resourceName = [method || domain, path].join(" "); const apiId: string = event.requestContext.apiId || ""; - const serviceName: string = SpanInferrer.determineServiceName(apiId, "lambda_url", domain, "aws.lambda_url"); + const serviceName: string = SpanInferrer.determineServiceName(apiId, "lambda_url", domain, domain); options.tags = { operation_name: "aws.lambda.url", @@ -305,7 +313,7 @@ export class SpanInferrer { const topicName = TopicArn?.split(":").pop() || ""; const resourceName = topicName; - const serviceName = SpanInferrer.determineServiceName(topicName, "lambda_sns", topicName, "aws.sns"); + const serviceName = SpanInferrer.determineServiceName(topicName, "lambda_sns", topicName, "sns"); options.tags = { operation_name: "aws.sns", resource_names: resourceName, @@ -357,7 +365,7 @@ export class SpanInferrer { } = referenceRecord; const queueName = eventSourceARN?.split(":").pop() || ""; const resourceName = queueName; - const serviceName = SpanInferrer.determineServiceName(queueName, "lambda_sqs", queueName, "aws.sqs"); + const serviceName = SpanInferrer.determineServiceName(queueName, "lambda_sqs", queueName, "sqs"); options.tags = { operation_name: "aws.sqs", resource_names: resourceName, @@ -424,7 +432,7 @@ export class SpanInferrer { } = referenceRecord; const streamName = (eventSourceARN?.split(":").pop() || "").replace(/^stream\//, ""); const shardId = eventID.split(":").pop(); - const serviceName = SpanInferrer.determineServiceName(streamName, "lambda_kinesis", streamName, "aws.kinesis"); + const serviceName = SpanInferrer.determineServiceName(streamName, "lambda_kinesis", streamName, "kinesis"); options.tags = { operation_name: "aws.kinesis", resource_names: streamName, @@ -473,7 +481,7 @@ export class SpanInferrer { eventTime, eventName, } = referenceRecord; - const serviceName = SpanInferrer.determineServiceName(bucketName, "lambda_s3", bucketName, "aws.s3"); + const serviceName = SpanInferrer.determineServiceName(bucketName, "lambda_s3", bucketName, "s3"); options.tags = { operation_name: "aws.s3", resource_names: bucketName, @@ -512,7 +520,7 @@ export class SpanInferrer { ): SpanWrapper { const options: SpanOptions = {}; const { time, source } = event as EventBridgeEvent; - const serviceName = SpanInferrer.determineServiceName(source, "lambda_eventbridge", source, "aws.eventbridge"); + const serviceName = SpanInferrer.determineServiceName(source, "lambda_eventbridge", source, "eventbridge"); options.tags = { operation_name: "aws.eventbridge", resource_names: source, diff --git a/src/trace/tracer-wrapper.ts b/src/trace/tracer-wrapper.ts index dd9f4c9d..014d656e 100644 --- a/src/trace/tracer-wrapper.ts +++ b/src/trace/tracer-wrapper.ts @@ -77,6 +77,7 @@ export class TracerWrapper { public startSpan any>(name: string, options: TraceOptions): T | null { if (!this.isTracerAvailable) { + logDebug("No Tracer available, cannot start span"); return null; } return this.tracer.startSpan(name, options); diff --git a/yarn.lock b/yarn.lock index 1ec322e5..8a0bab15 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2630,6 +2630,14 @@ builtin-modules@^1.1.1: resolved "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz" integrity sha512-wxXCdllwGhI2kCC0MnvTGYTMvnVZTvqgypkiTI8Pa5tcz2i6VqsqwYGgqwXji+4RgCzms6EajE4IxiUH6HH8nQ== +call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" + integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + call-bind@^1.0.2, call-bind@^1.0.7: version "1.0.7" resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz" @@ -2911,6 +2919,15 @@ domexception@^2.0.1: dependencies: webidl-conversions "^5.0.0" +dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== + dependencies: + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" + electron-to-chromium@^1.5.41: version "1.5.43" resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.43.tgz" @@ -2945,11 +2962,33 @@ es-define-property@^1.0.0: dependencies: get-intrinsic "^1.2.4" +es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== + es-errors@^1.3.0: version "1.3.0" resolved "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz" integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== +es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" + integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== + dependencies: + es-errors "^1.3.0" + +es-set-tostringtag@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" + integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== + dependencies: + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + has-tostringtag "^1.0.2" + hasown "^2.0.2" + escalade@^3.1.1, escalade@^3.2.0: version "3.2.0" resolved "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz" @@ -3073,13 +3112,15 @@ for-each@^0.3.3: is-callable "^1.1.3" form-data@^3.0.0: - version "3.0.2" - resolved "https://registry.npmjs.org/form-data/-/form-data-3.0.2.tgz" - integrity sha512-sJe+TQb2vIaIyO783qN6BlMYWMw3WBOHA1Ay2qxsnjuafEOQFJ2JakedOQirT6D5XPRxDvS7AHYyem9fTpb4LQ== + version "3.0.4" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.4.tgz#938273171d3f999286a4557528ce022dc2c98df1" + integrity sha512-f0cRzm6dkyVYV3nPoooP8XlccPQukegwhAnpoLcXy+X+A8KfpGOoXwDr9FLZd3wzgLaBGQBE3lY93Zm/i1JvIQ== dependencies: asynckit "^0.4.0" combined-stream "^1.0.8" - mime-types "^2.1.12" + es-set-tostringtag "^2.1.0" + hasown "^2.0.2" + mime-types "^2.1.35" fs.realpath@^1.0.0: version "1.0.0" @@ -3117,11 +3158,35 @@ get-intrinsic@^1.1.3, get-intrinsic@^1.2.4: has-symbols "^1.0.3" hasown "^2.0.0" +get-intrinsic@^1.2.6: + version "1.3.0" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" + integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== + dependencies: + call-bind-apply-helpers "^1.0.2" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + function-bind "^1.1.2" + get-proto "^1.0.1" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + math-intrinsics "^1.1.0" + get-package-type@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== +get-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== + dependencies: + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" + get-stream@^6.0.0: version "6.0.1" resolved "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz" @@ -3151,6 +3216,11 @@ gopd@^1.0.1: dependencies: get-intrinsic "^1.1.3" +gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== + graceful-fs@^4.2.9: version "4.2.11" resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz" @@ -3183,6 +3253,11 @@ has-symbols@^1.0.3: resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz" integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== +has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== + has-tostringtag@^1.0.0, has-tostringtag@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz" @@ -3997,6 +4072,11 @@ makeerror@1.0.12: dependencies: tmpl "1.0.5" +math-intrinsics@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== + merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz" @@ -4015,9 +4095,9 @@ mime-db@1.52.0: resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@^2.1.12: +mime-types@^2.1.35: version "2.1.35" - resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== dependencies: mime-db "1.52.0" From 70c5824d376ecc75c3f9ec375dff83c8f6cfabbd Mon Sep 17 00:00:00 2001 From: Michael Zhao Date: Wed, 13 Aug 2025 12:49:52 -0400 Subject: [PATCH 20/20] add checkpoints for headers --- src/trace/context/extractors/sns-sqs.ts | 2 ++ src/trace/context/extractors/sns.ts | 1 + 2 files changed, 3 insertions(+) diff --git a/src/trace/context/extractors/sns-sqs.ts b/src/trace/context/extractors/sns-sqs.ts index c7b085e9..8257d3b7 100644 --- a/src/trace/context/extractors/sns-sqs.ts +++ b/src/trace/context/extractors/sns-sqs.ts @@ -28,6 +28,7 @@ export class SNSSQSEventTraceExtractor implements EventTraceExtractor { } const traceContext = extractTraceContext(headers, this.tracerWrapper); + this.tracerWrapper.setConsumeCheckpoint(headers, "sqs", sourceARN); if (traceContext) { return traceContext; } @@ -40,6 +41,7 @@ export class SNSSQSEventTraceExtractor implements EventTraceExtractor { if (sqsMessageAttribute?.stringValue) { const headers = JSON.parse(sqsMessageAttribute.stringValue); const traceContext = extractTraceContext(headers, this.tracerWrapper); + this.tracerWrapper.setConsumeCheckpoint(headers, "sqs", sourceARN); if (traceContext) { return traceContext; } diff --git a/src/trace/context/extractors/sns.ts b/src/trace/context/extractors/sns.ts index 57e3da69..ddf93f5a 100644 --- a/src/trace/context/extractors/sns.ts +++ b/src/trace/context/extractors/sns.ts @@ -25,6 +25,7 @@ export class SNSEventTraceExtractor implements EventTraceExtractor { } const traceContext = extractTraceContext(headers, this.tracerWrapper); + this.tracerWrapper.setConsumeCheckpoint(headers, "sns", sourceARN); if (traceContext) { return traceContext; }