@dynatrace/opentelemetry-gcf
Version:
Dynatrace OpenTelemetry package for Google Cloud Functions
190 lines (187 loc) • 7.98 kB
JavaScript
;
/*
Copyright 2023 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.isColdStart = void 0;
exports.startHttpSpan = startHttpSpan;
exports.startActiveHttpSpan = startActiveHttpSpan;
exports.endHttpSpanAndFlush = endHttpSpanAndFlush;
exports.endHttpSpan = endHttpSpan;
exports.flushSpans = flushSpans;
exports.resetForceFlushCache = resetForceFlushCache;
const api_1 = require("@opentelemetry/api");
const opentelemetry_core_1 = require("@dynatrace/opentelemetry-core");
const Resources_1 = require("./Resources");
// ============================================================================
// TODO: should this start with 'dt.agent.nodejs.'?
const cInstrumentationLibraryName = "@dynatrace/opentelemetry-gcf";
const tracer = api_1.trace.getTracer(cInstrumentationLibraryName);
let gcfResourceAttributes;
let forceFlushCached = false;
let forceFlush;
/**
* Starts new server span and adds google cloud functions specific attributes.
*
* @param req the express request (see https://expressjs.com/en/api.html#req)
* @returns a promise with the created span.
*/
async function startHttpSpan(req) {
const coldStart = (0, exports.isColdStart)();
if (coldStart) {
gcfResourceAttributes = await Resources_1.gcfDetector.detect();
}
const parentContext = api_1.propagation.extract(api_1.ROOT_CONTEXT, req.headers);
const reqAttributes = getHttpRequestSpanAttributes(req);
const attrs = Object.assign(Object.assign({ [opentelemetry_core_1.SemConvTrace.FAAS_TRIGGER]: opentelemetry_core_1.SemConvTrace.FaasTriggerValues.HTTP, [opentelemetry_core_1.SemConvTrace.FAAS_COLDSTART]: coldStart }, gcfResourceAttributes), reqAttributes);
const options = {
attributes: attrs,
kind: api_1.SpanKind.SERVER
};
const span = tracer.startSpan(Resources_1.cFunctionName, options, parentContext);
(0, opentelemetry_core_1.setPropagatedResourceAttributes)(span, gcfResourceAttributes);
return span;
}
/**
* Starts a new server span and calls the given function passing it the
* created span as first argument.
* Additionally the new span gets set in context and this context is activated
* for the duration of the function call.
*
* @param req the express request (see https://expressjs.com/en/api.html#req)
* @param fn function called in the context of the span and receives the newly created span as an argument
*/
async function startActiveHttpSpan(req, fn) {
const span = await startHttpSpan(req);
const ctxWithSpan = api_1.trace.setSpan(api_1.ROOT_CONTEXT, span);
return api_1.context.with(ctxWithSpan, fn, undefined, span);
}
/**
* Ends the given span and flushes the tracer provider.
*
* @param span the span to end.
* @param res the express response (see https://expressjs.com/en/api.html#res). Provided response should be fully prepared (e.g. status code set)
* @param error an optional error that will be recorded before the span is ended.
* @returns a promise to await the tracer provider's flush operation.
*/
async function endHttpSpanAndFlush(span, res, error) {
endHttpSpan(span, res, error);
return flushSpans();
}
/**
* Ends the given span.
*
* @param span the span to end.
* @param res the express response (see https://expressjs.com/en/api.html#res). Provided response should be fully prepared (e.g. status code set)
* @param error an optional error that will be recorded before the span is ended.
*/
function endHttpSpan(span, res, error) {
if (error != null) {
(0, opentelemetry_core_1.addErrorAttributes)(span, error);
}
// only set error status (see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#status)
if (error != null || res.statusCode >= 500) {
span.setStatus({ code: api_1.SpanStatusCode.ERROR });
}
span.setAttribute(opentelemetry_core_1.SemConvTrace.HTTP_STATUS_CODE, res.statusCode);
span.end();
}
/**
* Flushes registered tracer provider. To successfully flush the registered tracer provider must have forceFlush method implemented.
*
* @returns flush promise
*/
async function flushSpans() {
const flush = getForceFlush();
if (typeof (flush) === "function") {
return flush();
}
}
/**
* Extracts the relevant relevant incoming HTTP span attributes from the given express request object.
*
* @param req the express request (see https://expressjs.com/en/api.html#req)
* @returns the span attributes for the incoming HTTP request.
*/
function getHttpRequestSpanAttributes(req) {
const attributes = captureHttpHeaders(req);
attributes[opentelemetry_core_1.SemConvTrace.HTTP_SCHEME] = req.protocol || "https";
attributes[opentelemetry_core_1.SemConvTrace.HTTP_METHOD] = req.method || "GET";
attributes[opentelemetry_core_1.SemConvTrace.HTTP_HOST] = req.hostname;
attributes[opentelemetry_core_1.SemConvTrace.HTTP_TARGET] = req.originalUrl;
return attributes;
}
/**
* Returns span attributes from captured http headers (e.g. referer, user-agent, etc.. )
* @param req the express request (see https://expressjs.com/en/api.html#req)
*/
function captureHttpHeaders(req) {
const attrs = {};
const headersToCapture = (0, opentelemetry_core_1.getHttpHeadersToCapture)();
for (const [headerName, semConvKey] of Object.entries(headersToCapture)) {
const headerValue = req.get(headerName);
if (headerValue != null) {
attrs[semConvKey] = headerValue;
}
}
// TODO capture headers for agent config
const forwarded = req.get("forwarded");
if (forwarded != null) {
attrs[opentelemetry_core_1.SemConvTrace.DT_HTTP_REQUEST_HEADER_FORWARDED] = forwarded;
}
else {
const xForwardedFor = req.get("x-forwarded-for");
attrs[opentelemetry_core_1.SemConvTrace.DT_HTTP_REQUEST_HEADER_X_FORWARDED_FOR] = xForwardedFor;
}
return attrs;
}
/**
* Returns true if it is cold start, otherwise false
* NOTE: the function is exported to make cold start unit testable
*/
const isColdStart = function () {
return gcfResourceAttributes == null;
};
exports.isColdStart = isColdStart;
/**
* Tries to find forceFlush function in provided tracerProvider and returns it.
* @returns forceFlush function if found, otherwise "<not found>"
*/
function getForceFlush() {
if (forceFlushCached) {
return forceFlush;
}
forceFlushCached = true;
const tracerProvider = api_1.trace.getTracerProvider();
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-call */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let currentProvider = tracerProvider;
if (typeof currentProvider.getDelegate === "function") {
currentProvider = currentProvider.getDelegate();
}
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
if (typeof currentProvider.forceFlush === "function") {
forceFlush = currentProvider.forceFlush.bind(currentProvider);
}
/* eslint-disable @typescript-eslint/no-unsafe-call */
return forceFlush;
}
/**
* Resets cached forceFlush function
* NOTE: the function is exported only for unit testing
*/
function resetForceFlushCache() {
forceFlushCached = false;
forceFlush = undefined;
}
//# sourceMappingURL=GcfInstrumentation.js.map