@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
JavaScript
;
/*
* 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