UNPKG

@splunk/otel

Version:

The Splunk distribution of OpenTelemetry Node Instrumentation provides a Node agent that automatically instruments your Node application to capture and report distributed traces to Splunk APM.

189 lines 6.77 kB
"use strict"; /* * Copyright Splunk Inc. * * 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.configureHttpInstrumentation = configureHttpInstrumentation; exports.configureHttpDcInstrumentation = configureHttpDcInstrumentation; const http_1 = require("http"); const api_1 = require("@opentelemetry/api"); const Url = require("url"); function shouldAddRequestHook(options) { if (Array.isArray(options.captureHttpRequestUriParams) && options.captureHttpRequestUriParams.length === 0) { return false; } return true; } function parseUrlParams(request) { if (request.url === undefined) { return {}; } try { // As long as Node <11 is supported, need to use the legacy API. return Url.parse(request.url || '', true).query; } catch (err) { api_1.diag.debug(`error parsing url '${request.url}`, err); } return {}; } function captureUriParamByKeys(keys) { const capturedKeys = new Map(keys.map((k) => [k, k.replace(/\./g, '_')])); return (span, request) => { const params = parseUrlParams(request); for (const [key, normalizedKey] of capturedKeys) { const value = params[key]; if (value === undefined) { continue; } const values = Array.isArray(value) ? value : [value]; if (values.length > 0) { span.setAttribute(`http.request.param.${normalizedKey}`, values); } } }; } function captureUriParamByFunction(process) { return (span, request) => { const params = parseUrlParams(request); process(span, params); }; } function createHttpRequestHook(options) { const incomingRequestHooks = []; if (Array.isArray(options.captureHttpRequestUriParams)) { incomingRequestHooks.push(captureUriParamByKeys(options.captureHttpRequestUriParams)); } else { incomingRequestHooks.push(captureUriParamByFunction(options.captureHttpRequestUriParams)); } return (span, request) => { const spanContext = span.spanContext(); if (!(0, api_1.isSpanContextValid)(spanContext)) { return; } if (request instanceof http_1.IncomingMessage) { for (const hook of incomingRequestHooks) { hook(span, request); } } }; } function configureHttpInstrumentation(instrumentation, options) { const config = instrumentation.getConfig(); if (shouldAddRequestHook(options)) { const requestHook = createHttpRequestHook(options); if (config.requestHook === undefined) { config.requestHook = requestHook; } else { const original = config.requestHook; config.requestHook = function (span, request) { requestHook(span, request); original.call(this, span, request); }; } } if (!options.serverTimingEnabled) { instrumentation.setConfig(config); return; } const responseHook = (span, response) => { if (!(response instanceof http_1.ServerResponse)) { return; } const spanContext = span.spanContext(); if (!(0, api_1.isSpanContextValid)(spanContext)) { return; } const { traceFlags, traceId, spanId } = spanContext; const sampled = (traceFlags & api_1.TraceFlags.SAMPLED) === api_1.TraceFlags.SAMPLED; const flags = sampled ? '01' : '00'; appendHeader(response, 'Access-Control-Expose-Headers', 'Server-Timing'); appendHeader(response, 'Server-Timing', `traceparent;desc="00-${traceId}-${spanId}-${flags}"`); }; if (config.responseHook === undefined) { config.responseHook = responseHook; } else { const original = config.responseHook; config.responseHook = function (span, response) { responseHook(span, response); original.call(this, span, response); }; } instrumentation.setConfig(config); } function configureHttpDcInstrumentation(instrumentation, options) { const config = instrumentation.getConfig(); if (shouldAddRequestHook(options)) { const requestHook = createHttpRequestHook(options); if (config.requestHook === undefined) { config.requestHook = requestHook; } else { const original = config.requestHook; config.requestHook = function (span, request) { requestHook(span, request); original.call(this, span, request); }; } } if (!options.serverTimingEnabled) { instrumentation.setConfig(config); return; } const responseHook = (span, response) => { if (!(response instanceof http_1.ServerResponse)) { return; } const spanContext = span.spanContext(); if (!(0, api_1.isSpanContextValid)(spanContext)) { return; } const { traceFlags, traceId, spanId } = spanContext; const sampled = (traceFlags & api_1.TraceFlags.SAMPLED) === api_1.TraceFlags.SAMPLED; const flags = sampled ? '01' : '00'; appendHeader(response, 'Access-Control-Expose-Headers', 'Server-Timing'); appendHeader(response, 'Server-Timing', `traceparent;desc="00-${traceId}-${spanId}-${flags}"`); }; if (config.responseHook === undefined) { config.responseHook = responseHook; } else { const original = config.responseHook; config.responseHook = function (span, response) { responseHook(span, response); original.call(this, span, response); }; } instrumentation.setConfig(config); } function appendHeader(response, header, value) { const existing = response.getHeader(header); if (existing === undefined) { response.setHeader(header, value); return; } if (typeof existing === 'string') { response.setHeader(header, `${existing}, ${value}`); return; } if (Array.isArray(existing)) { existing.push(value); } } //# sourceMappingURL=http.js.map