UNPKG

enhanced-adot-node-autoinstrumentation

Version:

This package provides Amazon Web Services distribution of the OpenTelemetry Node Instrumentation, which allows for auto-instrumentation of NodeJS applications.

263 lines (261 loc) 15 kB
"use strict"; // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 Object.defineProperty(exports, "__esModule", { value: true }); exports.customExtractor = exports.applyInstrumentationPatches = exports.headerGetter = exports.AWSXRAY_TRACE_ID_HEADER_CAPITALIZED = exports.traceContextEnvironmentKey = void 0; const api_1 = require("@opentelemetry/api"); const propagator_aws_xray_1 = require("@opentelemetry/propagator-aws-xray"); const aws_attribute_keys_1 = require("../aws-attribute-keys"); const bedrock_1 = require("./aws/services/bedrock"); const secretsmanager_1 = require("./aws/services/secretsmanager"); const step_functions_1 = require("./aws/services/step-functions"); exports.traceContextEnvironmentKey = '_X_AMZN_TRACE_ID'; exports.AWSXRAY_TRACE_ID_HEADER_CAPITALIZED = 'X-Amzn-Trace-Id'; const awsPropagator = new propagator_aws_xray_1.AWSXRayPropagator(); exports.headerGetter = { keys(carrier) { return Object.keys(carrier); }, get(carrier, key) { return carrier[key]; }, }; function applyInstrumentationPatches(instrumentations) { /* Apply patches to upstream instrumentation libraries. This method is invoked to apply changes to upstream instrumentation libraries, typically when changes to upstream are required on a timeline that cannot wait for upstream release. Generally speaking, patches should be short-term local solutions that are comparable to long-term upstream solutions. Where possible, automated testing should be run to catch upstream changes resulting in broken patches */ instrumentations.forEach((instrumentation, index) => { var _a; if (instrumentation.instrumentationName === '@opentelemetry/instrumentation-aws-sdk') { api_1.diag.debug('Patching aws sdk instrumentation'); patchAwsSdkInstrumentation(instrumentation); // Access private property servicesExtensions of AwsInstrumentation // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore const services = (_a = instrumentations[index].servicesExtensions) === null || _a === void 0 ? void 0 : _a.services; if (services) { services.set('SecretsManager', new secretsmanager_1.SecretsManagerServiceExtension()); services.set('SFN', new step_functions_1.StepFunctionsServiceExtension()); services.set('Bedrock', new bedrock_1.BedrockServiceExtension()); services.set('BedrockAgent', new bedrock_1.BedrockAgentServiceExtension()); services.set('BedrockAgentRuntime', new bedrock_1.BedrockAgentRuntimeServiceExtension()); services.set('BedrockRuntime', new bedrock_1.BedrockRuntimeServiceExtension()); patchSqsServiceExtension(services.get('SQS')); patchSnsServiceExtension(services.get('SNS')); patchLambdaServiceExtension(services.get('Lambda')); } } else if (instrumentation.instrumentationName === '@opentelemetry/instrumentation-aws-lambda') { api_1.diag.debug('Patching aws lambda instrumentation'); patchAwsLambdaInstrumentation(instrumentation); } }); } exports.applyInstrumentationPatches = applyInstrumentationPatches; /* * This function `customExtractor` is used to extract SpanContext for AWS Lambda functions. * It first attempts to extract the trace context from the AWS X-Ray header, which is stored in the Lambda environment variables. * If a valid span context is extracted from the environment, it uses this as the parent context for the function's tracing. * If the X-Ray header is missing or invalid, it falls back to extracting trace context from the Lambda handler's event headers. * If neither approach succeeds, it defaults to using the root Otel context, ensuring the function is still instrumented for tracing. */ const customExtractor = (event, _handlerContext) => { var _a, _b; let parent = undefined; const lambdaTraceHeader = process.env[exports.traceContextEnvironmentKey]; if (lambdaTraceHeader) { parent = awsPropagator.extract(api_1.context.active(), { [propagator_aws_xray_1.AWSXRAY_TRACE_ID_HEADER]: lambdaTraceHeader }, exports.headerGetter); } if (parent) { const spanContext = (_a = api_1.trace.getSpan(parent)) === null || _a === void 0 ? void 0 : _a.spanContext(); if (spanContext && (0, api_1.isSpanContextValid)(spanContext)) { return parent; } } const httpHeaders = event.headers || {}; const extractedContext = api_1.propagation.extract(api_1.context.active(), httpHeaders, exports.headerGetter); if ((_b = api_1.trace.getSpan(extractedContext)) === null || _b === void 0 ? void 0 : _b.spanContext()) { return extractedContext; } return api_1.ROOT_CONTEXT; }; exports.customExtractor = customExtractor; /* * This patch extends the existing upstream extension for SQS. Extensions allow for custom logic for adding * service-specific information to spans, such as attributes. Specifically, we are adding logic to add * `aws.sqs.queue.url` and `aws.sqs.queue.name` attributes, to be used to generate RemoteTarget and achieve parity * with the Java/Python instrumentation. * * Callout that today, the upstream logic adds `messaging.url` and `messaging.destination` but we feel that * `aws.sqs` is more in line with existing AWS Semantic Convention attributes like `AWS_S3_BUCKET`, etc. * * @param sqsServiceExtension SQS Service Extension obtained the service extension list from the AWS SDK OTel Instrumentation */ function patchSqsServiceExtension(sqsServiceExtension) { // It is not expected that `sqsServiceExtension` is undefined if (sqsServiceExtension) { const requestPreSpanHook = sqsServiceExtension.requestPreSpanHook; // Save original `requestPreSpanHook` under a similar name, to be invoked by the patched hook sqsServiceExtension._requestPreSpanHook = requestPreSpanHook; // The patched hook will populate the 'aws.sqs.queue.url' and 'aws.sqs.queue.name' attributes according to spec // from the 'messaging.url' attribute const patchedRequestPreSpanHook = (request, _config) => { var _a, _b; const requestMetadata = sqsServiceExtension._requestPreSpanHook(request, _config); // It is not expected that `requestMetadata.spanAttributes` can possibly be undefined, but still be careful anyways if (requestMetadata.spanAttributes) { if ((_a = request.commandInput) === null || _a === void 0 ? void 0 : _a.QueueUrl) { requestMetadata.spanAttributes[aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_SQS_QUEUE_URL] = request.commandInput.QueueUrl; } if ((_b = request.commandInput) === null || _b === void 0 ? void 0 : _b.QueueName) { requestMetadata.spanAttributes[aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_SQS_QUEUE_NAME] = request.commandInput.QueueName; } } return requestMetadata; }; sqsServiceExtension.requestPreSpanHook = patchedRequestPreSpanHook; } } /* * This patch extends the existing upstream extension for SNS. Extensions allow for custom logic for adding * service-specific information to spans, such as attributes. Specifically, we are adding logic to add * `aws.sns.topic.arn` attribute, to be used to generate RemoteTarget and achieve parity with the Java/Python instrumentation. * * * @param snsServiceExtension SNS Service Extension obtained the service extension list from the AWS SDK OTel Instrumentation */ function patchSnsServiceExtension(snsServiceExtension) { if (snsServiceExtension) { const requestPreSpanHook = snsServiceExtension.requestPreSpanHook; snsServiceExtension._requestPreSpanHook = requestPreSpanHook; const patchedRequestPreSpanHook = (request, _config) => { var _a; const requestMetadata = snsServiceExtension._requestPreSpanHook(request, _config); if (requestMetadata.spanAttributes) { const topicArn = (_a = request.commandInput) === null || _a === void 0 ? void 0 : _a.TopicArn; if (topicArn) { requestMetadata.spanAttributes[aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_SNS_TOPIC_ARN] = topicArn; } } return requestMetadata; }; snsServiceExtension.requestPreSpanHook = patchedRequestPreSpanHook; } } /* * This patch extends the existing upstream extension for Lambda. Extensions allow for custom logic for adding * service-specific information to spans, such as attributes. Specifically, we are adding logic to add * `aws.lambda.resource_mapping.id` attribute, to be used to generate RemoteTarget and achieve parity with the Java/Python instrumentation. * * * @param lambdaServiceExtension Lambda Service Extension obtained the service extension list from the AWS SDK OTel Instrumentation */ function patchLambdaServiceExtension(lambdaServiceExtension) { if (lambdaServiceExtension) { const requestPreSpanHook = lambdaServiceExtension.requestPreSpanHook; lambdaServiceExtension._requestPreSpanHook = requestPreSpanHook; const patchedRequestPreSpanHook = (request, _config) => { var _a, _b; const requestMetadata = lambdaServiceExtension._requestPreSpanHook(request, _config); if (requestMetadata.spanAttributes) { const resourceMappingId = (_a = request.commandInput) === null || _a === void 0 ? void 0 : _a.UUID; if (resourceMappingId) { requestMetadata.spanAttributes[aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_LAMBDA_RESOURCE_MAPPING_ID] = resourceMappingId; } const requestFunctionNameFormat = (_b = request.commandInput) === null || _b === void 0 ? void 0 : _b.FunctionName; let functionName = requestFunctionNameFormat; if (requestFunctionNameFormat) { if (requestFunctionNameFormat.startsWith('arn:aws:lambda')) { const split = requestFunctionNameFormat.split(':'); functionName = split[split.length - 1]; } requestMetadata.spanAttributes[aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_LAMBDA_FUNCTION_NAME] = functionName; } } return requestMetadata; }; lambdaServiceExtension.requestPreSpanHook = patchedRequestPreSpanHook; if (typeof lambdaServiceExtension.responseHook === 'function') { const originalResponseHook = lambdaServiceExtension.responseHook; lambdaServiceExtension.responseHook = (response, span, tracer, config) => { originalResponseHook.call(lambdaServiceExtension, response, span, tracer, config); if (response.data && response.data.Configuration) { const functionArn = response.data.Configuration.FunctionArn; if (functionArn) { span.setAttribute(aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_LAMBDA_FUNCTION_ARN, functionArn); } } }; } } } // Override the upstream private _endSpan method to remove the unnecessary metric force-flush error message // https://github.com/open-telemetry/opentelemetry-js-contrib/blob/main/plugins/node/opentelemetry-instrumentation-aws-lambda/src/instrumentation.ts#L358-L398 function patchAwsLambdaInstrumentation(instrumentation) { if (instrumentation) { instrumentation['_endSpan'] = function (span, err, callback) { if (err) { span.recordException(err); } let errMessage; if (typeof err === 'string') { errMessage = err; } else if (err) { errMessage = err.message; } if (errMessage) { span.setStatus({ code: api_1.SpanStatusCode.ERROR, message: errMessage, }); } span.end(); const flushers = []; if (this._traceForceFlusher) { flushers.push(this._traceForceFlusher()); } else { api_1.diag.error('Spans may not be exported for the lambda function because we are not force flushing before callback.'); } Promise.all(flushers).then(callback, callback); }; } } // Override the upstream private _getV3SmithyClientSendPatch method to add middleware to inject X-Ray Trace Context into HTTP Headers // https://github.com/open-telemetry/opentelemetry-js-contrib/blob/instrumentation-aws-sdk-v0.48.0/plugins/node/opentelemetry-instrumentation-aws-sdk/src/aws-sdk.ts#L373-L384 const awsXrayPropagator = new propagator_aws_xray_1.AWSXRayPropagator(); const V3_CLIENT_CONFIG_KEY = Symbol('opentelemetry.instrumentation.aws-sdk.client.config'); function patchAwsSdkInstrumentation(instrumentation) { if (instrumentation) { instrumentation['_getV3SmithyClientSendPatch'] = function (original) { return function send(command, ...args) { var _a; (_a = this.middlewareStack) === null || _a === void 0 ? void 0 : _a.add((next, context) => async (middlewareArgs) => { awsXrayPropagator.inject(api_1.context.active(), middlewareArgs.request.headers, api_1.defaultTextMapSetter); // Need to set capitalized version of the trace id to ensure that the Recursion Detection Middleware // of aws-sdk-js-v3 will detect the propagated X-Ray Context // See: https://github.com/aws/aws-sdk-js-v3/blob/v3.768.0/packages/middleware-recursion-detection/src/index.ts#L13 const xrayTraceId = middlewareArgs.request.headers[propagator_aws_xray_1.AWSXRAY_TRACE_ID_HEADER]; if (xrayTraceId) { middlewareArgs.request.headers[exports.AWSXRAY_TRACE_ID_HEADER_CAPITALIZED] = xrayTraceId; delete middlewareArgs.request.headers[propagator_aws_xray_1.AWSXRAY_TRACE_ID_HEADER]; } const result = await next(middlewareArgs); return result; }, { step: 'build', name: '_adotInjectXrayContextMiddleware', override: true, }); command[V3_CLIENT_CONFIG_KEY] = this.config; return original.apply(this, [command, ...args]); }; }; } } //# sourceMappingURL=instrumentation-patch.js.map