@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.
669 lines • 32 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.getOutgoingStableRequestMetricAttributesOnResponse = exports.getOutgoingRequestMetricAttributesOnResponse = exports.getOutgoingRequestMetricAttributes = exports.getIncomingRequestMetricAttributesOnResponse = exports.getIncomingRequestMetricAttributes = exports.getIncomingStableRequestMetricAttributesOnResponse = exports.getIncomingRequestAttributesOnResponse = exports.getIncomingRequestAttributes = exports.getOutgoingRequestAttributesOnResponse = exports.setAttributesFromHttpKind = exports.isCompressed = exports.setRequestContentLengthAttribute = exports.setSpanWithError = void 0;
exports.getAbsoluteUrl = getAbsoluteUrl;
exports.parseResponseStatus = parseResponseStatus;
exports.getOutgoingRequestAttributes = getOutgoingRequestAttributes;
exports.getRemoteClientAddress = getRemoteClientAddress;
exports.headerCapture = headerCapture;
/*
* Copyright Splunk Inc., The OpenTelemetry Authors
*
* 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.
*/
const api_1 = require("@opentelemetry/api");
const semantic_conventions_1 = require("@opentelemetry/semantic-conventions");
const core_1 = require("@opentelemetry/core");
const internal_types_1 = require("./internal-types");
const forwardedParse = require("forwarded-parse");
const semconv_1 = require("./semconv");
const instrumentation_1 = require("@opentelemetry/instrumentation");
/**
* Get an absolute url
*/
function getAbsoluteUrl(req, redactedQueryParams = Array.from(internal_types_1.DEFAULT_QUERY_STRINGS_TO_REDACT)) {
var _a;
const port = (_a = req.socket) === null || _a === void 0 ? void 0 : _a.remotePort;
let path = req.path || '/';
const isDefaultPort = port === 80 || port === 443;
if (path.includes('?')) {
const [pathname, query] = path.split('?', 2);
const searchParams = new URLSearchParams(query);
const sensitiveParamsToRedact = redactedQueryParams || [];
for (const sensitiveParam of sensitiveParamsToRedact) {
if (searchParams.has(sensitiveParam) &&
searchParams.get(sensitiveParam) !== '') {
searchParams.set(sensitiveParam, internal_types_1.STR_REDACTED);
}
}
const redactedQuery = searchParams.toString();
path = `${pathname}?${redactedQuery}`;
}
let authPart = '';
const hasAuth = Boolean(req.getHeader('authorization'));
if (hasAuth) {
authPart = `${internal_types_1.STR_REDACTED}:${internal_types_1.STR_REDACTED}@`;
}
const protocol = req.protocol || 'http:';
const base = `${protocol}//${authPart}${req.host}`;
return isDefaultPort ? `${base}${path}` : `${base}:${port}${path}`;
}
/**
* Parse status code from HTTP response. [More details](https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-http.md#status)
*/
function parseResponseStatus(upperBound, statusCode) {
// 1xx, 2xx, 3xx are OK on client and server
// 4xx is OK on server
if (statusCode && statusCode >= 100 && statusCode < upperBound) {
return api_1.SpanStatusCode.UNSET;
}
// All other codes are error
return api_1.SpanStatusCode.ERROR;
}
/**
* Sets the span with the error passed in params
* @param {Span} span the span that need to be set
* @param {Error} error error that will be set to span
* @param {SemconvStability} semconvStability determines which semconv version to use
*/
const setSpanWithError = (span, error, semconvStability) => {
const message = error.message;
if ((semconvStability & instrumentation_1.SemconvStability.OLD) === instrumentation_1.SemconvStability.OLD) {
span.setAttribute(semconv_1.HTTP_ERROR_NAME, error.name);
span.setAttribute(semconv_1.HTTP_ERROR_MESSAGE, message);
}
if ((semconvStability & instrumentation_1.SemconvStability.STABLE) ===
instrumentation_1.SemconvStability.STABLE) {
span.setAttribute(semantic_conventions_1.ATTR_ERROR_TYPE, error.name);
}
span.setStatus({ code: api_1.SpanStatusCode.ERROR, message });
span.recordException(error);
};
exports.setSpanWithError = setSpanWithError;
/**
* Adds attributes for request content-length and content-encoding HTTP headers
* @param { IncomingMessage } Request object whose headers will be analyzed
* @param { Attributes } Attributes object to be modified
*/
const setRequestContentLengthAttribute = (request, attributes) => {
const length = getContentLength(request.headers);
if (length === null)
return;
if ((0, exports.isCompressed)(request.headers)) {
attributes[semantic_conventions_1.SEMATTRS_HTTP_REQUEST_CONTENT_LENGTH] = length;
}
else {
attributes[semantic_conventions_1.SEMATTRS_HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED] = length;
}
};
exports.setRequestContentLengthAttribute = setRequestContentLengthAttribute;
function getContentLength(headers) {
const contentLengthHeader = headers['content-length'];
if (contentLengthHeader === undefined)
return null;
const contentLength = parseInt(contentLengthHeader, 10);
if (isNaN(contentLength))
return null;
return contentLength;
}
const isCompressed = (headers) => {
const encoding = headers['content-encoding'];
return !!encoding && encoding !== 'identity';
};
exports.isCompressed = isCompressed;
/**
* Returns attributes related to the kind of HTTP protocol used
* @param {string} [kind] Kind of HTTP protocol used: "1.0", "1.1", "2", "SPDY" or "QUIC".
*/
const setAttributesFromHttpKind = (kind, attributes) => {
if (kind) {
attributes[semantic_conventions_1.SEMATTRS_HTTP_FLAVOR] = kind;
if (kind.toUpperCase() !== 'QUIC') {
attributes[semantic_conventions_1.SEMATTRS_NET_TRANSPORT] = semantic_conventions_1.NETTRANSPORTVALUES_IP_TCP;
}
else {
attributes[semantic_conventions_1.SEMATTRS_NET_TRANSPORT] = semantic_conventions_1.NETTRANSPORTVALUES_IP_UDP;
}
}
};
exports.setAttributesFromHttpKind = setAttributesFromHttpKind;
function getOutgoingRequestAttributesOnResponseOldSemconv(request, response, redactedQueryParams) {
const socket = response.socket;
const attributes = {
[semantic_conventions_1.SEMATTRS_HTTP_URL]: getAbsoluteUrl(request, redactedQueryParams),
[semantic_conventions_1.SEMATTRS_NET_PEER_IP]: socket.remoteAddress,
[semantic_conventions_1.SEMATTRS_NET_PEER_PORT]: socket.remotePort,
[semantic_conventions_1.SEMATTRS_HTTP_STATUS_CODE]: response.statusCode,
[semconv_1.HTTP_STATUS_TEXT]: response.statusMessage || '',
[semantic_conventions_1.SEMATTRS_HTTP_FLAVOR]: response.httpVersion,
[semantic_conventions_1.SEMATTRS_NET_TRANSPORT]: semantic_conventions_1.NETTRANSPORTVALUES_IP_TCP,
};
const length = getContentLength(response.headers);
if (length === null) {
return attributes;
}
if ((0, exports.isCompressed)(response.headers)) {
attributes[semantic_conventions_1.SEMATTRS_HTTP_RESPONSE_CONTENT_LENGTH] = length;
}
else {
attributes[semantic_conventions_1.SEMATTRS_HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED] = length;
}
return attributes;
}
function getOutgoingRequestAttributesOnResponseNewSemconv(request, response, redactedQueryParams) {
const socket = response.socket;
return {
[semantic_conventions_1.ATTR_URL_FULL]: getAbsoluteUrl(request, redactedQueryParams),
[semantic_conventions_1.ATTR_HTTP_RESPONSE_STATUS_CODE]: response.statusCode,
[semantic_conventions_1.ATTR_NETWORK_PEER_ADDRESS]: socket.remoteAddress,
[semantic_conventions_1.ATTR_NETWORK_PEER_PORT]: socket.remotePort,
[semantic_conventions_1.ATTR_NETWORK_PROTOCOL_VERSION]: response.httpVersion,
};
}
/**
* Returns outgoing request attributes scoped to the response data
* @param {IncomingMessage} response the response object
* @param {SemconvStability} semconvStability determines which semconv version to use
*/
const getOutgoingRequestAttributesOnResponse = (request, response, semconvStability, redactedQueryParams) => {
if (semconvStability === instrumentation_1.SemconvStability.OLD) {
return getOutgoingRequestAttributesOnResponseOldSemconv(request, response, redactedQueryParams);
}
if (semconvStability === instrumentation_1.SemconvStability.STABLE) {
return getOutgoingRequestAttributesOnResponseNewSemconv(request, response, redactedQueryParams);
}
const oldAttributes = getOutgoingRequestAttributesOnResponseOldSemconv(request, response, redactedQueryParams);
const newAttributes = getOutgoingRequestAttributesOnResponseNewSemconv(request, response, redactedQueryParams);
return Object.assign(oldAttributes, newAttributes);
};
exports.getOutgoingRequestAttributesOnResponse = getOutgoingRequestAttributesOnResponse;
function getOutgoingRequestAttributesOldSemconv(request, redactedQueryParams) {
const userAgent = request.getHeader('user-agent');
const hostHeader = request.getHeader('host');
return {
[semantic_conventions_1.SEMATTRS_HTTP_METHOD]: request.method,
[semantic_conventions_1.SEMATTRS_HTTP_TARGET]: request.path || '/',
[semantic_conventions_1.SEMATTRS_NET_PEER_NAME]: request.host,
[semantic_conventions_1.SEMATTRS_HTTP_HOST]: hostHeader,
[semantic_conventions_1.SEMATTRS_HTTP_USER_AGENT]: userAgent,
[semantic_conventions_1.SEMATTRS_HTTP_URL]: getAbsoluteUrl(request, redactedQueryParams),
};
}
function getOutgoingRequestAttributesNewSemconv(request, enableSyntheticSourceDetection, redactedQueryParams) {
var _a;
let port;
const userAgent = request.getHeader('user-agent');
const hostHeader = request.getHeader('host');
if (typeof hostHeader === 'string') {
const portString = hostHeader.substring(hostHeader.indexOf(':') + 1);
port = Number(portString);
}
const method = (_a = request.method) !== null && _a !== void 0 ? _a : 'GET';
const normalizedMethod = normalizeMethod(method);
const attributes = {};
Object.assign(attributes, {
// Required attributes
[semantic_conventions_1.ATTR_HTTP_REQUEST_METHOD]: normalizedMethod,
[semantic_conventions_1.ATTR_SERVER_ADDRESS]: request.host,
[semantic_conventions_1.ATTR_SERVER_PORT]: port,
[semantic_conventions_1.ATTR_URL_FULL]: getAbsoluteUrl(request, redactedQueryParams),
[semantic_conventions_1.ATTR_USER_AGENT_ORIGINAL]: userAgent,
//[ATTR_URL_FULL]: `${request.protocol}//${request.host}${request.path}`,
// leaving out protocol version, it is not yet negotiated
// leaving out protocol name, it is only required when protocol version is set
// retries and redirects not supported
// Opt-in attributes left off for now
});
// conditionally required if request method required case normalization
if (method !== normalizedMethod) {
attributes[semantic_conventions_1.ATTR_HTTP_REQUEST_METHOD_ORIGINAL] = method;
}
if (enableSyntheticSourceDetection && userAgent) {
attributes[semconv_1.ATTR_USER_AGENT_SYNTHETIC_TYPE] = getSyntheticType(userAgent);
}
return attributes;
}
function getOutgoingRequestAttributes(request, semconvStability, redactedQueryParams, enableSyntheticSourceDetection) {
if (semconvStability === instrumentation_1.SemconvStability.OLD) {
return getOutgoingRequestAttributesOldSemconv(request, redactedQueryParams);
}
if (semconvStability === instrumentation_1.SemconvStability.STABLE) {
return getOutgoingRequestAttributesNewSemconv(request, enableSyntheticSourceDetection, redactedQueryParams);
}
return Object.assign(getOutgoingRequestAttributesOldSemconv(request, redactedQueryParams), getOutgoingRequestAttributesNewSemconv(request, enableSyntheticSourceDetection, redactedQueryParams));
}
function parseHostHeader(hostHeader, proto) {
const notIPv6 = !hostHeader.startsWith('[');
if (notIPv6) {
const firstColonIndex = hostHeader.indexOf(':');
// no semicolon implies ipv4 dotted syntax or host name without port
// x.x.x.x
// example.com
if (firstColonIndex === -1) {
if (proto === 'http') {
return { host: hostHeader, port: '80' };
}
if (proto === 'https') {
return { host: hostHeader, port: '443' };
}
return { host: hostHeader };
}
const secondColonIndex = hostHeader.indexOf(':', firstColonIndex + 1);
// single semicolon implies ipv4 dotted syntax or host name with port
// x.x.x.x:yyyy
// example.com:yyyy
if (secondColonIndex === -1) {
return {
host: hostHeader.substring(0, firstColonIndex),
port: hostHeader.substring(firstColonIndex + 1),
};
}
// if nothing above matches just return the host header
return { host: hostHeader };
}
// more than 2 parts implies ipv6 syntax with multiple colons
// [x:x:x:x:x:x:x:x]
// [x:x:x:x:x:x:x:x]:yyyy
const parts = hostHeader.split(':');
if (parts[parts.length - 1].endsWith(']')) {
if (proto === 'http') {
return { host: hostHeader, port: '80' };
}
if (proto === 'https') {
return { host: hostHeader, port: '443' };
}
}
else if (parts[parts.length - 2].endsWith(']')) {
return {
host: parts.slice(0, -1).join(':'),
port: parts[parts.length - 1],
};
}
return { host: hostHeader };
}
/**
* Get server.address and port according to http semconv 1.27
* https://github.com/open-telemetry/semantic-conventions/blob/bf0a2c1134f206f034408b201dbec37960ed60ec/docs/http/http-spans.md#setting-serveraddress-and-serverport-attributes
*/
function getServerAddress(request, component) {
const forwardedHeader = request.headers['forwarded'];
if (forwardedHeader) {
for (const entry of parseForwardedHeader(forwardedHeader)) {
if (entry.host) {
return parseHostHeader(entry.host, entry.proto);
}
}
}
const xForwardedHost = request.headers['x-forwarded-host'];
if (typeof xForwardedHost === 'string') {
if (typeof request.headers['x-forwarded-proto'] === 'string') {
return parseHostHeader(xForwardedHost, request.headers['x-forwarded-proto']);
}
if (Array.isArray(request.headers['x-forwarded-proto'])) {
return parseHostHeader(xForwardedHost, request.headers['x-forwarded-proto'][0]);
}
return parseHostHeader(xForwardedHost);
}
else if (Array.isArray(xForwardedHost) &&
typeof xForwardedHost[0] === 'string' &&
xForwardedHost[0].length > 0) {
if (typeof request.headers['x-forwarded-proto'] === 'string') {
return parseHostHeader(xForwardedHost[0], request.headers['x-forwarded-proto']);
}
if (Array.isArray(request.headers['x-forwarded-proto'])) {
return parseHostHeader(xForwardedHost[0], request.headers['x-forwarded-proto'][0]);
}
return parseHostHeader(xForwardedHost[0]);
}
const host = request.headers['host'];
if (typeof host === 'string' && host.length > 0) {
return parseHostHeader(host, component);
}
return null;
}
/**
* Get server.address and port according to http semconv 1.27
* https://github.com/open-telemetry/semantic-conventions/blob/bf0a2c1134f206f034408b201dbec37960ed60ec/docs/http/http-spans.md#setting-serveraddress-and-serverport-attributes
*/
function getRemoteClientAddress(request) {
const forwardedHeader = request.headers['forwarded'];
if (forwardedHeader) {
for (const entry of parseForwardedHeader(forwardedHeader)) {
if (entry.for) {
return entry.for;
}
}
}
const xForwardedFor = request.headers['x-forwarded-for'];
if (typeof xForwardedFor === 'string') {
return xForwardedFor;
}
else if (Array.isArray(xForwardedFor)) {
return xForwardedFor[0];
}
const remote = request.socket.remoteAddress;
if (remote) {
return remote;
}
return null;
}
function getInfoFromIncomingMessage(component, request, logger) {
var _a, _b;
try {
const host = request.headers.host;
if (host) {
return new URL((_a = request.url) !== null && _a !== void 0 ? _a : '/', `${component}://${host}`);
}
else {
const unsafeParsedUrl = new URL((_b = request.url) !== null && _b !== void 0 ? _b : '/',
// using localhost as a workaround to still use the URL constructor for parsing
`${component}://localhost`);
// since we use localhost as a workaround, ensure we hide the rest of the properties to avoid
// our workaround leaking though.
return {
pathname: unsafeParsedUrl.pathname,
search: unsafeParsedUrl.search,
toString: function () {
// we cannot use the result of unsafeParsedUrl.toString as it's potentially wrong.
return unsafeParsedUrl.pathname + unsafeParsedUrl.search;
},
};
}
}
catch (e) {
// something is wrong, use undefined - this *should* never happen, logging
// for troubleshooting in case it does happen.
logger.verbose('Unable to get URL from request', e);
return {};
}
}
/**
* Returns incoming request attributes scoped to the request data
* @param {IncomingMessage} request the request object
* @param {{ component: string, serverName?: string, hookAttributes?: Attributes }} options used to pass data needed to create attributes
* @param {SemconvStability} semconvStability determines which semconv version to use
*/
const getIncomingRequestAttributes = (request, options, logger) => {
const headers = request.headers;
const userAgent = headers['user-agent'];
const ips = headers['x-forwarded-for'];
const httpVersion = request.httpVersion;
const host = headers.host || 'localhost';
const hostnameEnd = host.lastIndexOf(':');
const hostname = hostnameEnd >= 0 ? host.substring(0, hostnameEnd) : host;
const method = request.method;
const normalizedMethod = normalizeMethod(method);
const scheme =
// eslint-disable-next-line @typescript-eslint/no-explicit-any
request.socket['encrypted'] === true ? 'https' : 'http';
const serverAddress = getServerAddress(request, scheme);
const serverName = options.serverName;
const remoteClientAddress = getRemoteClientAddress(request);
const newAttributes = {
[semantic_conventions_1.ATTR_HTTP_REQUEST_METHOD]: normalizedMethod,
[semantic_conventions_1.ATTR_URL_SCHEME]: scheme,
[semantic_conventions_1.ATTR_SERVER_ADDRESS]: serverAddress === null || serverAddress === void 0 ? void 0 : serverAddress.host,
[semantic_conventions_1.ATTR_NETWORK_PEER_ADDRESS]: request.socket.remoteAddress,
[semantic_conventions_1.ATTR_NETWORK_PEER_PORT]: request.socket.remotePort,
[semantic_conventions_1.ATTR_NETWORK_PROTOCOL_VERSION]: request.httpVersion,
[semantic_conventions_1.ATTR_USER_AGENT_ORIGINAL]: userAgent,
};
const parsedUrl = getInfoFromIncomingMessage(scheme, request, logger);
if ((parsedUrl === null || parsedUrl === void 0 ? void 0 : parsedUrl.pathname) !== null) {
newAttributes[semantic_conventions_1.ATTR_URL_PATH] = parsedUrl.pathname;
}
if (remoteClientAddress !== null) {
newAttributes[semantic_conventions_1.ATTR_CLIENT_ADDRESS] = remoteClientAddress;
}
if (serverAddress && serverAddress.port !== null) {
newAttributes[semantic_conventions_1.ATTR_SERVER_PORT] = Number(serverAddress.port);
}
// conditionally required if request method required case normalization
if (method !== normalizedMethod) {
newAttributes[semantic_conventions_1.ATTR_HTTP_REQUEST_METHOD_ORIGINAL] = method;
}
if (options.enableSyntheticSourceDetection && userAgent) {
newAttributes[semconv_1.ATTR_USER_AGENT_SYNTHETIC_TYPE] = getSyntheticType(userAgent);
}
const oldAttributes = {
[semantic_conventions_1.SEMATTRS_HTTP_URL]: parsedUrl.toString(),
[semantic_conventions_1.SEMATTRS_HTTP_HOST]: host,
[semantic_conventions_1.SEMATTRS_NET_HOST_NAME]: hostname,
[semantic_conventions_1.SEMATTRS_HTTP_METHOD]: method,
[semantic_conventions_1.SEMATTRS_HTTP_SCHEME]: scheme,
};
if (typeof ips === 'string') {
oldAttributes[semantic_conventions_1.SEMATTRS_HTTP_CLIENT_IP] = ips.split(',')[0];
}
if (typeof serverName === 'string') {
oldAttributes[semantic_conventions_1.SEMATTRS_HTTP_SERVER_NAME] = serverName;
}
if (parsedUrl === null || parsedUrl === void 0 ? void 0 : parsedUrl.pathname) {
oldAttributes[semantic_conventions_1.SEMATTRS_HTTP_TARGET] =
(parsedUrl === null || parsedUrl === void 0 ? void 0 : parsedUrl.pathname) + (parsedUrl === null || parsedUrl === void 0 ? void 0 : parsedUrl.search) || '/';
}
if (userAgent !== undefined) {
oldAttributes[semantic_conventions_1.SEMATTRS_HTTP_USER_AGENT] = userAgent;
}
(0, exports.setRequestContentLengthAttribute)(request, oldAttributes);
(0, exports.setAttributesFromHttpKind)(httpVersion, oldAttributes);
switch (options.semconvStability) {
case instrumentation_1.SemconvStability.STABLE:
return Object.assign(newAttributes, options.hookAttributes);
case instrumentation_1.SemconvStability.OLD:
return Object.assign(oldAttributes, options.hookAttributes);
}
return Object.assign(oldAttributes, newAttributes, options.hookAttributes);
};
exports.getIncomingRequestAttributes = getIncomingRequestAttributes;
/**
* Returns incoming request attributes scoped to the response data
* @param {(ServerResponse & { socket: Socket; })} response the response object
*/
const getIncomingRequestAttributesOnResponse = (request, response, semconvStability) => {
// take socket from the request,
// since it may be detached from the response object in keep-alive mode
const { socket } = request;
const { statusCode, statusMessage } = response;
const newAttributes = {
[semantic_conventions_1.ATTR_HTTP_RESPONSE_STATUS_CODE]: statusCode,
};
const rpcMetadata = (0, core_1.getRPCMetadata)(api_1.context.active());
const oldAttributes = {};
if (socket) {
const { localAddress, localPort, remoteAddress, remotePort } = socket;
oldAttributes[semantic_conventions_1.SEMATTRS_NET_HOST_IP] = localAddress;
oldAttributes[semantic_conventions_1.SEMATTRS_NET_HOST_PORT] = localPort;
oldAttributes[semantic_conventions_1.SEMATTRS_NET_PEER_IP] = remoteAddress;
oldAttributes[semantic_conventions_1.SEMATTRS_NET_PEER_PORT] = remotePort;
}
oldAttributes[semantic_conventions_1.SEMATTRS_HTTP_STATUS_CODE] = statusCode;
oldAttributes[semconv_1.HTTP_STATUS_TEXT] = (statusMessage || '').toUpperCase();
if ((rpcMetadata === null || rpcMetadata === void 0 ? void 0 : rpcMetadata.type) === core_1.RPCType.HTTP && rpcMetadata.route !== undefined) {
oldAttributes[semantic_conventions_1.SEMATTRS_HTTP_ROUTE] = rpcMetadata.route;
newAttributes[semantic_conventions_1.ATTR_HTTP_ROUTE] = rpcMetadata.route;
}
switch (semconvStability) {
case instrumentation_1.SemconvStability.STABLE:
return newAttributes;
case instrumentation_1.SemconvStability.OLD:
return oldAttributes;
}
return Object.assign(oldAttributes, newAttributes);
};
exports.getIncomingRequestAttributesOnResponse = getIncomingRequestAttributesOnResponse;
/**
* Returns incoming request Metric attributes scoped to the request data
* @param {Attributes} spanAttributes the span attributes
*/
const getIncomingStableRequestMetricAttributesOnResponse = (spanAttributes) => {
const metricAttributes = {};
if (spanAttributes[semantic_conventions_1.ATTR_HTTP_ROUTE] !== undefined) {
metricAttributes[semantic_conventions_1.ATTR_HTTP_ROUTE] = spanAttributes[semantic_conventions_1.ATTR_HTTP_ROUTE];
}
// required if and only if one was sent, same as span requirement
if (spanAttributes[semantic_conventions_1.ATTR_HTTP_RESPONSE_STATUS_CODE]) {
metricAttributes[semantic_conventions_1.ATTR_HTTP_RESPONSE_STATUS_CODE] =
spanAttributes[semantic_conventions_1.ATTR_HTTP_RESPONSE_STATUS_CODE];
}
return metricAttributes;
};
exports.getIncomingStableRequestMetricAttributesOnResponse = getIncomingStableRequestMetricAttributesOnResponse;
function headerCapture(type, headers) {
const normalizedHeaders = new Map();
for (let i = 0, len = headers.length; i < len; i++) {
const capturedHeader = headers[i].toLowerCase();
normalizedHeaders.set(capturedHeader, capturedHeader.replace(/-/g, '_'));
}
return (span, getHeader) => {
for (const capturedHeader of normalizedHeaders.keys()) {
const value = getHeader(capturedHeader);
if (value === undefined) {
continue;
}
const normalizedHeader = normalizedHeaders.get(capturedHeader);
const key = `http.${type}.header.${normalizedHeader}`;
if (typeof value === 'string') {
span.setAttribute(key, [value]);
}
else if (Array.isArray(value)) {
span.setAttribute(key, value);
}
else {
span.setAttribute(key, [value]);
}
}
};
}
const KNOWN_METHODS = new Set([
// methods from https://www.rfc-editor.org/rfc/rfc9110.html#name-methods
'GET',
'HEAD',
'POST',
'PUT',
'DELETE',
'CONNECT',
'OPTIONS',
'TRACE',
// PATCH from https://www.rfc-editor.org/rfc/rfc5789.html
'PATCH',
]);
function normalizeMethod(method) {
if (!method) {
return 'GET';
}
const upper = method.toUpperCase();
if (KNOWN_METHODS.has(upper)) {
return upper;
}
return '_OTHER';
}
function parseForwardedHeader(header) {
try {
return forwardedParse(header);
}
catch (_a) {
return [];
}
}
/**
* Returns incoming request Metric attributes scoped to the request data
* @param {Attributes} spanAttributes the span attributes
* @param {{ component: string }} options used to pass data needed to create attributes
*/
const getIncomingRequestMetricAttributes = (spanAttributes) => {
const metricAttributes = {};
metricAttributes[semantic_conventions_1.SEMATTRS_HTTP_SCHEME] = spanAttributes[semantic_conventions_1.SEMATTRS_HTTP_SCHEME];
metricAttributes[semantic_conventions_1.SEMATTRS_HTTP_METHOD] = spanAttributes[semantic_conventions_1.SEMATTRS_HTTP_METHOD];
metricAttributes[semantic_conventions_1.SEMATTRS_NET_HOST_NAME] =
spanAttributes[semantic_conventions_1.SEMATTRS_NET_HOST_NAME];
metricAttributes[semantic_conventions_1.SEMATTRS_HTTP_FLAVOR] = spanAttributes[semantic_conventions_1.SEMATTRS_HTTP_FLAVOR];
//TODO: http.target attribute, it should substitute any parameters to avoid high cardinality.
return metricAttributes;
};
exports.getIncomingRequestMetricAttributes = getIncomingRequestMetricAttributes;
/**
* Returns incoming request Metric attributes scoped to the request data
* @param {Attributes} spanAttributes the span attributes
*/
const getIncomingRequestMetricAttributesOnResponse = (spanAttributes) => {
const metricAttributes = {};
metricAttributes[semantic_conventions_1.SEMATTRS_HTTP_STATUS_CODE] =
spanAttributes[semantic_conventions_1.SEMATTRS_HTTP_STATUS_CODE];
metricAttributes[semantic_conventions_1.SEMATTRS_NET_HOST_PORT] =
spanAttributes[semantic_conventions_1.SEMATTRS_NET_HOST_PORT];
if (spanAttributes[semantic_conventions_1.ATTR_HTTP_ROUTE] !== undefined) {
metricAttributes[semantic_conventions_1.ATTR_HTTP_ROUTE] = spanAttributes[semantic_conventions_1.ATTR_HTTP_ROUTE];
}
return metricAttributes;
};
exports.getIncomingRequestMetricAttributesOnResponse = getIncomingRequestMetricAttributesOnResponse;
/**
* Returns the type of synthetic source based on the user agent
* @param {OutgoingHttpHeader} userAgent the user agent string
*/
const getSyntheticType = (userAgent) => {
const userAgentString = String(userAgent).toLowerCase();
for (const name of internal_types_1.SYNTHETIC_TEST_NAMES) {
if (userAgentString.includes(name)) {
return semconv_1.USER_AGENT_SYNTHETIC_TYPE_VALUE_TEST;
}
}
for (const name of internal_types_1.SYNTHETIC_BOT_NAMES) {
if (userAgentString.includes(name)) {
return semconv_1.USER_AGENT_SYNTHETIC_TYPE_VALUE_BOT;
}
}
return;
};
/**
* Returns outgoing request Metric attributes scoped to the request data
* @param {Attributes} spanAttributes the span attributes
*/
const getOutgoingRequestMetricAttributes = (spanAttributes) => {
const metricAttributes = {};
metricAttributes[semantic_conventions_1.SEMATTRS_HTTP_METHOD] = spanAttributes[semantic_conventions_1.SEMATTRS_HTTP_METHOD];
metricAttributes[semantic_conventions_1.SEMATTRS_NET_PEER_NAME] =
spanAttributes[semantic_conventions_1.SEMATTRS_NET_PEER_NAME];
//TODO: http.url attribute, it should substitute any parameters to avoid high cardinality.
return metricAttributes;
};
exports.getOutgoingRequestMetricAttributes = getOutgoingRequestMetricAttributes;
/**
* Returns outgoing request Metric attributes scoped to the response data
* @param {Attributes} spanAttributes the span attributes
*/
const getOutgoingRequestMetricAttributesOnResponse = (spanAttributes) => {
const metricAttributes = {};
metricAttributes[semantic_conventions_1.SEMATTRS_NET_PEER_PORT] =
spanAttributes[semantic_conventions_1.SEMATTRS_NET_PEER_PORT];
metricAttributes[semantic_conventions_1.SEMATTRS_HTTP_STATUS_CODE] =
spanAttributes[semantic_conventions_1.SEMATTRS_HTTP_STATUS_CODE];
metricAttributes[semantic_conventions_1.SEMATTRS_HTTP_FLAVOR] = spanAttributes[semantic_conventions_1.SEMATTRS_HTTP_FLAVOR];
return metricAttributes;
};
exports.getOutgoingRequestMetricAttributesOnResponse = getOutgoingRequestMetricAttributesOnResponse;
const getOutgoingStableRequestMetricAttributesOnResponse = (spanAttributes) => {
const metricAttributes = {};
if (spanAttributes[semantic_conventions_1.ATTR_NETWORK_PROTOCOL_VERSION]) {
metricAttributes[semantic_conventions_1.ATTR_NETWORK_PROTOCOL_VERSION] =
spanAttributes[semantic_conventions_1.ATTR_NETWORK_PROTOCOL_VERSION];
}
if (spanAttributes[semantic_conventions_1.ATTR_HTTP_RESPONSE_STATUS_CODE]) {
metricAttributes[semantic_conventions_1.ATTR_HTTP_RESPONSE_STATUS_CODE] =
spanAttributes[semantic_conventions_1.ATTR_HTTP_RESPONSE_STATUS_CODE];
}
return metricAttributes;
};
exports.getOutgoingStableRequestMetricAttributesOnResponse = getOutgoingStableRequestMetricAttributesOnResponse;
//# sourceMappingURL=utils.js.map