8000 Extract trace context from ALB multiValueHeaders by nhulston · Pull Request #647 · DataDog/datadog-lambda-js · GitHub
[go: up one dir, main page]

Skip to content

Extract trace context from ALB multiValueHeaders #647

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions event_samples/application-load-balancer-multivalue-headers.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
{
"requestContext": {
"elb": {
"targetGroupArn": "arn:aws:elasticloadbalancing:us-east-1:1234567890:targetgroup/nhulston-alb-test/dcabb42f66a496e0"
}
},
"httpMethod": "GET",
"path": "/",
"multiValueQueryStringParameters": {},
"multiValueHeaders": {
"accept": [
"*/*"
],
"accept-encoding": [
"gzip, deflate"
],
"accept-language": [
"*"
],
"connection": [
"keep-alive"
],
"host": [
"nhulston-test-0987654321.us-east-1.elb.amazonaws.com"
],
"sec-fetch-mode": [
"cors"
],
"traceparent": [
"00-68126c4300000000125a7f065cf9a530-1c6dcc8ab8a6e99d-01"
],
"tracestate": [
"dd=t.dm:-0;t.tid:68126c4300000000;s:1;p:1c6dcc8ab8a6e99d"
],
"user-agent": [
"node"
],
"x-amzn-trace-id": [
"Root=1-68126c45-01b175997ab51c4c47a2d643"
],
"x-datadog-parent-id": [
"1234567890"
],
"x-datadog-sampling-priority": [
"1"
],
"x-datadog-tags": [
"_dd.p.tid=68126c4300000000,_dd.p.dm=-0"
],
"x-datadog-trace-id": [
"0987654321"
],
"x-forwarded-for": [
"18.204.55.6"
],
"x-forwarded-port": [
"80"
],
"x-forwarded-proto": [
"http"
]
},
"body": "",
"isBase64Encoded": false
}
3 changes: 2 additions & 1 deletion src/trace/context/extractor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ export class TraceContextExtractor {
private getTraceEventExtractor(event: any): EventTraceExtractor | undefined {
if (!event || typeof event !== "object") return;

if (event.headers !== null && typeof event.headers === "object") {
const headers = event.headers ?? event.multiValueHeaders;
if (headers !== null && typeof headers === "object") {
return new HTTPEventTraceExtractor(this.tracerWrapper, this.config.decodeAuthorizerContext);
}

Expand Down
61 changes: 61 additions & 0 deletions src/trace/context/extractors/http.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { TracerWrapper } from "../../tracer-wrapper";
import { HTTPEventSubType, HTTPEventTraceExtractor } from "./http";
const albMultivalueHeadersEvent = require("../../../../event_samples/application-load-balancer-multivalue-headers.json");

let mockSpanContext: any = null;

Expand Down Expand Up @@ -97,6 +98,66 @@ describe("HTTPEventTraceExtractor", () => {
expect(traceContext?.source).toBe("event");
});

it("extracts trace context from payload with multiValueHeaders", () => {
mockSpanContext = {
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you add an event from either ALB or API GW with real values to ensure this works? That way we can re-use it in Bottlecap too

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just commenting for visibility -- as discussed, this change only applies to ALB. API GW can have multiValueHeaders, but trace context is always injected under the regular headers so no changes needed for API GW

Copy link
Contributor Author

Choose a reason for hiding this comment

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

But I added a real event from ALB!

toTraceId: () => "123",
toSpanId: () => "456",
_sampling: { priority: "1" },
};
const tracerWrapper = new TracerWrapper();
const payload = {
multiValueHeaders: {
"X-Datadog-Trace-Id": ["123", "789"],
"X-Datadog-Parent-Id": ["456"],
"X-Datadog-Sampling-Priority": ["1"],
},
};
const extractor = new HTTPEventTraceExtractor(tracerWrapper);
const traceContext = extractor.extract(payload);

expect(traceContext).not.toBeNull();
expect(spyTracerWrapper).toHaveBeenCalledWith({
"x-datadog-trace-id": "123",
"x-datadog-parent-id": "456",
"x-datadog-sampling-priority": "1",
});

expect(traceContext?.toTraceId()).toBe("123");
expect(traceContext?.toSpanId()).toBe("456");
expect(traceContext?.sampleMode()).toBe("1");
});

it("flattens a real ALB multiValueHeaders payload into a lowercase, single-value map", () => {
const tracerWrapper = new TracerWrapper();
const extractor = new HTTPEventTraceExtractor(tracerWrapper);

spyTracerWrapper.mockClear();
extractor.extract(albMultivalueHeadersEvent);
expect(spyTracerWrapper).toHaveBeenCalled();

const captured = spyTracerWrapper.mock.calls[0][0] as Record<string, string>;

expect(captured).toEqual({
accept: "*/*",
"accept-encoding": "gzip, deflate",
"accept-language": "*",
connection: "keep-alive",
host: "nhulston-test-0987654321.us-east-1.elb.amazonaws.com",
"sec-fetch-mode": "cors",
"user-agent": "node",
traceparent: "00-68126c4300000000125a7f065cf9a530-1c6dcc8ab8a6e99d-01",
tracestate: "dd=t.dm:-0;t.tid:68126c4300000000;s:1;p:1c6dcc8ab8a6e99d",
"x-amzn-trace-id": "Root=1-68126c45-01b175997ab51c4c47a2d643",
"x-datadog-tags": "_dd.p.tid=68126c4300000000,_dd.p.dm=-0",
"x-datadog-sampling-priority": "1",
"x-datadog-trace-id": "0987654321",
"x-datadog-parent-id": "1234567890",
"x-forwarded-for": "18.204.55.6",
"x-forwarded-port": "80",
"x-forwarded-proto": "http",
});
});

it("extracts trace context from payload with authorizer", () => {
mockSpanContext = {
toTraceId: () => "2389589954026090296",
Expand Down
12 changes: 9 additions & 3 deletions src/trace/context/extractors/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,17 @@ export class HTTPEventTraceExtractor implements EventTraceExtractor {
}
}

const headers = event.headers;
const headers = event.headers ?? event.multiValueHeaders;
const lowerCaseHeaders: { [key: string]: string } = {};

for (const key of Object.keys(headers)) {
lowerCaseHeaders[key.toLowerCase()] = headers[key];
for (const [key, val] of Object.entries(headers)) {
if (Array.isArray(val)) {
// MultiValueHeaders: take the first value
lowerCaseHeaders[key.toLowerCase()] = val[0] ?? "";
} else if (typeof val === "string") {
// Single‐value header
lowerCaseHeaders[key.toLowerCase()] = val;
}
Comment on lines +48 to +54
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
if (Array.isArray(val)) {
// MultiValueHeaders: take the first value
lowerCaseHeaders[key.toLowerCase()] = val[0] ?? "";
} else if (typeof val === "string") {
// Single‐value header
lowerCaseHeaders[key.toLowerCase()] = val;
}
if (Array.isArray(val)) {
// MultiValueHeaders: take the first value
lowerCaseHeaders[key.toLowerCase()] = val[0] ?? "";
continue;
}
// Single‐value header
lowerCaseHeaders[key.toLowerCase()] = val;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We have to type check key and make sure it's a string first; otherwise typescript errors TS2322: Type unknown is not assignable to type string

}

const traceContext = this.tracerWrapper.extract(lowerCaseHeaders);
Expand Down
Loading
0