Skip to content

Commit 1f76a08

Browse files
feat: Support async trace context extraction (#387)
Co-authored-by: Liza Pressman <35850765+lizapressman@users.noreply.github.com>
1 parent 22627ab commit 1f76a08

File tree

6 files changed

+79
-42
lines changed

6 files changed

+79
-42
lines changed

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ export function datadog<TEvent, TResult>(
138138
currentTraceListener = traceListener;
139139

140140
try {
141-
traceListener.onStartInvocation(event, context);
141+
await traceListener.onStartInvocation(event, context);
142142
await metricsListener.onStartInvocation(event);
143143
if (finalConfig.enhancedMetrics) {
144144
incrementInvocationsMetric(metricsListener, context);

src/trace/context.spec.ts

Lines changed: 63 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -817,10 +817,10 @@ describe("extractTraceContext", () => {
817817
process.env["_X_AMZN_TRACE_ID"] = undefined;
818818
process.env[awsXrayDaemonAddressEnvVar] = undefined;
819819
});
820-
it("returns trace read from header as highest priority with no extractor", () => {
820+
it("returns trace read from header as highest priority with no extractor", async () => {
821821
process.env["_X_AMZN_TRACE_ID"] = "Root=1-5ce31dc2-2c779014b90ce44db5e03875;Parent=0b11cc4230d3e09e;Sampled=1";
822822

823-
const result = extractTraceContext(
823+
const result = await extractTraceContext(
824824
{
825825
headers: {
826826
"x-datadog-parent-id": "797643193680388251",
@@ -837,16 +837,53 @@ describe("extractTraceContext", () => {
837837
source: Source.Event,
838838
});
839839
});
840-
it("returns an empty context when headers are null", () => {
841-
const result = extractTraceContext(
840+
it("returns an empty context when headers are null", async () => {
841+
const result = await extractTraceContext(
842842
{
843843
headers: null,
844844
},
845845
{} as Context,
846846
);
847847
expect(result).toEqual(undefined);
848848
});
849-
it("returns trace read from event with the extractor as the highest priority", () => {
849+
850+
it("returns trace read from event with an async extractor as the highest priority", async () => {
851+
process.env["_X_AMZN_TRACE_ID"] = "Root=1-5ce31dc2-2c779014b90ce44db5e03875;Parent=0b11cc4230d3e09e;Sampled=1";
852+
853+
const extractor = async (event: any, context: Context) => {
854+
const traceID = event.foo[traceIDHeader];
855+
const parentID = event.foo[parentIDHeader];
856+
const sampledHeader = event.foo[samplingPriorityHeader];
857+
const sampleMode = parseInt(sampledHeader, 10);
858+
859+
return {
860+
parentID,
861+
sampleMode,
862+
source: Source.Event,
863+
traceID,
864+
};
865+
};
866+
867+
const result = await extractTraceContext(
868+
{
869+
foo: {
870+
"x-datadog-parent-id": "797643193680388251",
871+
"x-datadog-sampling-priority": "2",
872+
"x-datadog-trace-id": "4110911582297405551",
873+
},
874+
},
875+
{} as Context,
876+
extractor,
877+
);
878+
expect(result).toEqual({
879+
parentID: "797643193680388251",
880+
sampleMode: SampleMode.USER_KEEP,
881+
traceID: "4110911582297405551",
882+
source: Source.Event,
883+
});
884+
});
885+
886+
it("returns trace read from event with a synchronous extractor as the highest priority", async () => {
850887
process.env["_X_AMZN_TRACE_ID"] = "Root=1-5ce31dc2-2c779014b90ce44db5e03875;Parent=0b11cc4230d3e09e;Sampled=1";
851888

852889
const extractor = (event: any, context: Context) => {
@@ -863,7 +900,7 @@ describe("extractTraceContext", () => {
863900
};
864901
};
865902

866-
const result = extractTraceContext(
903+
const result = await extractTraceContext(
867904
{
868905
foo: {
869906
"x-datadog-parent-id": "797643193680388251",
@@ -882,14 +919,14 @@ describe("extractTraceContext", () => {
882919
});
883920
});
884921

885-
it("handles gracefully errors in extractors", () => {
922+
it("handles gracefully errors in extractors", async () => {
886923
process.env["_X_AMZN_TRACE_ID"] = "Root=1-5ce31dc2-2c779014b90ce44db5e03875;Parent=0b11cc4230d3e09e;Sampled=1";
887924

888925
const extractor = (event: any, context: Context) => {
889926
throw new Error("test");
890927
};
891928

892-
const result = extractTraceContext(
929+
const result = await extractTraceContext(
893930
{
894931
foo: {
895932
"x-datadog-parent-id": "797643193680388251",
@@ -907,10 +944,10 @@ describe("extractTraceContext", () => {
907944
source: "xray",
908945
});
909946
});
910-
it("returns trace read from SQS metadata as second highest priority", () => {
947+
it("returns trace read from SQS metadata as second highest priority", async () => {
911948
process.env["_X_AMZN_TRACE_ID"] = "Root=1-5ce31dc2-2c779014b90ce44db5e03875;Parent=0b11cc4230d3e09e;Sampled=1";
912949

913-
const result = extractTraceContext(
950+
const result = await extractTraceContext(
914951
{
915952
Records: [
916953
{
@@ -945,7 +982,7 @@ describe("extractTraceContext", () => {
945982
source: Source.Event,
946983
});
947984
});
948-
it("returns trace read from Lambda Context as third highest priority", () => {
985+
it("returns trace read from Lambda Context as third highest priority", async () => {
949986
process.env["_X_AMZN_TRACE_ID"] = "Root=1-5ce31dc2-2c779014b90ce44db5e03875;Parent=0b11cc4230d3e09e;Sampled=1";
950987
const lambdaContext: Context = {
951988
clientContext: {
@@ -959,7 +996,7 @@ describe("extractTraceContext", () => {
959996
},
960997
},
961998
} as any;
962-
const result = extractTraceContext(
999+
const result = await extractTraceContext(
9631000
{
9641001
Records: [
9651002
{
@@ -993,34 +1030,34 @@ describe("extractTraceContext", () => {
9931030
source: Source.Event,
9941031
});
9951032
});
996-
it("returns trace read from env if no headers present", () => {
1033+
it("returns trace read from env if no headers present", async () => {
9971034
process.env["_X_AMZN_TRACE_ID"] = "Root=1-5ce31dc2-2c779014b90ce44db5e03875;Parent=0b11cc4230d3e09e;Sampled=1";
9981035

999-
const result = extractTraceContext({}, {} as Context);
1036+
const result = await extractTraceContext({}, {} as Context);
10001037
expect(result).toEqual({
10011038
parentID: "797643193680388254",
10021039
sampleMode: SampleMode.USER_KEEP,
10031040
traceID: "4110911582297405557",
10041041
source: "xray",
10051042
});
10061043
});
1007-
it("returns trace read from env if no headers present", () => {
1044+
it("returns trace read from env if no headers present", async () => {
10081045
process.env["_X_AMZN_TRACE_ID"] = "Root=1-5ce31dc2-2c779014b90ce44db5e03875;Parent=0b11cc4230d3e09e;Sampled=1";
10091046

1010-
const result = extractTraceContext({}, {} as Context);
1047+
const result = await extractTraceContext({}, {} as Context);
10111048
expect(result).toEqual({
10121049
parentID: "797643193680388254",
10131050
sampleMode: SampleMode.USER_KEEP,
10141051
traceID: "4110911582297405557",
10151052
source: "xray",
10161053
});
10171054
});
1018-
it("adds datadog metadata segment to xray when trace context is in event", () => {
1055+
it("adds datadog metadata segment to xray when trace context is in event", async () => {
10191056
jest.spyOn(Date, "now").mockImplementation(() => 1487076708000);
10201057
process.env[xrayTraceEnvVar] = "Root=1-5e272390-8c398be037738dc042009320;Parent=94ae789b969f1cc5;Sampled=1";
10211058
process.env[awsXrayDaemonAddressEnvVar] = "localhost:127.0.0.1:2000";
10221059

1023-
const result = extractTraceContext(
1060+
const result = await extractTraceContext(
10241061
{
10251062
headers: {
10261063
"x-datadog-parent-id": "797643193680388251",
@@ -1039,11 +1076,11 @@ describe("extractTraceContext", () => {
10391076
{\\"id\\":\\"11111\\",\\"trace_id\\":\\"1-5e272390-8c398be037738dc042009320\\",\\"parent_id\\":\\"94ae789b969f1cc5\\",\\"name\\":\\"datadog-metadata\\",\\"start_time\\":1487076708,\\"end_time\\":1487076708,\\"type\\":\\"subsegment\\",\\"metadata\\":{\\"datadog\\":{\\"trace\\":{\\"parent-id\\":\\"797643193680388251\\",\\"sampling-priority\\":\\"2\\",\\"trace-id\\":\\"4110911582297405551\\"}}}}"
10401077
`);
10411078
});
1042-
it("skips adding datadog metadata to x-ray when daemon isn't present", () => {
1079+
it("skips adding datadog metadata to x-ray when daemon isn't present", async () => {
10431080
jest.spyOn(Date, "now").mockImplementation(() => 1487076708000);
10441081
process.env[xrayTraceEnvVar] = "Root=1-5e272390-8c398be037738dc042009320;Parent=94ae789b969f1cc5;Sampled=1";
10451082

1046-
const result = extractTraceContext(
1083+
const result = await extractTraceContext(
10471084
{
10481085
headers: {
10491086
"x-datadog-parent-id": "797643193680388251",
@@ -1057,7 +1094,7 @@ describe("extractTraceContext", () => {
10571094
expect(sentSegment).toBeUndefined();
10581095
});
10591096

1060-
it("returns trace read from step functions event with the extractor as the highest priority", () => {
1097+
it("returns trace read from step functions event with the extractor as the highest priority", async () => {
10611098
const stepFunctionEvent = {
10621099
MyInput: "MyValue",
10631100
Execution: {
@@ -1080,7 +1117,7 @@ describe("extractTraceContext", () => {
10801117
},
10811118
};
10821119

1083-
const result = extractTraceContext(stepFunctionEvent, {} as Context, undefined);
1120+
const result = await extractTraceContext(stepFunctionEvent, {} as Context, undefined);
10841121
expect(result).toEqual({
10851122
parentID: "4602916161841036335",
10861123
sampleMode: 1,
@@ -1089,12 +1126,12 @@ describe("extractTraceContext", () => {
10891126
});
10901127
});
10911128

1092-
it("skips adding datadog metadata to x-ray when x-ray trace isn't sampled", () => {
1129+
it("skips adding datadog metadata to x-ray when x-ray trace isn't sampled", async () => {
10931130
jest.spyOn(Date, "now").mockImplementation(() => 1487076708000);
10941131
process.env[xrayTraceEnvVar] = "Root=1-5e272390-8c398be037738dc042009320;Parent=94ae789b969f1cc5;Sampled=0";
10951132
process.env[awsXrayDaemonAddressEnvVar] = "localhost:127.0.0.1:2000";
10961133

1097-
const result = extractTraceContext(
1134+
const result = await extractTraceContext(
10981135
{
10991136
headers: {
11001137
"x-datadog-parent-id": "797643193680388251",
@@ -1108,7 +1145,7 @@ describe("extractTraceContext", () => {
11081145
expect(sentSegment).toBeUndefined();
11091146
});
11101147

1111-
it("adds step function metadata to xray", () => {
1148+
it("adds step function metadata to xray", async () => {
11121149
const stepFunctionEvent = {
11131150
Execution: {
11141151
Id: "arn:aws:states:sa-east-1:425362996713:express:logs-to-traces-sequential:85a9933e-9e11-83dc-6a61-b92367b6c3be:3f7ef5c7-c8b8-4c88-90a1-d54aa7e7e2bf",
@@ -1134,7 +1171,7 @@ describe("extractTraceContext", () => {
11341171
process.env[xrayTraceEnvVar] = "Root=1-5e272390-8c398be037738dc042009320;Parent=94ae789b969f1cc5;Sampled=1";
11351172
process.env[awsXrayDaemonAddressEnvVar] = "localhost:127.0.0.1:2000";
11361173

1137-
extractTraceContext(stepFunctionEvent, {} as Context);
1174+
await extractTraceContext(stepFunctionEvent, {} as Context);
11381175
expect(sentSegment instanceof Buffer).toBeTruthy();
11391176

11401177
expect(closedSocket).toBeTruthy();

src/trace/context.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,17 +102,17 @@ export function deterministicMd5HashToBigIntString(s: string): string {
102102
* Reads the trace context from either an incoming lambda event, or the current xray segment.
103103
* @param event An incoming lambda event. This must have incoming trace headers in order to be read.
104104
*/
105-
export function extractTraceContext(
105+
export async function extractTraceContext(
106106
event: any,
107107
context: Context,
108108
extractor?: TraceExtractor,
109109
decodeAuthorizerContext: boolean = true,
110-
): TraceContext | undefined {
110+
): Promise<TraceContext | undefined> {
111111
let trace;
112112

113113
if (extractor) {
114114
try {
115-
trace = extractor(event, context);
115+
trace = await extractor(event, context);
116116
logDebug(`extracted trace context from the custom extractor`, { trace });
117117
} catch (error) {
118118
if (error instanceof Error) {

src/trace/listener.spec.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ describe("TraceListener", () => {
106106

107107
it("wraps dd-trace span around invocation", async () => {
108108
const listener = new TraceListener(defaultConfig);
109-
listener.onStartInvocation({}, context as any);
109+
await listener.onStartInvocation({}, context as any);
110110
const unwrappedFunc = () => {};
111111
const wrappedFunc = listener.onWrap(unwrappedFunc);
112112
wrappedFunc();
@@ -141,7 +141,7 @@ describe("TraceListener", () => {
141141
"x-datadog-trace-id": "4110911582297405551",
142142
};
143143
mockTraceSource = Source.Event;
144-
listener.onStartInvocation({}, context as any);
144+
await listener.onStartInvocation({}, context as any);
145145
const unwrappedFunc = () => {};
146146
const wrappedFunc = listener.onWrap(unwrappedFunc);
147147
wrappedFunc();
@@ -179,7 +179,7 @@ describe("TraceListener", () => {
179179
};
180180
mockTraceSource = Source.Xray;
181181

182-
listener.onStartInvocation({}, context as any);
182+
await listener.onStartInvocation({}, context as any);
183183
const unwrappedFunc = () => {};
184184
const wrappedFunc = listener.onWrap(unwrappedFunc);
185185
wrappedFunc();
@@ -215,7 +215,7 @@ describe("TraceListener", () => {
215215
};
216216
mockTraceSource = Source.Xray;
217217

218-
listener.onStartInvocation({}, context as any);
218+
await listener.onStartInvocation({}, context as any);
219219
const unwrappedFunc = () => {};
220220
const wrappedFunc = listener.onWrap(unwrappedFunc);
221221
wrappedFunc();
@@ -246,7 +246,7 @@ describe("TraceListener", () => {
246246

247247
it("wraps dd-trace span around invocation, with function alias", async () => {
248248
const listener = new TraceListener(defaultConfig);
249-
listener.onStartInvocation({}, contextWithFunctionAlias as any);
249+
await listener.onStartInvocation({}, contextWithFunctionAlias as any);
250250
const unwrappedFunc = () => {};
251251
const wrappedFunc = listener.onWrap(unwrappedFunc);
252252
wrappedFunc();
@@ -275,7 +275,7 @@ describe("TraceListener", () => {
275275

276276
it("wraps dd-trace span around invocation, with function version", async () => {
277277
const listener = new TraceListener(defaultConfig);
278-
listener.onStartInvocation({}, contextWithFunctionVersion as any);
278+
await listener.onStartInvocation({}, contextWithFunctionVersion as any);
279279
const unwrappedFunc = () => {};
280280
const wrappedFunc = listener.onWrap(unwrappedFunc);
281281
wrappedFunc();

src/trace/listener.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { SpanContext, TraceOptions, TracerWrapper } from "./tracer-wrapper";
2020
import { SpanInferrer } from "./span-inferrer";
2121
import { SpanWrapper } from "./span-wrapper";
2222
import { getTraceTree } from "../runtime/index";
23-
export type TraceExtractor = (event: any, context: Context) => TraceContext;
23+
export type TraceExtractor = (event: any, context: Context) => Promise<TraceContext> | TraceContext;
2424

2525
export interface TraceConfig {
2626
/**
@@ -88,7 +88,7 @@ export class TraceListener {
8888
this.inferrer = new SpanInferrer(this.tracerWrapper);
8989
}
9090

91-
public onStartInvocation(event: any, context: Context) {
91+
public async onStartInvocation(event: any, context: Context) {
9292
const tracerInitialized = this.tracerWrapper.isTracerAvailable;
9393
if (this.config.injectLogContext) {
9494
patchConsole(console, this.contextService);
@@ -104,7 +104,7 @@ export class TraceListener {
104104
} else {
105105
logDebug("Not patching HTTP libraries", { autoPatchHTTP: this.config.autoPatchHTTP, tracerInitialized });
106106
}
107-
const rootTraceHeaders = this.contextService.extractHeadersFromContext(
107+
const rootTraceHeaders = await this.contextService.extractHeadersFromContext(
108108
event,
109109
context,
110110
this.config.traceExtractor,

src/trace/trace-context-service.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@ export class TraceContextService {
2121

2222
constructor(private tracerWrapper: TracerWrapper) {}
2323

24-
extractHeadersFromContext(
24+
async extractHeadersFromContext(
2525
event: any,
2626
context: Context,
2727
extractor?: TraceExtractor,
2828
decodeAuthorizerContext: boolean = true,
29-
): Partial<TraceHeaders> | undefined {
30-
this.rootTraceContext = extractTraceContext(event, context, extractor, decodeAuthorizerContext);
29+
): Promise<Partial<TraceHeaders> | undefined> {
30+
this.rootTraceContext = await extractTraceContext(event, context, extractor, decodeAuthorizerContext);
3131
return this.currentTraceHeaders;
3232
}
3333

0 commit comments

Comments
 (0)