UNPKG

@instana/core

Version:
186 lines (154 loc) 5.38 kB
/* * (c) Copyright IBM Corp. 2021 * (c) Copyright Instana Inc. and contributors 2020 */ 'use strict'; const http2 = require('http2'); const cls = require('../../cls'); const constants = require('../../constants'); const { getExtraHeadersCaseInsensitive, mergeExtraHeadersFromNormalizedObjectLiteral } = require('./captureHttpHeadersUtil'); const readSymbolProperty = require('../../../util/readSymbolProperty'); const tracingUtil = require('../../tracingUtil'); const { sanitizeUrl, splitAndFilter } = require('../../../util/url'); let extraHttpHeadersToCapture; let isActive = false; const originS = 'Symbol(origin)'; const sentHeadersS = 'Symbol(sent-headers)'; const HTTP2_HEADER_METHOD = http2.constants.HTTP2_HEADER_METHOD; const HTTP2_HEADER_PATH = http2.constants.HTTP2_HEADER_PATH; const HTTP2_HEADER_STATUS = http2.constants.HTTP2_HEADER_STATUS; exports.init = function init(config) { instrument(http2); extraHttpHeadersToCapture = config.tracing.http.extraHttpHeadersToCapture; }; exports.updateConfig = config => { extraHttpHeadersToCapture = config.tracing.http.extraHttpHeadersToCapture; }; exports.activate = function activate(extraConfig) { if ( extraConfig && extraConfig.tracing && extraConfig.tracing.http && Array.isArray(extraConfig.tracing.http.extraHttpHeadersToCapture) ) { extraHttpHeadersToCapture = extraConfig.tracing.http.extraHttpHeadersToCapture; } isActive = true; }; exports.deactivate = function deactivate() { isActive = false; }; function instrument(coreModule) { const originalConnect = coreModule.connect; coreModule.connect = function connect() { const clientHttp2Session = originalConnect.apply(this, arguments); instrumentClientHttp2Session(clientHttp2Session); return clientHttp2Session; }; } function instrumentClientHttp2Session(clientHttp2Session) { const originalRequest = clientHttp2Session.request; clientHttp2Session.request = function request(headers) { let w3cTraceContext = cls.getW3cTraceContext(); const skipTracingResult = cls.skipExitTracing({ isActive, extendedResponse: true, checkReducedSpan: true }); if (skipTracingResult.skip) { if (skipTracingResult.suppressed) { addTraceLevelHeader(headers, '0', w3cTraceContext); } return originalRequest.apply(this, arguments); } const parentSpan = skipTracingResult.parentSpan; const originalThis = this; const originalArgs = new Array(arguments.length); for (let i = 0; i < arguments.length; i++) { originalArgs[i] = arguments[i]; } return cls.ns.runAndReturn(() => { const span = cls.startSpan({ spanName: 'node.http.client', kind: constants.EXIT, traceId: parentSpan?.t, parentSpanId: parentSpan?.s }); // startSpan updates the W3C trace context and writes it back to CLS, so we have to refetch the updated context // object from CLS. w3cTraceContext = cls.getW3cTraceContext(); addHeaders(headers, span, w3cTraceContext); const stream = originalRequest.apply(originalThis, originalArgs); const origin = readSymbolProperty(stream, originS); const reqHeaders = readSymbolProperty(stream, sentHeadersS); let capturedHeaders = getExtraHeadersCaseInsensitive(reqHeaders, extraHttpHeadersToCapture); let method; let path; let status; if (reqHeaders) { method = reqHeaders[HTTP2_HEADER_METHOD]; path = reqHeaders[HTTP2_HEADER_PATH]; } method = method || 'GET'; path = path || '/'; const pathWithoutQuery = sanitizeUrl(path); const params = splitAndFilter(path); span.stack = tracingUtil.getStackTrace(request); span.data.http = { method, url: origin + pathWithoutQuery, params }; stream.on('response', resHeaders => { status = resHeaders[HTTP2_HEADER_STATUS]; capturedHeaders = mergeExtraHeadersFromNormalizedObjectLiteral( capturedHeaders, resHeaders, extraHttpHeadersToCapture ); }); stream.on('end', () => { span.d = Date.now() - span.ts; span.ec = status >= 500 ? 1 : 0; span.data.http.status = status; if (capturedHeaders) { span.data.http.header = capturedHeaders; } span.transmit(); }); return stream; }); }; } function addTraceLevelHeader(headers, level, w3cTraceContext) { if (!headers) { return; } headers[constants.traceLevelHeaderName] = level; addW3cHeaders(headers, w3cTraceContext); } function addHeaders(headers, span, w3cTraceContext) { if (!headers) { return; } if (span.shouldSuppressDownstream) { // Suppress trace propagation to downstream services. addTraceLevelHeader(headers, '0', w3cTraceContext); return; } headers[constants.spanIdHeaderName] = span.s; headers[constants.traceIdHeaderName] = span.t; headers[constants.traceLevelHeaderName] = '1'; addW3cHeaders(headers, w3cTraceContext); } function addW3cHeaders(headers, w3cTraceContext) { if (w3cTraceContext) { headers[constants.w3cTraceParent] = w3cTraceContext.renderTraceParent(); if (w3cTraceContext.hasTraceState()) { headers[constants.w3cTraceState] = w3cTraceContext.renderTraceState(); } } }