UNPKG

@azure/monitor-opentelemetry

Version:
564 lines 23.8 kB
"use strict"; // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. Object.defineProperty(exports, "__esModule", { value: true }); exports.getSdkVersion = getSdkVersion; exports.getSdkVersionType = getSdkVersionType; exports.setSdkPrefix = setSdkPrefix; exports.resourceMetricsToQuickpulseDataPoint = resourceMetricsToQuickpulseDataPoint; exports.getSpanData = getSpanData; exports.getSpanExceptionColumns = getSpanExceptionColumns; exports.getLogData = getLogData; exports.getLogDocument = getLogDocument; exports.isRequestData = isRequestData; exports.isDependencyData = isDependencyData; exports.isTraceData = isTraceData; exports.isExceptionData = isExceptionData; exports.getSpanDocument = getSpanDocument; exports.getTransmissionTime = getTransmissionTime; exports.getMsFromFilterTimestampString = getMsFromFilterTimestampString; exports.getPeerIp = getPeerIp; exports.getUserAgent = getUserAgent; exports.getHttpUrl = getHttpUrl; exports.getHttpMethod = getHttpMethod; exports.getHttpStatusCode = getHttpStatusCode; exports.getHttpScheme = getHttpScheme; exports.getHttpTarget = getHttpTarget; exports.getHttpHost = getHttpHost; exports.getNetPeerName = getNetPeerName; exports.getNetHostPort = getNetHostPort; exports.getNetPeerPort = getNetPeerPort; const index_js_1 = require("../../generated/index.js"); const api_1 = require("@opentelemetry/api"); const semantic_conventions_1 = require("@opentelemetry/semantic-conventions"); const core_1 = require("@opentelemetry/core"); const sdk_metrics_1 = require("@opentelemetry/sdk-metrics"); const types_js_1 = require("../../types.js"); const types_js_2 = require("./types.js"); const common_js_1 = require("../../utils/common.js"); const common_js_2 = require("../../utils/common.js"); const utils_js_1 = require("../utils.js"); const index_js_2 = require("../../shared/logging/index.js"); /** Get the internal SDK version */ function getSdkVersion() { var _a; const { nodeVersion } = process.versions; const opentelemetryVersion = core_1.SDK_INFO[semantic_conventions_1.SEMRESATTRS_TELEMETRY_SDK_VERSION]; const version = getSdkVersionType(); const internalSdkVersion = `${(_a = process.env[types_js_1.AZURE_MONITOR_PREFIX]) !== null && _a !== void 0 ? _a : ""}node${nodeVersion}:otel${opentelemetryVersion}:${version}`; return internalSdkVersion; } /** Get the internal SDK version type */ function getSdkVersionType() { if (process.env[types_js_1.APPLICATION_INSIGHTS_SHIM_VERSION]) { return `sha${process.env[types_js_1.APPLICATION_INSIGHTS_SHIM_VERSION]}`; } else { return `dst${types_js_1.AZURE_MONITOR_OPENTELEMETRY_VERSION}`; } } // eslint-disable-next-line tsdoc/syntax /** Set the version prefix to a string in the format "{ResourceProvider}{OS}m_ */ function setSdkPrefix() { if (!process.env[types_js_1.AZURE_MONITOR_PREFIX]) { const prefixAttachType = process.env[types_js_1.AZURE_MONITOR_AUTO_ATTACH] === "true" ? types_js_1.AttachTypePrefix.INTEGRATED_AUTO : types_js_1.AttachTypePrefix.MANUAL; process.env[types_js_1.AZURE_MONITOR_PREFIX] = `${(0, common_js_2.getResourceProvider)()}${(0, common_js_1.getOsPrefix)()}${prefixAttachType}_`; } } function resourceMetricsToQuickpulseDataPoint(metrics, baseMonitoringDataPoint, documents, errors, derivedMetricValues) { const metricPoints = []; metrics.scopeMetrics.forEach((scopeMetric) => { scopeMetric.metrics.forEach((metric) => { metric.dataPoints.forEach((dataPoint) => { const metricPoint = { weight: 1, name: "", value: 0, }; // Update name to expected value in Quickpulse, needed because those names are invalid in OTel switch (metric.descriptor.name) { case types_js_2.QuickPulseOpenTelemetryMetricNames.PHYSICAL_BYTES: metricPoint.name = types_js_2.QuickPulseMetricNames.PHYSICAL_BYTES; break; case types_js_2.QuickPulseOpenTelemetryMetricNames.DEPENDENCY_DURATION: metricPoint.name = types_js_2.QuickPulseMetricNames.DEPENDENCY_DURATION; break; case types_js_2.QuickPulseOpenTelemetryMetricNames.DEPENDENCY_FAILURE_RATE: metricPoint.name = types_js_2.QuickPulseMetricNames.DEPENDENCY_FAILURE_RATE; break; case types_js_2.QuickPulseOpenTelemetryMetricNames.DEPENDENCY_RATE: metricPoint.name = types_js_2.QuickPulseMetricNames.DEPENDENCY_RATE; break; case types_js_2.QuickPulseOpenTelemetryMetricNames.EXCEPTION_RATE: metricPoint.name = types_js_2.QuickPulseMetricNames.EXCEPTION_RATE; break; case types_js_2.QuickPulseOpenTelemetryMetricNames.PROCESSOR_TIME_NORMALIZED: metricPoint.name = types_js_2.QuickPulseMetricNames.PROCESSOR_TIME_NORMALIZED; break; case types_js_2.QuickPulseOpenTelemetryMetricNames.REQUEST_DURATION: metricPoint.name = types_js_2.QuickPulseMetricNames.REQUEST_DURATION; break; case types_js_2.QuickPulseOpenTelemetryMetricNames.REQUEST_FAILURE_RATE: metricPoint.name = types_js_2.QuickPulseMetricNames.REQUEST_FAILURE_RATE; break; case types_js_2.QuickPulseOpenTelemetryMetricNames.REQUEST_RATE: metricPoint.name = types_js_2.QuickPulseMetricNames.REQUEST_RATE; break; default: metricPoint.name = metric.descriptor.name; } if (metric.dataPointType === sdk_metrics_1.DataPointType.SUM || metric.dataPointType === sdk_metrics_1.DataPointType.GAUGE) { metricPoint.value = dataPoint.value; } else { metricPoint.value = dataPoint.value.sum || 0; } metricPoints.push(metricPoint); // TODO: remove the metric points with the old metric names after // UI side has done their changes to support the new names. if (metricPoint.name === types_js_2.QuickPulseMetricNames.PHYSICAL_BYTES || metricPoint.name === types_js_2.QuickPulseMetricNames.PROCESSOR_TIME_NORMALIZED) { const oldMetricPoint = { weight: 1, name: metricPoint.name === types_js_2.QuickPulseMetricNames.PHYSICAL_BYTES ? types_js_2.QuickPulseMetricNames.COMMITTED_BYTES : types_js_2.QuickPulseMetricNames.PROCESSOR_TIME, value: dataPoint.value, }; metricPoints.push(oldMetricPoint); } }); }); }); derivedMetricValues.forEach((value, id) => { const metricPoint = { weight: 1, name: id, value: value, }; metricPoints.push(metricPoint); }); const quickpulseDataPoint = Object.assign(Object.assign({}, baseMonitoringDataPoint), { timestamp: new Date(), metrics: metricPoints, documents: documents, collectionConfigurationErrors: errors }); return [quickpulseDataPoint]; } function getIso8601Duration(milliseconds) { const seconds = milliseconds / 1000; return `PT${seconds}S`; } function getSpanData(span) { if (span.kind === api_1.SpanKind.SERVER || span.kind === api_1.SpanKind.CONSUMER) { // request return getRequestData(span); } else { // dependency return getDependencyData(span); } } function getSpanExceptionColumns(eventAttributes, spanAttributes) { const exceptionData = { Message: eventAttributes[semantic_conventions_1.SEMATTRS_EXCEPTION_MESSAGE], StackTrace: eventAttributes[semantic_conventions_1.SEMATTRS_EXCEPTION_STACKTRACE], CustomDimensions: createCustomDimsFromAttributes(spanAttributes), }; return exceptionData; } // A slightly modified version of createRequestData from spanUtils in exporter function getRequestData(span) { const requestData = { Url: "", Duration: (0, core_1.hrTimeToMilliseconds)(span.duration), ResponseCode: 0, Success: false, Name: span.name || "", CustomDimensions: createCustomDimsFromAttributes(span.attributes), }; const httpMethod = getHttpMethod(span.attributes); const grpcStatusCode = span.attributes[semantic_conventions_1.SEMATTRS_RPC_GRPC_STATUS_CODE]; if (httpMethod) { requestData.Url = getUrl(span.attributes); if (URL.canParse(requestData.Url)) { const urlObj = new URL(requestData.Url); requestData.Name = `${httpMethod} ${urlObj.pathname}`; } else { index_js_2.Logger.getInstance().info("Request data sent to live metrics has no valid URL field."); } const httpStatusCode = getHttpStatusCode(span.attributes); if (httpStatusCode) { requestData.ResponseCode = Number(httpStatusCode); } } else if (grpcStatusCode) { requestData.ResponseCode = Number(grpcStatusCode); } requestData.Success = span.status.code !== api_1.SpanStatusCode.ERROR && requestData.ResponseCode < 400; return requestData; } // A slightly modified version of createDependencyData from spanUtils in exporter function getDependencyData(span) { const dependencyData = { Target: "", Duration: (0, core_1.hrTimeToMilliseconds)(span.duration), Success: span.status.code !== api_1.SpanStatusCode.ERROR, Name: span.name, ResultCode: 0, Type: "", Data: "", CustomDimensions: createCustomDimsFromAttributes(span.attributes), }; if (span.kind === api_1.SpanKind.PRODUCER) { dependencyData.Type = types_js_2.DependencyTypes.QueueMessage; } if (span.kind === api_1.SpanKind.INTERNAL && span.parentSpanId) { dependencyData.Type = types_js_2.DependencyTypes.InProc; } const httpMethod = getHttpMethod(span.attributes); const dbSystem = span.attributes[semantic_conventions_1.SEMATTRS_DB_SYSTEM]; const rpcSystem = span.attributes[semantic_conventions_1.SEMATTRS_RPC_SYSTEM]; // HTTP Dependency if (httpMethod) { const httpUrl = getHttpUrl(span.attributes); if (httpUrl) { if (URL.canParse(String(httpUrl))) { const dependencyUrl = new URL(String(httpUrl)); dependencyData.Name = `${httpMethod} ${dependencyUrl.pathname}`; } else { index_js_2.Logger.getInstance().info("Dependency data sent to live metrics has no valid URL field."); } } dependencyData.Type = types_js_2.DependencyTypes.Http; dependencyData.Data = getUrl(span.attributes); const httpStatusCode = getHttpStatusCode(span.attributes); if (httpStatusCode) { dependencyData.ResultCode = Number(httpStatusCode); } let target = (0, utils_js_1.getDependencyTarget)(span.attributes); if (target) { try { // Remove default port const portRegex = new RegExp(/(https?)(:\/\/.*)(:\d+)(\S*)/); const res = portRegex.exec(target); if (res !== null) { const protocol = res[1]; const port = res[3]; if ((protocol === "https" && port === ":443") || (protocol === "http" && port === ":80")) { // Drop port target = res[1] + res[2] + res[4]; } } } catch (ex) { /* no-op */ } dependencyData.Target = `${target}`; } } // DB Dependency else if (dbSystem) { // TODO: Remove special logic when Azure UX supports OpenTelemetry dbSystem if (String(dbSystem) === semantic_conventions_1.DBSYSTEMVALUES_MYSQL) { dependencyData.Type = types_js_2.DependencyTypes.mysql; } else if (String(dbSystem) === semantic_conventions_1.DBSYSTEMVALUES_POSTGRESQL) { dependencyData.Type = types_js_2.DependencyTypes.postgresql; } else if (String(dbSystem) === semantic_conventions_1.DBSYSTEMVALUES_MONGODB) { dependencyData.Type = types_js_2.DependencyTypes.mongodb; } else if (String(dbSystem) === semantic_conventions_1.DBSYSTEMVALUES_REDIS) { dependencyData.Type = types_js_2.DependencyTypes.redis; } else if ((0, utils_js_1.isSqlDB)(String(dbSystem))) { dependencyData.Type = types_js_2.DependencyTypes.Sql; } else { dependencyData.Type = String(dbSystem); } const dbStatement = span.attributes[semantic_conventions_1.SEMATTRS_DB_STATEMENT]; const dbOperation = span.attributes[semantic_conventions_1.SEMATTRS_DB_OPERATION]; if (dbStatement) { dependencyData.Data = String(dbStatement); } else if (dbOperation) { dependencyData.Data = String(dbOperation); } const target = (0, utils_js_1.getDependencyTarget)(span.attributes); const dbName = span.attributes[semantic_conventions_1.SEMATTRS_DB_NAME]; if (target) { dependencyData.Target = dbName ? `${target}|${dbName}` : `${target}`; } else { dependencyData.Target = dbName ? `${dbName}` : `${dbSystem}`; } } // grpc Dependency else if (rpcSystem) { if (rpcSystem === types_js_2.DependencyTypes.Wcf) { dependencyData.Type = types_js_2.DependencyTypes.Wcf; } else { dependencyData.Type = types_js_2.DependencyTypes.Grpc; } const grpcStatusCode = span.attributes[semantic_conventions_1.SEMATTRS_RPC_GRPC_STATUS_CODE]; if (grpcStatusCode) { dependencyData.ResultCode = Number(grpcStatusCode); } const target = (0, utils_js_1.getDependencyTarget)(span.attributes); if (target) { dependencyData.Target = `${target}`; } else if (rpcSystem) { dependencyData.Target = String(rpcSystem); } } return dependencyData; } function getLogData(log) { const customDims = createCustomDimsFromAttributes(log.attributes); if ((0, utils_js_1.isExceptionTelemetry)(log)) { return { // eslint-disable-next-line @typescript-eslint/no-base-to-string Message: String(log.attributes[semantic_conventions_1.SEMATTRS_EXCEPTION_MESSAGE]), // eslint-disable-next-line @typescript-eslint/no-base-to-string StackTrace: String(log.attributes[semantic_conventions_1.SEMATTRS_EXCEPTION_STACKTRACE]), CustomDimensions: customDims, }; } else { return { // eslint-disable-next-line @typescript-eslint/no-base-to-string Message: String(log.body), CustomDimensions: customDims, }; } } function getLogDocument(data, exceptionType) { if (isExceptionData(data) && exceptionType) { return { documentType: index_js_1.KnownDocumentType.Exception, exceptionMessage: data.Message, exceptionType: exceptionType, properties: mapToKeyValuePairList(data.CustomDimensions), }; } else { // trace return { documentType: index_js_1.KnownDocumentType.Trace, message: data.Message, properties: mapToKeyValuePairList(data.CustomDimensions), }; } } function isRequestData(data) { return data.Url !== undefined; } function isDependencyData(data) { return data.Target !== undefined; } function isTraceData(data) { return data.Message !== undefined && data.StackTrace === undefined; } function isExceptionData(data) { return data.StackTrace !== undefined; } function getSpanDocument(telemetryData) { let document = { documentType: index_js_1.KnownDocumentType.Request, }; if (isRequestData(telemetryData)) { document = { documentType: index_js_1.KnownDocumentType.Request, name: telemetryData.Name, url: telemetryData.Url, responseCode: String(telemetryData.ResponseCode), duration: getIso8601Duration(telemetryData.Duration), }; } else if (isDependencyData(telemetryData)) { document = { documentType: index_js_1.KnownDocumentType.RemoteDependency, name: telemetryData.Name, commandName: telemetryData.Data, resultCode: String(telemetryData.ResultCode), duration: getIso8601Duration(telemetryData.Duration), }; } document.properties = mapToKeyValuePairList(telemetryData.CustomDimensions); return document; } function createCustomDimsFromAttributes(attributes) { const customDims = new Map(); if (attributes) { for (const key of Object.keys(attributes)) { if (!(key.startsWith("_MS.") || types_js_2.legacySemanticValues.includes(key) || types_js_2.httpSemanticValues.includes(key))) { // eslint-disable-next-line @typescript-eslint/no-base-to-string customDims.set(key, String(attributes[key])); } } } return customDims; } function mapToKeyValuePairList(map) { const list = []; map.forEach((value, key) => { list.push({ key, value }); }); return list; } function getUrl(attributes) { if (!attributes) { return ""; } const httpMethod = getHttpMethod(attributes); if (httpMethod) { const httpUrl = getHttpUrl(attributes); if (httpUrl) { return String(httpUrl); } else { const httpScheme = getHttpScheme(attributes); const httpTarget = getHttpTarget(attributes); if (httpScheme && httpTarget) { const httpHost = getHttpHost(attributes); if (httpHost) { return `${httpScheme}://${httpHost}${httpTarget}`; } else { const netPeerPort = getNetPeerPort(attributes); if (netPeerPort) { const netPeerName = getNetPeerName(attributes); if (netPeerName) { return `${httpScheme}://${netPeerName}:${netPeerPort}${httpTarget}`; } else { const netPeerIp = getPeerIp(attributes); if (netPeerIp) { return `${httpScheme}://${netPeerIp}:${netPeerPort}${httpTarget}`; } } } } } } } return ""; } /** * UTC time the request was made. Expressed as the number of 100-nanosecond intervals that have elapsed since 12:00:00 midnight on January 1, 0001. This is used for clock skew calculations, so the value can never be stale (cached). * * @example * 8/5/2020 10:15:00 PM UTC =\> 637322625000000000 * 8/5/2020 10:15:01 PM UTC =\> 637322625010000000 */ function getTransmissionTime() { return (Date.now() + 62135596800000) * 10000; } function getMsFromFilterTimestampString(timestamp) { // The service side will return a timestamp in the following format: // [days].[hours]:[minutes]:[seconds] // the seconds may be a whole number or something like 7.89. 7.89 seconds translates to 7890 ms. // writing this method because date.getmilliseconds() returns incorrect result on large timestamps. // examples: "14.6:56:7.89" = 1234567890 ms, "0.0:0:0.2" = 200 ms const parts = timestamp.split(":"); if (parts.length !== 3) { return NaN; } const seconds = parseFloat(parts[2]); const minutes = parseFloat(parts[1]); const firstPart = parts[0].split("."); if (firstPart.length !== 2) { return NaN; } const hours = parseFloat(firstPart[1]); const days = parseFloat(firstPart[0]); if (isNaN(days) || isNaN(hours) || isNaN(minutes) || isNaN(seconds)) { return NaN; } return seconds * 1000 + minutes * 60000 + hours * 3600000 + days * 86400000; } function getPeerIp(attributes) { if (attributes) { return String(attributes[semantic_conventions_1.ATTR_NETWORK_PEER_ADDRESS] || attributes[semantic_conventions_1.SEMATTRS_NET_PEER_IP]); } return; } function getUserAgent(attributes) { if (attributes) { return String(attributes[semantic_conventions_1.ATTR_USER_AGENT_ORIGINAL] || attributes[semantic_conventions_1.SEMATTRS_HTTP_USER_AGENT]); } return; } function getHttpUrl(attributes) { // Stable sem conv only supports populating url from `url.full` if (attributes) { return String(attributes[semantic_conventions_1.ATTR_URL_FULL] || attributes[semantic_conventions_1.SEMATTRS_HTTP_URL] || ""); } return ""; } function getHttpMethod(attributes) { if (attributes) { return String(attributes[semantic_conventions_1.ATTR_HTTP_REQUEST_METHOD] || attributes[semantic_conventions_1.SEMATTRS_HTTP_METHOD]); } return; } function getHttpStatusCode(attributes) { if (attributes) { return String(attributes[semantic_conventions_1.ATTR_HTTP_RESPONSE_STATUS_CODE] || attributes[semantic_conventions_1.SEMATTRS_HTTP_STATUS_CODE]); } return; } function getHttpScheme(attributes) { if (attributes) { return String(attributes[semantic_conventions_1.ATTR_URL_SCHEME] || attributes[semantic_conventions_1.SEMATTRS_HTTP_SCHEME]); } return; } function getHttpTarget(attributes) { if (attributes) { if (attributes[semantic_conventions_1.ATTR_URL_PATH]) { return String(attributes[semantic_conventions_1.ATTR_URL_PATH]); } if (attributes[semantic_conventions_1.ATTR_URL_QUERY]) { return String(attributes[semantic_conventions_1.ATTR_URL_QUERY]); } return String(attributes[semantic_conventions_1.SEMATTRS_HTTP_TARGET] || ""); } return ""; } function getHttpHost(attributes) { if (attributes) { return String(attributes[semantic_conventions_1.ATTR_SERVER_ADDRESS] || attributes[semantic_conventions_1.SEMATTRS_HTTP_HOST]); } return; } function getNetPeerName(attributes) { if (attributes) { return String(attributes[semantic_conventions_1.ATTR_CLIENT_ADDRESS] || attributes[semantic_conventions_1.SEMATTRS_NET_PEER_NAME] || ""); } return ""; } function getNetHostPort(attributes) { if (attributes) { return String(attributes[semantic_conventions_1.ATTR_SERVER_PORT] || attributes[semantic_conventions_1.SEMATTRS_NET_HOST_PORT] || ""); } return ""; } function getNetPeerPort(attributes) { if (attributes) { return String(attributes[semantic_conventions_1.ATTR_CLIENT_PORT] || attributes[semantic_conventions_1.ATTR_SERVER_PORT] || attributes[semantic_conventions_1.SEMATTRS_NET_PEER_PORT]); } return; } //# sourceMappingURL=utils.js.map