Skip to content

[bug-fix] Make it possible to grant xray traces with an http request in Nuxt3 #616

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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
27 changes: 25 additions & 2 deletions app/http_fetch_event.html
Original file line number Diff line number Diff line change
@@ -5,8 +5,18 @@

<script>
window.fetch = function (request, init) {
document.getElementById('fetchRequestHeaders').innerText =
init.headers['X-Amzn-Trace-Id'];
// Display the trace header for debugging
if (init && init.headers) {
if (init.headers instanceof Headers) {
document.getElementById(
'fetchRequestHeaders'
).innerText = init.headers.get('X-Amzn-Trace-Id');
} else {
document.getElementById(
'fetchRequestHeaders'
).innerText = init.headers['X-Amzn-Trace-Id'];
}
}
return Promise.resolve({
status: 200,
headers: new Headers({ 'Content-Length': 125 }),
@@ -43,6 +53,12 @@
fetch('https://aws.amazon.com');
}

// Headers object has a set method by default
function sendFetchRequestWithHeadersObject() {
const headers = new Headers();
fetch('https://aws.amazon.com', { headers });
}

function sendDataPlaneRequest() {
fetch(
'https://dataplane.rum.us-west-2.amazonaws.com/appmonitors/abc123/'
@@ -89,6 +105,13 @@
<button id="clearRequestResponse" onclick="clearRequestResponse()">
Clear
</button>
<!-- Button for testing headers with set method -->
<button
id="sendFetchRequestWithHeadersObject"
onclick="sendFetchRequestWithHeadersObject()"
>
Send Fetch Request with Headers Object
</button>
<hr />
<span id="request"></span>
<span id="response"></span>
39 changes: 39 additions & 0 deletions src/__smoke-test__/dataplane-integ.spec.ts
Original file line number Diff line number Diff line change
@@ -70,3 +70,42 @@ test('when web client calls PutRumEvents then the payload contains all events',
expect(navigation.length).toEqual(NAVIGATION_COUNT);
expect(resource.length).toEqual(RESOURCE_EVENT_COUNT);
});

test('when web client is used in Nuxt3 environment then X-Ray trace ID is added to HTTP requests', async ({
page
}) => {
// Mock Nuxt3 environment
await page.addInitScript(() => {
// Override fetch to simulate Nuxt3's ofetch behavior
const originalFetch = window.fetch;
window.fetch = function (input, init) {
if (!init) {
init = {};
}
if (!init.headers) {
// Create Headers object with set method
init.headers = new Headers();
}
return originalFetch(input, init);
};
});

// Open page
await page.goto(TEST_URL);

// Trigger a fetch request
await page.evaluate(() => {
fetch('/api/test');
});

// Verify that the X-Ray trace ID header was added
const request = await page.waitForRequest(
(request) =>
request.url().includes('/api/test') &&
request.headers()['x-amzn-trace-id'] !== undefined
);

expect(request.headers()['x-amzn-trace-id']).toMatch(
/Root=1-[0-9a-f]{8}-[0-9a-f]{24};Parent=[0-9a-f]{16};Sampled=1/
);
});
29 changes: 29 additions & 0 deletions src/plugins/event-plugins/__integ__/FetchPlugin.test.ts
Original file line number Diff line number Diff line change
@@ -3,6 +3,9 @@ import { REQUEST_BODY } from '../../../test-utils/integ-test-utils';
import { XRAY_TRACE_EVENT_TYPE, HTTP_EVENT_TYPE } from '../../utils/constant';

const sendFetchRequest: Selector = Selector(`#sendFetchRequest`);
const sendFetchRequestWithHeadersObject: Selector = Selector(
`#sendFetchRequestWithHeadersObject`
);
const sendDataPlaneRequest: Selector = Selector(`#sendDataPlaneRequest`);
const dispatch: Selector = Selector(`#dispatch`);
const clearRequestResponse: Selector = Selector(`#clearRequestResponse`);
@@ -65,3 +68,29 @@ test('when fetch is called then an http event is recorded', async (t: TestContro
.expect(eventDetails.response.status)
.eql(200);
});

test('when fetch is called with headers that have set method then trace header is added', async (t: TestController) => {
// This test simulates the environment where headers have a set method
await t
.wait(300)
.click(dispatch)
.expect(REQUEST_BODY.textContent)
.contains('BatchId')
.click(clearRequestResponse)
.click(sendFetchRequestWithHeadersObject)
.expect(fetchRequestHeaders.textContent)
.match(/Root=1-[0-9a-f]{8}-[0-9a-f]{24};Parent=[0-9a-f]{16};Sampled=1/)
.click(dispatch)
.expect(REQUEST_BODY.textContent)
.contains('BatchId');

const json = JSON.parse(await REQUEST_BODY.textContent);
const eventType = json.RumEvents[0].type;
const eventDetails = JSON.parse(json.RumEvents[0].details);

await t
.expect(eventType)
.eql(XRAY_TRACE_EVENT_TYPE)
.expect(eventDetails.name)
.eql('sample.rum.aws.amazon.com');
});
44 changes: 43 additions & 1 deletion src/plugins/utils/__tests__/http-utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { X_AMZN_TRACE_ID, getTraceHeader } from '../http-utils';
import {
X_AMZN_TRACE_ID,
getTraceHeader,
addAmznTraceIdHeaderToInit,
getAmznTraceIdHeaderValue
} from '../http-utils';

const Request = function (input: RequestInfo, init?: RequestInit) {
if (typeof input === 'string') {
@@ -50,4 +55,41 @@ describe('http-utils', () => {
expect(traceHeader.traceId).toEqual(undefined);
expect(traceHeader.segmentId).toEqual(undefined);
});

test('when headers object has set method then trace header is added using set method', async () => {
const traceId = '1-0-000000000000000000000001';
const segmentId = '0000000000000001';

const headersWithSetMethod = {
set: jest.fn()
};

const init: RequestInit = {
headers: headersWithSetMethod as any
};

addAmznTraceIdHeaderToInit(init, traceId, segmentId);

expect(headersWithSetMethod.set).toHaveBeenCalledWith(
X_AMZN_TRACE_ID,
getAmznTraceIdHeaderValue(traceId, segmentId)
);
});

test('when headers object does not have set method then trace header is added as property', async () => {
const traceId = '1-0-000000000000000000000001';
const segmentId = '0000000000000001';

const headersWithoutSetMethod = {} as any;

const init: RequestInit = {
headers: headersWithoutSetMethod
};

addAmznTraceIdHeaderToInit(init, traceId, segmentId);

expect(headersWithoutSetMethod[X_AMZN_TRACE_ID]).toEqual(
getAmznTraceIdHeaderValue(traceId, segmentId)
);
});
});
15 changes: 11 additions & 4 deletions src/plugins/utils/http-utils.ts
Original file line number Diff line number Diff line change
@@ -168,10 +168,17 @@ export const addAmznTraceIdHeaderToInit = (
if (!init.headers) {
init.headers = {};
}
(init.headers as any)[X_AMZN_TRACE_ID] = getAmznTraceIdHeaderValue(
traceId,
segmentId
);
if ((init.headers as any).set) {
(init.headers as any).set(
X_AMZN_TRACE_ID,
getAmznTraceIdHeaderValue(traceId, segmentId)
);
} else {
(init.headers as any)[X_AMZN_TRACE_ID] = getAmznTraceIdHeaderValue(
traceId,
segmentId
);
}
};

export const addAmznTraceIdHeaderToHeaders = (