Skip to content

Commit 5184c05

Browse files
committed
extrace trace context from AWSTraceHeader in SQS java upstream case
1 parent 5d49414 commit 5184c05

File tree

4 files changed

+113
-30
lines changed

4 files changed

+113
-30
lines changed

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@
3939
"typescript": "^4.3.2"
4040
},
4141
"dependencies": {
42-
"bignumber.js": "^9.0.1",
4342
"dc-polyfill": "^0.1.3",
4443
"hot-shots": "8.5.0",
4544
"promise-retry": "^2.0.1",

src/trace/context/extractors/sqs.spec.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,5 +126,53 @@ describe("SQSEventTraceExtractor", () => {
126126
const traceContext = extractor.extract(payload);
127127
expect(traceContext).toBeNull();
128128
});
129+
130+
it("extracts trace context from AWSTraceHeader with valid payload", () => {
131+
mockSpanContext = {
132+
toTraceId: () => "625397077193750208",
133+
toSpanId: () => "6538302989251745223",
134+
_sampling: {
135+
priority: "1",
136+
},
137+
};
138+
const tracerWrapper = new TracerWrapper();
139+
const payload: SQSEvent = {
140+
Records: [
141+
{
142+
body: "Hello world",
143+
attributes: {
144+
ApproximateReceiveCount: "1",
145+
SentTimestamp: "1605544528092",
146+
SenderId: "AROAYYB64AB3JHSRKO6XR:sqs-trace-dev-producer",
147+
ApproximateFirstReceiveTimestamp: "1605544528094",
148+
AWSTraceHeader: "Root=1-65f2f78c-0000000008addb5405b376c0;Parent=5abcb7ed643995c7;Sampled=1",
149+
},
150+
messageAttributes: {},
151+
eventSource: "aws:sqs",
152+
eventSourceARN: "arn:aws:sqs:eu-west-1:601427279990:metal-queue",
153+
awsRegion: "eu-west-1",
154+
messageId: "foo",
155+
md5OfBody: "x",
156+
receiptHandle: "x",
157+
},
158+
],
159+
};
160+
161+
const extractor = new SQSEventTraceExtractor(tracerWrapper);
162+
163+
const traceContext = extractor.extract(payload);
164+
expect(traceContext).not.toBeNull();
165+
166+
expect(spyTracerWrapper).toHaveBeenCalledWith({
167+
"x-datadog-parent-id": "6538302989251745223",
168+
"x-datadog-sampling-priority": "1",
169+
"x-datadog-trace-id": "625397077193750208",
170+
});
171+
172+
expect(traceContext?.toTraceId()).toBe("625397077193750208");
173+
expect(traceContext?.toSpanId()).toBe("6538302989251745223");
174+
expect(traceContext?.sampleMode()).toBe("1");
175+
expect(traceContext?.source).toBe("event");
176+
});
129177
});
130178
});

src/trace/context/extractors/sqs.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,29 @@ import { EventTraceExtractor } from "../extractor";
33
import { TracerWrapper } from "../../tracer-wrapper";
44
import { logDebug } from "../../../utils";
55
import { SpanContextWrapper } from "../../span-context-wrapper";
6+
import { XrayService } from "../../xray-service";
67

78
export class SQSEventTraceExtractor implements EventTraceExtractor {
89
constructor(private tracerWrapper: TracerWrapper) {}
910

1011
extract(event: SQSEvent): SpanContextWrapper | null {
11-
const headers = event?.Records?.[0]?.messageAttributes?._datadog?.stringValue;
12-
if (headers === undefined) return null;
13-
1412
try {
15-
const traceContext = this.tracerWrapper.extract(JSON.parse(headers));
16-
if (traceContext === null) return null;
13+
var prepared_headers;
14+
const headers = event?.Records?.[0]?.messageAttributes?._datadog?.stringValue;
15+
if (headers !== undefined) {
16+
prepared_headers = JSON.parse(headers);
17+
} else {
18+
if (event?.Records?.[0]?.attributes?.AWSTraceHeader) {
19+
prepared_headers = XrayService.extraceDDContextFromAWSTraceHeader(event.Records[0].attributes.AWSTraceHeader);
20+
}
21+
}
1722

23+
if (!prepared_headers) return null;
24+
const traceContext = this.tracerWrapper.extract(prepared_headers);
25+
if (traceContext === null) {
26+
logDebug("Unable to extract the injected trace context from event");
27+
return null;
28+
}
1829
logDebug(`Extracted trace context from SQS event`, { traceContext, event });
1930
return traceContext;
2031
} catch (error) {

src/trace/xray-service.ts

Lines changed: 49 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
import { randomBytes } from "crypto";
22
import { logDebug } from "../utils";
33
import { SampleMode, TraceContext, TraceSource } from "./trace-context-service";
4-
import BigNumber from "bignumber.js";
54
import { Socket, createSocket } from "dgram";
65
import { SpanContextWrapper } from "./span-context-wrapper";
76
import { StepFunctionContext } from "./step-function-service";
7+
import {
8+
DATADOG_TRACE_ID_HEADER,
9+
DATADOG_PARENT_ID_HEADER,
10+
DATADOG_SAMPLING_PRIORITY_HEADER,
11+
DatadogTraceHeaders,
12+
} from "./context/extractor";
813

914
const AMZN_TRACE_ID_ENV_VAR = "_X_AMZN_TRACE_ID";
1015
const AWS_XRAY_DAEMON_ADDRESS_ENV_VAR = "AWS_XRAY_DAEMON_ADDRESS";
@@ -70,16 +75,8 @@ export class XrayService {
7075
});
7176
}
7277

73-
private parseTraceContextHeader(): XrayTraceHeader | undefined {
74-
const header = process.env[AMZN_TRACE_ID_ENV_VAR];
75-
if (header === undefined) {
76-
logDebug("Couldn't read Xray trace header from env");
77-
return;
78-
}
79-
80-
// Example: Root=1-5e272390-8c398be037738dc042009320;Parent=94ae789b969f1cc5;Sampled=1
81-
logDebug(`Reading Xray trace context from env var ${header}`);
82-
const [root, parent, _sampled] = header.split(";");
78+
public static parseAWSTraceHeader(awsTraceHeader: string): XrayTraceHeader | undefined {
79+
const [root, parent, _sampled] = awsTraceHeader.split(";");
8380
if (parent === undefined || _sampled === undefined) return;
8481

8582
const [, traceId] = root.split("=");
@@ -94,6 +91,18 @@ export class XrayService {
9491
};
9592
}
9693

94+
private parseTraceContextHeader(): XrayTraceHeader | undefined {
95+
const header = process.env[AMZN_TRACE_ID_ENV_VAR];
96+
if (header === undefined) {
97+
logDebug("Couldn't read Xray trace header from env");
98+
return;
99+
}
100+
101+
// Example: Root=1-5e272390-8c398be037738dc042009320;Parent=94ae789b969f1cc5;Sampled=1
102+
logDebug(`Reading Xray trace context from env var ${header}`);
103+
return XrayService.parseAWSTraceHeader(header);
104+
}
105+
97106
private convertToSampleMode(xraySampled: number): SampleMode {
98107
return xraySampled === 1 ? SampleMode.USER_KEEP : SampleMode.USER_REJECT;
99108
}
@@ -172,11 +181,11 @@ export class XrayService {
172181

173182
private convertToParentId(xrayParentId: string): string | undefined {
174183
if (xrayParentId.length !== 16) return;
175-
176-
const hex = new BigNumber(xrayParentId, 16);
177-
if (hex.isNaN()) return;
178-
179-
return hex.toString(10);
184+
try {
185+
return BigInt("0x" + xrayParentId).toString(10);
186+
} catch (_) {
187+
return undefined;
188+
}
180189
}
181190

182191
private convertToTraceId(xrayTraceId: string): string | undefined {
@@ -187,13 +196,29 @@ export class XrayService {
187196
if (lastPart.length !== 24) return;
188197

189198
// We want to turn the last 63 bits into a decimal number in a string representation
190-
// Unfortunately, all numbers in javascript are represented by float64 bit numbers, which
191-
// means we can't parse 64 bit integers accurately.
192-
const hex = new BigNumber(lastPart, 16);
193-
if (hex.isNaN()) return;
194-
195-
// Toggle off the 64th bit
196-
const last63Bits = hex.mod(new BigNumber("8000000000000000", 16));
197-
return last63Bits.toString(10);
199+
try {
200+
return (BigInt("0x" + lastPart) % BigInt("0x8000000000000000")).toString(10);
201+
} catch (_) {
202+
return undefined;
203+
}
204+
}
205+
206+
public static extraceDDContextFromAWSTraceHeader(amznTraceId: string): DatadogTraceHeaders | null {
207+
var aws_context = XrayService.parseAWSTraceHeader(amznTraceId);
208+
if (!aws_context) {
209+
return null;
210+
}
211+
const trace_id_parts = aws_context.traceId.split("-");
212+
if (trace_id_parts && trace_id_parts.length > 2 && trace_id_parts[2].startsWith("00000000")) {
213+
// This AWSTraceHeader contains Datadog injected trace context
214+
return {
215+
[DATADOG_TRACE_ID_HEADER]: hexStrToDecimalStr(trace_id_parts[2].substring(8)),
216+
[DATADOG_PARENT_ID_HEADER]: hexStrToDecimalStr(aws_context.parentId),
217+
[DATADOG_SAMPLING_PRIORITY_HEADER]: aws_context.sampled,
218+
};
219+
}
220+
return null;
198221
}
199222
}
223+
224+
const hexStrToDecimalStr = (hexString: string): string => BigInt("0x" + hexString).toString(10);

0 commit comments

Comments
 (0)