UNPKG

@aws-lambda-powertools/tracer

Version:
198 lines (197 loc) 8.72 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ProviderService = void 0; const aws_xray_sdk_core_1 = __importDefault(require("aws-xray-sdk-core")); const { captureAWS, captureAWSClient, captureAWSv3Client, captureAsyncFunc, captureFunc, captureHTTPsGlobal, getNamespace, getSegment, setSegment, Segment: XraySegment, setContextMissingStrategy, setDaemonAddress, setLogger, } = aws_xray_sdk_core_1.default; const node_diagnostics_channel_1 = require("node:diagnostics_channel"); const node_http_1 = __importDefault(require("node:http")); const node_https_1 = __importDefault(require("node:https")); const commons_1 = require("@aws-lambda-powertools/commons"); const EnvironmentVariablesService_js_1 = require("../config/EnvironmentVariablesService.js"); const utilities_js_1 = require("./utilities.js"); /** * The ProviderService class is a wrapper around the AWS X-Ray SDK for Node.js. * * The service provides exposes a selection of methods to interact with the SDK directly, * these methods were chosen to be the most useful in the context of a Lambda function and * have been tested to work with Powertools for AWS Lambda. * * If you want to use one of the other methods that are not exposed by this class, * you can import the methods directly from the `aws-xray-sdk-core` package, and for most cases, * they should work as expected. However, support for these methods is not guaranteed. */ class ProviderService { /** * @deprecated */ captureAWS(awssdk) { return captureAWS(awssdk); } /** * @deprecated */ captureAWSClient(service) { return captureAWSClient(service); } captureAWSv3Client(service) { (0, commons_1.addUserAgentMiddleware)(service, 'tracer'); // biome-ignore lint/suspicious/noExplicitAny: Type must be aliased as any because of this https://github.com/aws/aws-xray-sdk-node/issues/439#issuecomment-859715660 return captureAWSv3Client(service); } captureAsyncFunc(name, fcn, _parent) { return captureAsyncFunc(name, fcn); } captureFunc(name, fcn, _parent) { return captureFunc(name, fcn); } captureHTTPsGlobal() { captureHTTPsGlobal(node_http_1.default); captureHTTPsGlobal(node_https_1.default); } getNamespace() { return getNamespace(); } getSegment() { return getSegment(); } /** * Instrument `fetch` requests with AWS X-Ray * * The instrumentation is done by subscribing to the `undici` events. When a request is created, * a new subsegment is created with the hostname of the request. * * Then, when the headers are received, the subsegment is updated with the request and response details. * * Finally, when the request is completed, the subsegment is closed. * * @see {@link https://nodejs.org/api/diagnostics_channel.html#diagnostics_channel_channel_publish | Diagnostics Channel - Node.js Documentation} */ instrumentFetch() { /** * Create a segment at the start of a request made with `undici` or `fetch`. * * That `message` must be `unknown` because that's the type expected by `subscribe` * * @param message The message received from the `undici` channel */ const onRequestStart = (message) => { const { request } = message; const parentSubsegment = this.getSegment(); const requestURL = (0, utilities_js_1.getRequestURL)(request); if (parentSubsegment && requestURL) { const method = request.method; const subsegment = parentSubsegment.addNewSubsegment(requestURL.hostname); 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_js_1.environmentVariablesService.getXrayTraceId()};Parent=${subsegment.id};Sampled=${subsegment.notTraced ? '0' : '1'}`); subsegment.http = { request: { url: `${requestURL.protocol}//${requestURL.hostname}${requestURL.pathname}`, method, }, }; this.setSegment(subsegment); } }; /** * Enrich the subsegment with the response details, and close it. * Then, set the parent segment as the active segment. * * `message` must be `unknown` because that's the type expected by `subscribe` * * @param message The message received from the `undici` channel */ const onResponse = (message) => { const { response } = message; const subsegment = this.getSegment(); if ((0, utilities_js_1.isHttpSubsegment)(subsegment)) { const status = response.statusCode; const contentLenght = (0, utilities_js_1.findHeaderAndDecode)(response.headers, 'content-length'); subsegment.http = { ...subsegment.http, response: { status, ...(contentLenght && { content_length: Number.parseInt(contentLenght), }), }, }; if (status === 429) { subsegment.addThrottleFlag(); } if (status >= 400 && status < 500) { subsegment.addErrorFlag(); } else if (status >= 500 && status < 600) { subsegment.addFaultFlag(); } subsegment.close(); this.setSegment(subsegment.parent); } }; /** * Add an error to the subsegment when the request fails. * * This is used to handle the case when the request fails to establish a connection with the server or timeouts. * In all other cases, for example, when the server returns a 4xx or 5xx status code, the error is added in the `onResponse` function. * * that `message` must be `unknown` because that's the type expected by `subscribe` * * @param message The message received from the `undici` channel */ const onError = (message) => { const { error } = message; const subsegment = this.getSegment(); if ((0, utilities_js_1.isHttpSubsegment)(subsegment)) { subsegment.addErrorFlag(); error instanceof Error && subsegment.addError(error, true); subsegment.close(); this.setSegment(subsegment.parent); } }; (0, node_diagnostics_channel_1.subscribe)('undici:request:create', onRequestStart); (0, node_diagnostics_channel_1.subscribe)('undici:request:headers', onResponse); (0, node_diagnostics_channel_1.subscribe)('undici:request:error', onError); } putAnnotation(key, value) { const segment = this.getSegment(); if (segment === undefined) { console.warn('No active segment or subsegment found, skipping annotation'); return; } if (segment instanceof XraySegment) { console.warn('You cannot annotate the main segment in a Lambda execution environment'); return; } segment.addAnnotation(key, value); } putMetadata(key, value, namespace) { const segment = this.getSegment(); if (segment === undefined) { console.warn('No active segment or subsegment found, skipping metadata addition'); return; } if (segment instanceof XraySegment) { console.warn('You cannot add metadata to the main segment in a Lambda execution environment'); return; } segment.addMetadata(key, value, namespace); } setContextMissingStrategy(strategy) { setContextMissingStrategy(strategy); } setDaemonAddress(address) { setDaemonAddress(address); } setLogger(logObj) { setLogger(logObj); } setSegment(segment) { setSegment(segment); } } exports.ProviderService = ProviderService;