Skip to content

Commit f05c600

Browse files
duncanistaastuyve
andauthored
[w3c] Support W3C trace context propagation (#429)
* refactor `trace-context-service.ts` aiming to enhance its use, created for xray trace context handling, now we expect it to use it for all of the tracing context * add `span-context-wrapper.ts` simple wrapper to use instead of `TraceContext`, this allows us to type the object extracted by the tracer. I decided to wrap it into a class, to easily managea black-box class – yes, I know we are importing it in one of the constructors, but that is the easiest way to create one for trace context with Xray, StepFunction event, and for Custom Extractors, so we can tell the tracer what is a child of what. * update `tracer-wrapper.ts` make it complaint to use the refactored classes, also updated its unit tests to mock the new methods being used * add `step-function-service.ts` the main point of this class is to create a singleton state manager for the `StepFunctionContext` interface, it will run per invocation, and should not interfere with new invocations * add `xray-service.ts` this new class handles all logic related to Xray, previously, it was scattered in `context.ts`, but it actually didnt make sense, since we had multiple functions which should be in the same place, we could also keep a state since we are relying on a header and context which will always be deterministic, so no need to calculate it again in multiple functions * add `extractor.ts` this file will be in charge of extracting the trace context from the incoming context, event, step function context, or xray, separating the logic into an adapter pattern for a much simpler and easier way of handling this extraction * add `http.ts` extractor refactoring code to make sure we correctly handling an HTTP event for trace context * add `custom.ts` extractor allow custom extractor to be in a separate class, which then uses a helper method to generate an `SpanContextWrapper` to be returned, had to deprecate some fields, so to avoid breaking changes, I have marked some fields as nullable * add `app-sync.ts` extractor basically relies on `http.ts` to work * add `sns.ts` extractor same as other extractors, separation of concerns into its own file * add `sqs.ts` extractor same as other extractors, separation of concerns into its own file * add `kinesis.ts` extractor same as other extractors, separation of concerns into its own file * add `sns-sqs.ts` extractor same as other extractors, separation of concerns into its own file, I think we could do some more refactoring, but for now, I will leave sns-sqs and eventbridge-sqs as separate files * add `event-bridge.ts` extractor same as other extractors, separation of concerns into its own file * add `event-bridge-sqs.ts` extractor same as other extractors, separation of concerns into its own file, related to sns-sqs comment, maybe in the future we could explore refactoring this extraction, but for now, will stay as is * add `lambda-context.ts` extractor same as other extractors, separation of concerns into its own file, also made sure legacy payloads still work * add `step-function.ts` extractor same as other extractors, separation of concerns into its own file, uses the singleton class to get context * add `index.ts` for extractors basically to export them from the root * refactor main `index.ts` updated unit tests to make sure everything works as before with the new methods and mockings * refactor `patch-http.ts` w3c will not be supported here, since we would have to do manual extraction and injection, this will not be supported for customers not using `dd-trace`, so the refactoring is just to use the new methods and mockings * refactor `patch-console.ts` w3c will not be supported here, since we would have to do manual extraction and injection, this will not be supported for customers not using `dd-trace`, so the refactoring is just to use the new methods and mockings, if a new user wants to correlate traces with logs without `dd-trace`, they will have to use the datadog headers, this might be up for removal in the future * refactor `listener.ts` make use of the new services, also refactored unit tests to work as expected * add `reset` method to `step-function-service.ts` might need to re-explore this method, but for safety, I am adding it just to make sure we are indeed creating a new instance of the service on every invocation * update `index.ts` in trace export from the right file * add `event-validator.ts` this class aims to replace `event-type-guards.ts`, I think the file name is confusing, and we should use a class as a namespace here, nonetheless, I wonder if this change is necessary at all, since it would mostly use the same exact logic, but allowing it to be exported as a single class with static methods, this is exactly the same as what we do when importing the whole file. * remove `context.ts` decomissioning a large file and its unit tests, made sure to refactor every single method into multiple files and classes * lint * format * when tracer is not present, mock an object which allows us to keep using the `SpanContextWrapper` class as usual * remove state for `XrayService`, sadly, for some reason we have to generate the context everytime, I tried to remove this by having the `header` and `context` as properties, but somehow, it ends up being `undefined`, therefore the switch back * get `spanContext` for parenting only when tracer is available * minor changes to unit test so they can pass * update function signature for `toString` in `SpanContextWrapper` * lint * fix unit tests imports * rename `TraceHeaders` to `DatadogTraceHeaders` * feat: Fix export of TraceHeaders, add note about deprecation. Fix relative import * fix: s/breaking/major/ * feat: lint --------- Co-authored-by: AJ Stuyvenberg <[email protected]>
1 parent 9df2686 commit f05c600

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+4239
-1961
lines changed

src/index.spec.ts

+74-9
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,14 @@ import http from "http";
22
import nock from "nock";
33

44
import { Context, Handler } from "aws-lambda";
5-
import {
6-
datadog,
7-
getTraceHeaders,
8-
sendDistributionMetric,
9-
TraceHeaders,
10-
sendDistributionMetricWithDate,
11-
} from "./index";
5+
import { datadog, getTraceHeaders, sendDistributionMetric, sendDistributionMetricWithDate } from "./index";
126
import { incrementErrorsMetric, incrementInvocationsMetric } from "./metrics/enhanced-metrics";
137
import { LogLevel, setLogLevel } from "./utils";
148
import { HANDLER_STREAMING, STREAM_RESPONSE } from "./constants";
159
import { PassThrough } from "stream";
10+
import { DatadogTraceHeaders } from "./trace/context/extractor";
11+
import { SpanContextWrapper } from "./trace/span-context-wrapper";
12+
import { TraceSource } from "./trace/trace-context-service";
1613

1714
jest.mock("./metrics/enhanced-metrics");
1815

@@ -28,6 +25,34 @@ const mockContext = {
2825
// typeof OriginalListenerModule.MetricsListener
2926
// >;
3027

28+
let mockSpanContextWrapper: any;
29+
let mockSpanContext: any;
30+
let mockTraceHeaders: Record<string, string> | undefined = undefined;
31+
let mockTraceSource: TraceSource | undefined = undefined;
32+
33+
jest.mock("./trace/trace-context-service", () => {
34+
class MockTraceContextService {
35+
extract(event: any, context: Context): SpanContextWrapper {
36+
return mockSpanContextWrapper;
37+
}
38+
39+
get traceSource() {
40+
return mockTraceSource;
41+
}
42+
get currentTraceContext() {
43+
return mockSpanContextWrapper;
44+
}
45+
46+
get currentTraceHeaders() {
47+
return mockTraceHeaders;
48+
}
49+
}
50+
return {
51+
...jest.requireActual("./trace/trace-context-service"),
52+
TraceContextService: MockTraceContextService,
53+
};
54+
});
55+
3156
describe("datadog", () => {
3257
let traceId: string | undefined;
3358
let parentId: string | undefined;
@@ -43,6 +68,9 @@ describe("datadog", () => {
4368
callback(null, "Result");
4469
};
4570
beforeEach(() => {
71+
mockTraceHeaders = undefined;
72+
mockSpanContext = undefined;
73+
mockSpanContextWrapper = undefined;
4674
traceId = undefined;
4775
parentId = undefined;
4876
sampled = undefined;
@@ -60,6 +88,12 @@ describe("datadog", () => {
6088

6189
it("patches http request when autoPatch enabled", async () => {
6290
nock("http://www.example.com").get("/").reply(200, {});
91+
mockTraceHeaders = {
92+
"x-datadog-parent-id": "9101112",
93+
"x-datadog-sampling-priority": "2",
94+
"x-datadog-trace-id": "123456",
95+
};
96+
6397
const wrapped = datadog(handler, { forceWrap: true });
6498
await wrapped(
6599
{
@@ -189,7 +223,12 @@ describe("datadog", () => {
189223
});
190224

191225
it("makes the current trace headers available", async () => {
192-
let traceHeaders: Partial<TraceHeaders> = {};
226+
mockTraceHeaders = {
227+
"x-datadog-parent-id": "9101112",
228+
"x-datadog-sampling-priority": "2",
229+
"x-datadog-trace-id": "123456",
230+
};
231+
let traceHeaders: Partial<DatadogTraceHeaders> = {};
193232
const event = {
194233
headers: {
195234
"x-datadog-parent-id": "9101112",
@@ -214,6 +253,19 @@ describe("datadog", () => {
214253
});
215254

216255
it("injects context into console.log messages", async () => {
256+
mockSpanContext = {
257+
toTraceId: () => "123456",
258+
toSpanId: () => "9101112",
259+
_sampling: {
260+
priority: "2",
261+
},
262+
};
263+
mockSpanContextWrapper = {
264+
spanContext: mockSpanContext,
265+
toTraceId: () => mockSpanContext.toTraceId(),
266+
toSpanId: () => mockSpanContext.toSpanId(),
267+
};
268+
217269
const event = {
218270
headers: {
219271
"x-datadog-parent-id": "9101112",
@@ -238,6 +290,19 @@ describe("datadog", () => {
238290
it("injects context into console.log messages with env var", async () => {
239291
process.env.DD_LOGS_INJECTION = "true";
240292

293+
mockSpanContext = {
294+
toTraceId: () => "123456",
295+
toSpanId: () => "9101112",
296+
_sampling: {
297+
priority: "2",
298+
},
299+
};
300+
mockSpanContextWrapper = {
301+
spanContext: mockSpanContext,
302+
toTraceId: () => mockSpanContext.toTraceId(),
303+
toSpanId: () => mockSpanContext.toSpanId(),
304+
};
305+
241306
const event = {
242307
headers: {
243308
"x-datadog-parent-id": "9101112",
@@ -333,7 +398,7 @@ describe("datadog", () => {
333398

334399
expect(mockedIncrementInvocations).toBeCalledTimes(1);
335400
expect(mockedIncrementInvocations).toBeCalledWith(expect.anything(), mockContext);
336-
expect(logger.debug).toHaveBeenCalledTimes(11);
401+
expect(logger.debug).toHaveBeenCalledTimes(8);
337402
expect(logger.debug).toHaveBeenLastCalledWith('{"status":"debug","message":"datadog:Unpatching HTTP libraries"}');
338403
});
339404

src/index.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
MetricsConfig,
88
MetricsListener,
99
} from "./metrics";
10-
import { TraceConfig, TraceHeaders, TraceListener } from "./trace";
10+
import { TraceConfig, TraceListener } from "./trace";
1111
import { subscribeToDC } from "./runtime";
1212
import {
1313
logDebug,
@@ -20,9 +20,10 @@ import {
2020
setLogLevel,
2121
} from "./utils";
2222
import { getEnhancedMetricTags } from "./metrics/enhanced-metrics";
23+
import { DatadogTraceHeaders } from "./trace/context/extractor";
2324

24-
export { TraceHeaders } from "./trace";
25-
25+
// Backwards-compatible export, TODO deprecate in next major
26+
export { DatadogTraceHeaders as TraceHeaders } from "./trace/context/extractor";
2627
export const apiKeyEnvVar = "DD_API_KEY";
2728
export const apiKeyKMSEnvVar = "DD_KMS_API_KEY";
2829
export const captureLambdaPayloadEnvVar = "DD_CAPTURE_LAMBDA_PAYLOAD";
@@ -278,7 +279,7 @@ export function sendDistributionMetric(name: string, value: number, ...tags: str
278279
/**
279280
* Retrieves the Datadog headers for the current trace.
280281
*/
281-
export function getTraceHeaders(): Partial<TraceHeaders> {
282+
export function getTraceHeaders(): Partial<DatadogTraceHeaders> {
282283
if (currentTraceListener === undefined) {
283284
return {};
284285
}

0 commit comments

Comments
 (0)