@dynatrace/opentelemetry-azure-functions
Version:
Dynatrace OpenTelemetry package for Azure Functions
116 lines (113 loc) • 5.87 kB
JavaScript
;
/*
Copyright 2022 Dynatrace LLC
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
http://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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.wrapHandler = exports._testClearIsV3Ctx = void 0;
const util = require("util");
const opentelemetry_core_1 = require("@dynatrace/opentelemetry-core");
const api_1 = require("@opentelemetry/api");
const AttributeDetector_1 = require("./AttributeDetector");
const Bindings_1 = require("./Bindings");
const Logger_1 = require("./Logger");
const ResourceDetector_1 = require("./ResourceDetector");
const Util_1 = require("./Util");
// ============================================================================
let isV3Ctx;
// only exported for testing
function _testClearIsV3Ctx() {
isV3Ctx = undefined;
}
exports._testClearIsV3Ctx = _testClearIsV3Ctx;
// ============================================================================
function isObject(obj) {
return obj != null && typeof (obj) === "object";
}
// ============================================================================
/**
* Checks if the provided object is a V3 (programming model) Context
* @param maybeContext context to be checked
* @returns true if provided parameter is a V3 Context, otherwise false
*/
function isV3Context(maybeContext) {
if (isV3Ctx == null) {
isV3Ctx = isObject(maybeContext) &&
maybeContext.invocationId != null &&
isObject(maybeContext.executionContext) &&
isObject(maybeContext.bindings) &&
isObject(maybeContext.bindingDefinitions);
if (isV3Ctx === false) {
Logger_1.logger.info("Only V3 Azure Functions programming model is supported. No data will be exported");
}
}
return isV3Ctx;
}
// ============================================================================
/**
* Wraps an azure function handler for tracing.
*
* The returned function handler shall replace the exiting one. It starts a span on each invocation and calls the
* original handler with the span set as active. The span is ended once the original function returns.
*
* Function arguments, this, return value and exceptions are forwarded by the wrapped handler to the original.
*
* @param handler the handler function to wrap
* @returns the wrapped handler function
*/
function wrapHandler(handler) {
const isAsyncFunction = util.types.isAsyncFunction(handler);
Logger_1.logger.info(`wrapHandler: ${handler.name}, isAsyncFunction ${isAsyncFunction}`);
// as of now only async functions are supported. Azure doc recommends to use async functions even if await is not used.
// eslint-disable-next-line max-len
// see https://docs.microsoft.com/en-us/azure/azure-functions/functions-reference-node?tabs=v2-v3-v4-export%2Cv2-v3-v4-done%2Cv2%2Cv2-log-custom-telemetry%2Cv2-accessing-request-and-response%2Cwindows-setting-the-node-version#use-async-and-await
if (!isAsyncFunction) {
Logger_1.logger.info("Only async handler functions are supported. No data will be exported");
return handler;
}
return async function dtHandler(context, ...args) {
var _a, _b, _c;
if (!isV3Context(context)) {
// If the first argument is not a V3 Context, then it's not a V3 programming model.
// V4 programming model has Request as a handler's first argument
return Reflect.apply(handler, this, [context, ...args]);
}
const req = args[0];
const azureHttpRequest = (0, Util_1.isAzureHttpRequestV3)(req) ? req : undefined;
const triggerType = (0, Bindings_1.getTriggerType)(context);
const resourceAttributes = (0, ResourceDetector_1.detectResource)((_a = context === null || context === void 0 ? void 0 : context.executionContext) === null || _a === void 0 ? void 0 : _a.functionName);
const attributes = Object.assign(Object.assign({}, resourceAttributes), (0, AttributeDetector_1.getStartAttributes)(triggerType, azureHttpRequest));
const name = (_c = (_b = context === null || context === void 0 ? void 0 : context.executionContext) === null || _b === void 0 ? void 0 : _b.functionName) !== null && _c !== void 0 ? _c : "invoke";
const ctx = (0, Util_1.extractContext)(triggerType, azureHttpRequest);
return (0, Util_1.getTracer)().startActiveSpan(name, { kind: api_1.SpanKind.SERVER, attributes }, ctx, async (span) => {
(0, opentelemetry_core_1.setPropagatedResourceAttributes)(span, resourceAttributes);
try {
// call the original handler and await the result
const result = await Reflect.apply(handler, this, [context, ...args]);
const exitAttributes = (0, AttributeDetector_1.getExitAttributes)(triggerType, context, result);
if (exitAttributes != null) {
span.setAttributes(exitAttributes);
}
return result;
}
catch (e) {
(0, opentelemetry_core_1.addErrorAttributes)(span, e);
span.setStatus({ code: api_1.SpanStatusCode.ERROR });
throw e;
}
finally {
span.end();
}
});
};
}
exports.wrapHandler = wrapHandler;
//# sourceMappingURL=AzureFunctionsInstrumentation.js.map