Skip to content
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

Fetch #590

Merged
merged 30 commits into from
Mar 9, 2024
Merged

Fetch #590

Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
2f3efc8
Add support for including sql query in sql subsegment for MySQL
Jan 26, 2023
a8228e5
Update createSqlData to accept values and sql as arguments, and the c…
Jan 28, 2023
08ad919
Add function comments to mysqL_p.createSqlData
Feb 21, 2023
9c1758e
Merge remote-tracking branch 'upstream/master'
Apr 4, 2023
3f53f08
Merge remote-tracking branch 'upstream/master'
Apr 6, 2023
9855c14
Working Node 14 and 18 tests for fetch
Apr 6, 2023
ef5108a
Auto/manual fetch support
Apr 20, 2023
02248f3
Add typescript defs for fetch, insure node-fetch tests run in NodeJS 14
May 9, 2023
1b96361
Merge remote-tracking branch 'upstream/master' into fetch
May 29, 2023
8ecfd6f
Moved fetch patcher to sdk_contrib
May 30, 2023
2b42c8f
Added integration testing for captureFetch
May 30, 2023
925ea85
Revert tests to Javascript
May 30, 2023
9de92ee
Add chai-as-promised to base package.json dev dependencies, because y…
May 30, 2023
5d0c457
Try fixing package*.json
May 30, 2023
4097c51
Merge remote-tracking branch 'upstream/master' into fetch
Nov 23, 2023
8a1eb78
Change var to const
Dec 18, 2023
49951cb
Removed captureFetch per @jj22ee
Dec 18, 2023
675921f
Removed chai-as-promised and tsconfig.debug.json
Dec 19, 2023
92ea416
wip
Jan 23, 2024
941fc47
wip
Jan 23, 2024
002defc
remove docker files used for diag
Jan 23, 2024
dfbef99
Updates per jj22ee
Jan 23, 2024
6d3644a
Housekeeping
Jan 23, 2024
d2117b0
More housekeeping
Jan 23, 2024
b23bf44
Fix typescript type test
Jan 23, 2024
32651cb
Merge branch 'master' into fetch-pr
jj22ee Feb 26, 2024
9fb28d2
update version and package-lock, fix tests
jj22ee Feb 26, 2024
d7713c3
remove duplicate code
jj22ee Feb 26, 2024
f5f6fdc
Record fetch info in subsegment http property
Mar 8, 2024
dd7f457
Add type def file for subsegment addFetchRequestData method; if url o…
Mar 9, 2024
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
16,754 changes: 10,367 additions & 6,387 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"aws-xray-sdk-core": "3.5.0",
"aws-xray-sdk-express": "3.5.0",
"chai": "^4.2.0",
"chai-as-promised": "^7.1.1",
Copy link
Contributor

Choose a reason for hiding this comment

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

Following up to the comment on moving to sdk_contrib, the dependencies that are unique to this instrumentation would then be placed in the package.json of the new instrumentation package in sdk_contrib.
You can reference this fastify instrumentation PR as a guide to help

"cls-hooked": "^4.2.2",
"codecov": "^3.8.3",
"eslint": "^7.5.0",
Expand Down Expand Up @@ -48,6 +49,7 @@
"rewire": "^4.0.1",
"sinon": "^9.0.2",
"sinon-chai": "^3.5.0",
"ts-node": "^10.9.1",
"tsd": "^0.25.0",
"typescript": "^4.4.4",
"upath": "^1.2.0"
Expand Down
2 changes: 2 additions & 0 deletions packages/core/lib/aws-xray.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ export { captureAWSClient as captureAWSv3Client } from './patchers/aws3_p';

export { captureHTTPs, captureHTTPsGlobal } from './patchers/http_p';

export { captureFetch } from './patchers/fetch_p';

export { capturePromise } from './patchers/promise_p';

export { utils };
Expand Down
13 changes: 13 additions & 0 deletions packages/core/lib/patchers/fetch_p.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import * as fetch from 'node-fetch';
import { Subsegment } from '../aws-xray';

// Need to have this here to get "clean" response from captureFetch since
// type file for node-fetch export both namespace and fetch function as default
export declare function fetchFunction(
url: fetch.RequestInfo,
init: fetch.RequestInit | undefined
): Promise<fetch.Response>;

type fetchSubsegmentCallback = (subsegment: Subsegment, req: fetch.Request, res: fetch.Response | null, error: Error) => void

export function captureFetch(downstreamXRayEnabled?: boolean, subsegmentCallback?: fetchSubsegmentCallback): typeof fetchFunction;
170 changes: 170 additions & 0 deletions packages/core/lib/patchers/fetch_p.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
/**
* @module fetch_p
*/

/**
* This module patches the global fetch instance for NodeJS 18+
*/

var contextUtils = require('../context_utils');
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: use const instead of var

var Utils = require('../utils');

var logger = require('../logger');

/**
* Wrap fetch either in global instance for recent NodeJS or the node-fetch module for older versions,
* to automatically capture information for the segment.
* This patches the built-in fetch function globally.
* @param {boolean} downstreamXRayEnabled - when true, adds a "traced:true" property to the subsegment
* so the AWS X-Ray service expects a corresponding segment from the downstream service.
* @param {function} subsegmentCallback - a callback that is called with the subsegment, the fetch request,
* the fetch response and any error issued, allowing custom annotations and metadata to be added.
* @alias module:fetch_p.captureFetch
*/
function captureFetch(downstreamXRayEnabled, subsegmentCallback) {
if ('fetch' in globalThis) {
if (!globalThis.__fetch) {
globalThis.__fetch = globalThis.fetch;
globalThis.fetch = exports._fetchEnableCapture(globalThis.__fetch, globalThis.Request,
downstreamXRayEnabled, subsegmentCallback);
}
return globalThis.fetch;
}
return exports.captureFetchModule(require('node-fetch'), downstreamXRayEnabled, subsegmentCallback);
}

/**
* Wrap fetch module to capture information for the segment.
* This patches the fetch function distributed in node-fetch package.
* @param {fetch} module - The fetch module
* @param {boolean} downstreamXRayEnabled - when true, adds a "traced:true" property to the subsegment
* so the AWS X-Ray service expects a corresponding segment from the downstream service.
* @param {function} subsegmentCallback - a callback that is called with the subsegment, the Node.js
* http.ClientRequest, the Node.js http.IncomingMessage (if a response was received) and any error issued,
* allowing custom annotations and metadata to be added.
* to be added to the subsegment.
* @alias module:fetch_p.captureFetchModule
*/
function captureFetchModule(module, downstreamXRayEnabled, subsegmentCallback) {
if (!module.default) {
logger.getLogger().warn('X-ray capture did not find fetch function in module');
return null;
}
if (!module.__fetch) {
module.__fetch = module.default;
module.default = exports._fetchEnableCapture(module.__fetch, module.Request,
downstreamXRayEnabled, subsegmentCallback);
}
return module.default;
}

const enableCapture = function enableCapture(baseFetchFunction, requestClass, downstreamXRayEnabled, subsegmentCallback) {
// fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;

var overridenFetchAsync = async (...args) => {
this.downstreamXRayEnabled = !!downstreamXRayEnabled;
this.subsegmentCallback = subsegmentCallback;
// Standardize request information
const request = typeof args[0] === 'object' ?
args[0] :
new requestClass(...args);

// Facilitate the addition of Segment information via the request arguments
const params = args.length > 1 ? args[1] : {};

// Short circuit if the HTTP is already being captured
if (request.headers.has('X-Amzn-Trace-Id')) {

return await baseFetchFunction(...args);
}

const url = new URL(request.url);
const isAutomaticMode = contextUtils.isAutomaticMode();

const parent = contextUtils.resolveSegment(contextUtils.resolveManualSegmentParams(params));
const hostname = url.hostname || url.host || 'Unknown host';

if (!parent) {
let output = '[ host: ' + hostname +
(request.method ? (', method: ' + request.method) : '') +
', path: ' + url.pathname + ' ]';

if (isAutomaticMode) {
logger.getLogger().info('RequestInit for request ' + output +
' is missing the sub/segment context for automatic mode. Ignoring.');
} else {
logger.getLogger().info('RequestInit for request ' + output +
' requires a segment object on the options params as "XRaySegment" for tracing in manual mode. Ignoring.');
}

// Options are not modified, only parsed for logging. We can pass in the original arguments.
return await baseFetchFunction(...args);
}

let subsegment;
if (parent.notTraced) {
subsegment = parent.addNewSubsegmentWithoutSampling(hostname);
} else {
subsegment = parent.addNewSubsegment(hostname);
}

subsegment.namespace = 'remote';

request.headers.set('X-Amzn-Trace-Id',
'Root=' + (parent.segment ? parent.segment : parent).trace_id +
';Parent=' + subsegment.id +
';Sampled=' + (subsegment.notTraced ? '0' : '1'));

// Set up fetch call and capture any thrown errors
const capturedFetch = async () => {
const requestClone = request.clone();
let response;
try {
response = await baseFetchFunction(requestClone);

if (this.subsegmentCallback) {
this.subsegmentCallback(subsegment, requestClone, response);
}

const statusCode = response.status;
if (statusCode === 429) {
subsegment.addThrottleFlag();
}

const cause = Utils.getCauseTypeFromHttpStatus(statusCode);
if (cause) {
subsegment[cause] = true;
}

subsegment.addRemoteRequestData(requestClone, response, this.downstreamXRayEnabled);
subsegment.close();
return response;
} catch (e) {
if (this.subsegmentCallback) {
this.subsegmentCallback(subsegment, requestClone, response, e);
}
const madeItToDownstream = (e.code !== 'ECONNREFUSED');
subsegment.addErrorFlag();
subsegment.addRemoteRequestData(requestClone, response, madeItToDownstream && this.downstreamXRayEnabled);
subsegment.close(e);
throw (e);
}
};

if (isAutomaticMode) {
const session = contextUtils.getNamespace();
return await session.runPromise(async () => {
contextUtils.setSegment(subsegment);
return await capturedFetch();
});
} else {
return await capturedFetch();
}
};

return overridenFetchAsync;
};

module.exports.captureFetch = captureFetch;
module.exports.captureFetchModule = captureFetchModule;
module.exports._fetchEnableCapture = enableCapture;
Loading