Skip to content

Commit

Permalink
fix(tracer): forward X-Amzn-Trace-Id header when instrumenting fetch (
Browse files Browse the repository at this point in the history
  • Loading branch information
dreamorosi authored Jan 14, 2025
1 parent e154e58 commit 4eb3e2d
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 13 deletions.
15 changes: 5 additions & 10 deletions packages/tracer/src/Tracer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ import type {
import type { Handler } from 'aws-lambda';
import type { Segment, Subsegment } from 'aws-xray-sdk-core';
import xraySdk from 'aws-xray-sdk-core';
import { EnvironmentVariablesService } from './config/EnvironmentVariablesService.js';
import {
type EnvironmentVariablesService,
environmentVariablesService,
} from './config/EnvironmentVariablesService.js';
import { ProviderService } from './provider/ProviderService.js';
import type { ConfigServiceInterface } from './types/ConfigServiceInterface.js';
import type { ProviderServiceInterface } from './types/ProviderService.js';
Expand Down Expand Up @@ -861,14 +864,6 @@ class Tracer extends Utility implements TracerInterface {
: undefined;
}

/**
* Set and initialize `envVarsService`.
* Used internally during initialization.
*/
private setEnvVarsService(): void {
this.envVarsService = new EnvironmentVariablesService();
}

/**
* Method that reconciles the configuration passed with the environment variables.
* Used internally during initialization.
Expand All @@ -879,7 +874,7 @@ class Tracer extends Utility implements TracerInterface {
const { enabled, serviceName, captureHTTPsRequests, customConfigService } =
options;

this.setEnvVarsService();
this.envVarsService = environmentVariablesService;
this.setCustomConfigService(customConfigService);
this.setTracingEnabled(enabled);
this.setCaptureResponse();
Expand Down
4 changes: 3 additions & 1 deletion packages/tracer/src/config/EnvironmentVariablesService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,6 @@ class EnvironmentVariablesService
}
}

export { EnvironmentVariablesService };
const environmentVariablesService = new EnvironmentVariablesService();

export { EnvironmentVariablesService, environmentVariablesService };
8 changes: 8 additions & 0 deletions packages/tracer/src/provider/ProviderService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import http from 'node:http';
import https from 'node:https';
import { addUserAgentMiddleware } from '@aws-lambda-powertools/commons';
import type { DiagnosticsChannel } from 'undici-types';
import { environmentVariablesService } from '../config/EnvironmentVariablesService.js';
import {
findHeaderAndDecode,
getRequestURL,
Expand Down Expand Up @@ -127,6 +128,13 @@ class ProviderService implements ProviderServiceInterface {
);
subsegment.addAttribute('namespace', 'remote');

// addHeader is not part of the type definition but it's available https://github.com/nodejs/undici/blob/main/docs/docs/api/DiagnosticsChannel.md#undicirequestcreate
// @ts-expect-error
request.addHeader(
'X-Amzn-Trace-Id',
`Root=${environmentVariablesService.getXrayTraceId()};Parent=${subsegment.id};Sampled=${subsegment.notTraced ? '0' : '1'}`
);

(subsegment as HttpSubsegment).http = {
request: {
url: `${requestURL.protocol}//${requestURL.hostname}${requestURL.pathname}`,
Expand Down
6 changes: 5 additions & 1 deletion packages/tracer/tests/helpers/mockRequests.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { channel } from 'node:diagnostics_channel';
import type { URL } from 'node:url';
import { vi } from 'vitest';

type MockFetchOptions = {
origin?: string | URL;
Expand Down Expand Up @@ -31,7 +32,7 @@ const mockFetch = ({
statusCode,
headers,
throwError,
}: MockFetchOptions): void => {
}: MockFetchOptions) => {
const requestCreateChannel = channel('undici:request:create');
const responseHeadersChannel = channel('undici:request:headers');
const errorChannel = channel('undici:request:error');
Expand All @@ -40,6 +41,7 @@ const mockFetch = ({
origin,
method: method ?? 'GET',
path,
addHeader: vi.fn(),
};

requestCreateChannel.publish({
Expand Down Expand Up @@ -70,6 +72,8 @@ const mockFetch = ({
headers: encodedHeaders,
},
});

return request;
};

export { mockFetch };
43 changes: 42 additions & 1 deletion packages/tracer/tests/unit/ProviderService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ describe('Class: ProviderService', () => {

// Act
provider.instrumentFetch();
mockFetch({
const mockRequest = mockFetch({
origin: 'https://aws.amazon.com',
path: '/blogs',
headers: {
Expand All @@ -387,6 +387,12 @@ describe('Class: ProviderService', () => {
});
expect(subsegment.close).toHaveBeenCalledTimes(1);
expect(provider.setSegment).toHaveBeenLastCalledWith(segment);
expect(mockRequest.addHeader).toHaveBeenLastCalledWith(
'X-Amzn-Trace-Id',
expect.stringMatching(
/Root=1-abcdef12-3456abcdef123456abcdef12;Parent=\S{16};Sampled=1/
)
);
});

it('excludes the content_length header when invalid or not found', async () => {
Expand Down Expand Up @@ -616,4 +622,39 @@ describe('Class: ProviderService', () => {
expect(subsegment.close).toHaveBeenCalledTimes(1);
expect(provider.setSegment).toHaveBeenLastCalledWith(segment);
});

it('forwards the correct sampling decision in the request header', async () => {
// Prepare
const provider: ProviderService = new ProviderService();
const segment = new Subsegment('## dummySegment');
const subsegment = segment.addNewSubsegment('aws.amazon.com');
subsegment.notTraced = true;
vi.spyOn(segment, 'addNewSubsegment').mockImplementationOnce(
() => subsegment
);
vi.spyOn(provider, 'getSegment')
.mockImplementationOnce(() => segment)
.mockImplementationOnce(() => subsegment)
.mockImplementationOnce(() => subsegment);
vi.spyOn(subsegment, 'close');
vi.spyOn(provider, 'setSegment');

// Act
provider.instrumentFetch();
const mockRequest = mockFetch({
origin: 'https://aws.amazon.com',
path: '/blogs',
headers: {
'content-length': '100',
},
});

// Assess
expect(mockRequest.addHeader).toHaveBeenLastCalledWith(
'X-Amzn-Trace-Id',
expect.stringMatching(
/Root=1-abcdef12-3456abcdef123456abcdef12;Parent=\S{16};Sampled=0/
)
);
});
});

0 comments on commit 4eb3e2d

Please sign in to comment.