@opentelemetry/instrumentation-aws-sdk
Version:
OpenTelemetry instrumentation for `aws-sdk` and `@aws-sdk/client-*` clients for various AWS services
290 lines • 15.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AwsInstrumentation = void 0;
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const api_1 = require("@opentelemetry/api");
const core_1 = require("@opentelemetry/core");
const enums_1 = require("./enums");
const services_1 = require("./services");
/** @knipignore */
const version_1 = require("./version");
const instrumentation_1 = require("@opentelemetry/instrumentation");
const utils_1 = require("./utils");
const propwrap_1 = require("./propwrap");
const semconv_1 = require("./semconv");
const semantic_conventions_1 = require("@opentelemetry/semantic-conventions");
const V3_CLIENT_CONFIG_KEY = Symbol('opentelemetry.instrumentation.aws-sdk.client.config');
class AwsInstrumentation extends instrumentation_1.InstrumentationBase {
static component = 'aws-sdk';
_semconvStability;
constructor(config = {}) {
super(version_1.PACKAGE_NAME, version_1.PACKAGE_VERSION, config);
this._semconvStability = (0, instrumentation_1.semconvStabilityFromStr)('http', process.env.OTEL_SEMCONV_STABILITY_OPT_IN);
}
init() {
const v3MiddlewareStackFileOldVersions = new instrumentation_1.InstrumentationNodeModuleFile('@aws-sdk/middleware-stack/dist/cjs/MiddlewareStack.js', ['>=3.1.0 <3.35.0'], this.patchV3ConstructStack.bind(this), this.unpatchV3ConstructStack.bind(this));
const v3MiddlewareStackFileNewVersions = new instrumentation_1.InstrumentationNodeModuleFile('@aws-sdk/middleware-stack/dist-cjs/MiddlewareStack.js', ['>=3.35.0'], this.patchV3ConstructStack.bind(this), this.unpatchV3ConstructStack.bind(this));
// as for aws-sdk v3.13.1, constructStack is exported from @aws-sdk/middleware-stack as
// getter instead of function, which fails shimmer.
// so we are patching the MiddlewareStack.js file directly to get around it.
const v3MiddlewareStack = new instrumentation_1.InstrumentationNodeModuleDefinition('@aws-sdk/middleware-stack', ['^3.1.0'], undefined, undefined, [v3MiddlewareStackFileOldVersions, v3MiddlewareStackFileNewVersions]);
// Patch for @smithy/middleware-stack for @aws-sdk/* packages v3.363.0+.
// As of @smithy/middleware-stack@2.1.0 `constructStack` is only available
// as a getter, so we cannot use `this._wrap()`.
const self = this;
const v3SmithyMiddlewareStack = new instrumentation_1.InstrumentationNodeModuleDefinition('@smithy/middleware-stack', ['>=2.0.0'], (moduleExports, moduleVersion) => {
const newExports = (0, propwrap_1.propwrap)(moduleExports, 'constructStack', (orig) => {
self._diag.debug('propwrapping aws-sdk v3 constructStack');
return self._getV3ConstructStackPatch(moduleVersion, orig);
});
return newExports;
});
const v3SmithyClient = new instrumentation_1.InstrumentationNodeModuleDefinition('@aws-sdk/smithy-client', ['^3.1.0'], this.patchV3SmithyClient.bind(this), this.unpatchV3SmithyClient.bind(this));
// patch for new @smithy/smithy-client for aws-sdk packages v3.363.0+
const v3NewSmithyClient = new instrumentation_1.InstrumentationNodeModuleDefinition('@smithy/smithy-client', ['>=1.0.3'], this.patchV3SmithyClient.bind(this), this.unpatchV3SmithyClient.bind(this));
return [
v3MiddlewareStack,
v3SmithyMiddlewareStack,
v3SmithyClient,
v3NewSmithyClient,
];
}
patchV3ConstructStack(moduleExports, moduleVersion) {
this._wrap(moduleExports, 'constructStack', this._getV3ConstructStackPatch.bind(this, moduleVersion));
return moduleExports;
}
unpatchV3ConstructStack(moduleExports) {
this._unwrap(moduleExports, 'constructStack');
return moduleExports;
}
patchV3SmithyClient(moduleExports) {
this._wrap(moduleExports.Client.prototype, 'send', this._getV3SmithyClientSendPatch.bind(this));
return moduleExports;
}
unpatchV3SmithyClient(moduleExports) {
this._unwrap(moduleExports.Client.prototype, 'send');
return moduleExports;
}
_startAwsV3Span(normalizedRequest, metadata) {
const name = metadata.spanName ??
`${normalizedRequest.serviceName}.${normalizedRequest.commandName}`;
const newSpan = this.tracer.startSpan(name, {
kind: metadata.spanKind ?? api_1.SpanKind.CLIENT,
attributes: {
...(0, utils_1.extractAttributesFromNormalizedRequest)(normalizedRequest),
...metadata.spanAttributes,
},
});
return newSpan;
}
_callUserPreRequestHook(span, request, moduleVersion) {
const { preRequestHook } = this.getConfig();
if (preRequestHook) {
const requestInfo = {
moduleVersion,
request,
};
(0, instrumentation_1.safeExecuteInTheMiddle)(() => preRequestHook(span, requestInfo), (e) => {
if (e)
api_1.diag.error(`${AwsInstrumentation.component} instrumentation: preRequestHook error`, e);
}, true);
}
}
_callUserResponseHook(span, response) {
const { responseHook } = this.getConfig();
if (!responseHook)
return;
const responseInfo = {
response,
};
(0, instrumentation_1.safeExecuteInTheMiddle)(() => responseHook(span, responseInfo), (e) => {
if (e)
api_1.diag.error(`${AwsInstrumentation.component} instrumentation: responseHook error`, e);
}, true);
}
_callUserExceptionResponseHook(span, request, err) {
const { exceptionHook } = this.getConfig();
if (!exceptionHook)
return;
const requestInfo = {
request,
};
(0, instrumentation_1.safeExecuteInTheMiddle)(() => exceptionHook(span, requestInfo, err), (e) => {
if (e)
api_1.diag.error(`${AwsInstrumentation.component} instrumentation: exceptionHook error`, e);
}, true);
}
_getV3ConstructStackPatch(moduleVersion, original) {
const self = this;
return function constructStack(...args) {
const stack = original.apply(this, args);
self.patchV3MiddlewareStack(moduleVersion, stack);
return stack;
};
}
_getV3SmithyClientSendPatch(original) {
return function send(command, ...args) {
command[V3_CLIENT_CONFIG_KEY] = this.config;
return original.apply(this, [command, ...args]);
};
}
patchV3MiddlewareStack(moduleVersion, middlewareStackToPatch) {
if (!(0, instrumentation_1.isWrapped)(middlewareStackToPatch.resolve)) {
this._wrap(middlewareStackToPatch, 'resolve', this._getV3MiddlewareStackResolvePatch.bind(this, moduleVersion));
}
// 'clone' and 'concat' functions are internally calling 'constructStack' which is in same
// module, thus not patched, and we need to take care of it specifically.
this._wrap(middlewareStackToPatch, 'clone', this._getV3MiddlewareStackClonePatch.bind(this, moduleVersion));
this._wrap(middlewareStackToPatch, 'concat', this._getV3MiddlewareStackClonePatch.bind(this, moduleVersion));
}
_getV3MiddlewareStackClonePatch(moduleVersion, original) {
const self = this;
return function (...args) {
const newStack = original.apply(this, args);
self.patchV3MiddlewareStack(moduleVersion, newStack);
return newStack;
};
}
_getV3MiddlewareStackResolvePatch(moduleVersion, original) {
const self = this;
return function (_handler, awsExecutionContext) {
const origHandler = original.call(this, _handler, awsExecutionContext);
const patchedHandler = function (command) {
const clientConfig = command[V3_CLIENT_CONFIG_KEY];
const regionPromise = clientConfig?.region?.();
const serviceName = clientConfig?.serviceId ??
(0, utils_1.removeSuffixFromStringIfExists)(
// Use 'AWS' as a fallback serviceName to match type definition.
// In practice, `clientName` should always be set.
awsExecutionContext.clientName || 'AWS', 'Client');
const commandName = awsExecutionContext.commandName ?? command.constructor?.name;
const normalizedRequest = (0, utils_1.normalizeV3Request)(serviceName, commandName, command.input, undefined);
const requestMetadata = self.servicesExtensions.requestPreSpanHook(normalizedRequest, self.getConfig(), self._diag);
const startTime = (0, core_1.hrTime)();
const span = self._startAwsV3Span(normalizedRequest, requestMetadata);
const activeContextWithSpan = api_1.trace.setSpan(api_1.context.active(), span);
const handlerPromise = new Promise((resolve, reject) => {
Promise.resolve(regionPromise)
.then(resolvedRegion => {
normalizedRequest.region = resolvedRegion;
span.setAttribute(enums_1.AttributeNames.CLOUD_REGION, resolvedRegion);
})
.catch(e => {
// there is nothing much we can do in this case.
// we'll just continue without region
api_1.diag.debug(`${AwsInstrumentation.component} instrumentation: failed to extract region from async function`, e);
})
.finally(() => {
self._callUserPreRequestHook(span, normalizedRequest, moduleVersion);
const resultPromise = api_1.context.with(activeContextWithSpan, () => {
self.servicesExtensions.requestPostSpanHook(normalizedRequest);
return self._callOriginalFunction(() => origHandler.call(this, command));
});
const promiseWithResponseLogic = resultPromise
.then(response => {
const requestId = response.output?.$metadata?.requestId;
if (requestId) {
span.setAttribute(enums_1.AttributeNames.AWS_REQUEST_ID, requestId);
}
const httpStatusCode = response.output?.$metadata?.httpStatusCode;
if (httpStatusCode) {
if (self._semconvStability & instrumentation_1.SemconvStability.OLD) {
span.setAttribute(semconv_1.ATTR_HTTP_STATUS_CODE, httpStatusCode);
}
if (self._semconvStability & instrumentation_1.SemconvStability.STABLE) {
span.setAttribute(semantic_conventions_1.ATTR_HTTP_RESPONSE_STATUS_CODE, httpStatusCode);
}
}
const extendedRequestId = response.output?.$metadata?.extendedRequestId;
if (extendedRequestId) {
span.setAttribute(enums_1.AttributeNames.AWS_REQUEST_EXTENDED_ID, extendedRequestId);
}
const normalizedResponse = {
data: response.output,
request: normalizedRequest,
requestId: requestId,
};
const override = self.servicesExtensions.responseHook(normalizedResponse, span, self.tracer, self.getConfig(), startTime);
if (override) {
response.output = override;
normalizedResponse.data = override;
}
self._callUserResponseHook(span, normalizedResponse);
return response;
})
.catch(err => {
const requestId = err?.RequestId;
if (requestId) {
span.setAttribute(enums_1.AttributeNames.AWS_REQUEST_ID, requestId);
}
const httpStatusCode = err?.$metadata?.httpStatusCode;
if (httpStatusCode) {
if (self._semconvStability & instrumentation_1.SemconvStability.OLD) {
span.setAttribute(semconv_1.ATTR_HTTP_STATUS_CODE, httpStatusCode);
}
if (self._semconvStability & instrumentation_1.SemconvStability.STABLE) {
span.setAttribute(semantic_conventions_1.ATTR_HTTP_RESPONSE_STATUS_CODE, httpStatusCode);
}
}
const extendedRequestId = err?.extendedRequestId;
if (extendedRequestId) {
span.setAttribute(enums_1.AttributeNames.AWS_REQUEST_EXTENDED_ID, extendedRequestId);
}
span.setStatus({
code: api_1.SpanStatusCode.ERROR,
message: err.message,
});
span.recordException(err);
self._callUserExceptionResponseHook(span, normalizedRequest, err);
throw err;
})
.finally(() => {
if (!requestMetadata.isStream) {
span.end();
}
});
promiseWithResponseLogic
.then(res => {
resolve(res);
})
.catch(err => reject(err));
});
});
return requestMetadata.isIncoming
? (0, utils_1.bindPromise)(handlerPromise, activeContextWithSpan, 2)
: handlerPromise;
};
return patchedHandler;
};
}
_callOriginalFunction(originalFunction) {
if (this.getConfig().suppressInternalInstrumentation) {
return api_1.context.with((0, core_1.suppressTracing)(api_1.context.active()), originalFunction);
}
else {
return originalFunction();
}
}
_updateMetricInstruments() {
if (!this.servicesExtensions) {
this.servicesExtensions = new services_1.ServicesExtensions();
}
this.servicesExtensions.updateMetricInstruments(this.meter);
}
}
exports.AwsInstrumentation = AwsInstrumentation;
//# sourceMappingURL=aws-sdk.js.map