UNPKG

@dynatrace/opentelemetry-core

Version:

Dynatrace OpenTelemetry core package

440 lines (437 loc) 19.1 kB
"use strict"; /* Copyright 2022 Dynatrace LLC 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.serialize = serialize; exports.serializeResource = serializeResource; const api_1 = require("@opentelemetry/api"); const Long = require("long"); const odin_proto_1 = require("../../gen/proto/odin-proto"); const SemConvTrace = require("../../gen/dynatrace/odin/semconv/v1/SemConvTrace"); const Logger_1 = require("../Logger"); const LoggingUtils_1 = require("../utils/LoggingUtils"); const SpanMetaData_1 = require("./SpanMetaData"); const SpanEmbedder_1 = require("./SpanEmbedder"); // some shortcuts to inner namespaces in protocol var pCollectorTrace = odin_proto_1.dynatrace.odin.proto.collector.trace.v1; var pCollectorCommon = odin_proto_1.dynatrace.odin.proto.collector.common.v1; var pCommon = odin_proto_1.dynatrace.odin.proto.common.v1; var pTrace = odin_proto_1.dynatrace.odin.proto.trace.v1; var pResource = odin_proto_1.dynatrace.odin.proto.resource.v1; // ============================================================================ const logger = new Logger_1.ComponentLogger("Serialization"); // ============================================================================ /** * Serialize spans and resources into a protobuf message. * @param senderId The senderId * @param tenantUUID The Tenant UUID * @param spans The spans to serialize * @param resources The resources to include (serialized, @see serializeResources) * @returns serialized message */ function serialize(senderId, tenantUUID, spans, resources) { try { // create Span messages and map per traceId/serverId/... const spanMapper = new SpanMapper(); for (const span of spans) { spanMapper.processSpan(span); } const spanExport = new pCollectorTrace.SpanExport(); spanExport.tenantUUID = tenantUUID; spanExport.agentId = senderId; spanMapper.serializeSpans(spanExport); spanExport.exportMetaInfo = createExportMetaInfoMsg(); spanExport.resource = resources; // TypeDefinition of protobufjs tell they return an UInt8Array but in case of Node it's a Buffer return pCollectorTrace.SpanExport.encode(spanExport).finish(); } catch (e) { // ToDo: maybe implement a better error handling in exporter logger.error(`Failed ${(0, LoggingUtils_1.verboseFormatException)(e)}`); } return Buffer.alloc(0); } // ---------------------------------------------------------------------------- /** * Serialize Resource as Protobuf message. * @param resource The resources to serialize * @return The serialized Resource message */ function serializeResource(resource) { try { const resourceMsg = new pResource.Resource(); addAttributes(resourceMsg.attributes, resource.attributes); // TypeDefinition of protobufjs tell they return an UInt8Array but in case of Node it's a Buffer return pResource.Resource.encode(resourceMsg).finish(); } catch (e) { logger.error(`Failed ${(0, LoggingUtils_1.verboseFormatException)(e)}`); } return Buffer.alloc(0); } // ---------------------------------------------------------------------------- function createExportMetaInfoMsg() { const metaInfo = new pCollectorCommon.ExportMetaInfo(); metaInfo.timeSyncMode = pCollectorCommon.ExportMetaInfo.TimeSyncMode.NTPSync; return pCollectorCommon.ExportMetaInfo.encode(metaInfo).finish(); } // ---------------------------------------------------------------------------- function toSpanKindProto(kind) { const SpanKindProto = pTrace.Span.SpanKind; switch (kind) { case api_1.SpanKind.INTERNAL: return SpanKindProto.INTERNAL; case api_1.SpanKind.SERVER: return SpanKindProto.SERVER; case api_1.SpanKind.CLIENT: return SpanKindProto.CLIENT; case api_1.SpanKind.PRODUCER: return SpanKindProto.PRODUCER; case api_1.SpanKind.CONSUMER: return SpanKindProto.CONSUMER; default: logger.debug(`invalid SpanKind: ${kind}`); return SpanKindProto.SPAN_KIND_UNSPECIFIED; } } // ---------------------------------------------------------------------------- function toStatusCodeProto(code) { const ProtoStatusCode = pTrace.Status.StatusCode; switch (code) { case api_1.SpanStatusCode.OK: return ProtoStatusCode.Ok; case api_1.SpanStatusCode.UNSET: return ProtoStatusCode.Ok; case api_1.SpanStatusCode.ERROR: return ProtoStatusCode.UnknownError; default: logger.debug(`invalid StatusCode: ${code}`); return ProtoStatusCode.Ok; } } // ---------------------------------------------------------------------------- function toSendReason(sendState) { const SendReason = pTrace.Span.SendReason; switch (sendState) { case SpanMetaData_1.SendState.New: return SendReason.NewOrChanged; case SpanMetaData_1.SendState.Drop: return SendReason.Dropped; case SpanMetaData_1.SendState.Update: return SendReason.NewOrChanged; case SpanMetaData_1.SendState.Alive: return SendReason.KeepAlive; case SpanMetaData_1.SendState.Finished: return SendReason.Ended; default: logger.debug(`invalid SendState: ${sendState}`); return SendReason.KeepAlive; } } // ---------------------------------------------------------------------------- function hrTimeToLong(hrTime) { return Long.fromNumber(hrTime[0], true).mul(1e9).add(hrTime[1]); } // ---------------------------------------------------------------------------- function setAttrVal(msg, val) { const ValueType = pCommon.AttributeKeyValue.ValueType; if (Array.isArray(val)) { const firstNonNullVal = val.find(v => v != null); if (firstNonNullVal == null) { msg.type = ValueType.VALUELESS_ARRAY; return true; } switch (typeof (firstNonNullVal)) { case "number": msg.type = ValueType.DOUBLE_ARRAY; msg.double_values = val.map(v => typeof (v) === "number" ? v : 0); break; case "boolean": msg.type = ValueType.BOOL_ARRAY; msg.bool_values = val.map(v => typeof (v) === "boolean" ? v : false); break; case "string": msg.type = ValueType.STRING_ARRAY; msg.string_values = val.map(v => typeof (v) === "string" ? v : ""); break; default: logger.debug(`invalid Array Attribute type: ${typeof (firstNonNullVal)}`); return false; } return true; } switch (typeof (val)) { case "number": msg.type = ValueType.DOUBLE; msg.double_value = val; break; case "boolean": msg.type = ValueType.BOOL; msg.bool_value = val; break; case "string": msg.type = ValueType.STRING; msg.string_value = val; break; default: logger.debug(`invalid Attribute type: ${typeof (val)}`); return false; } return true; } // ---------------------------------------------------------------------------- function addInstrumentationScope(attrPb, lib) { const name = new pCommon.AttributeKeyValue(); if (setAttrVal(name, lib.name)) { name.key = SemConvTrace.OTEL_LIBRARY_NAME; attrPb.push(name); } if (lib.version) { const version = new pCommon.AttributeKeyValue(); version.key = SemConvTrace.OTEL_LIBRARY_VERSION; version.string_value = lib.version; attrPb.push(version); } } // ---------------------------------------------------------------------------- function addAttributes(attrPb, attributes) { for (const key in attributes) { if (attributes.hasOwnProperty(key)) { addAttribute(attrPb, key, attributes[key]); } } } // ---------------------------------------------------------------------------- function addAttribute(attrPb, key, value) { const attribMsg = new pCommon.AttributeKeyValue(); if (setAttrVal(attribMsg, value)) { attribMsg.key = key; attrPb.push(attribMsg); } } // ---------------------------------------------------------------------------- function addSpanAttributes(attrPb, span) { var _a, _b, _c; const resourceAttrs = (0, SpanEmbedder_1.getPropagatedResourceAttributes)(span); if (resourceAttrs != null) { for (const key in resourceAttrs) { if (resourceAttrs.hasOwnProperty(key)) { addAttribute(attrPb, key, resourceAttrs[key]); } } } let spanAttrs = span.attributes; const reasons = (_c = (_b = (_a = (0, SpanEmbedder_1.getFw4Tag)(span.spanContext().traceState)) === null || _a === void 0 ? void 0 : _a.extensions) === null || _b === void 0 ? void 0 : _b.tcr) === null || _c === void 0 ? void 0 : _c.reasons; if (reasons != null && reasons.length > 0) { spanAttrs = Object.assign(Object.assign({}, spanAttrs), { [SemConvTrace.TRACE_CAPTURE_REASONS]: reasons }); } for (let key in spanAttrs) { if (!spanAttrs.hasOwnProperty(key)) { continue; } const value = spanAttrs[key]; if (resourceAttrs === null || resourceAttrs === void 0 ? void 0 : resourceAttrs.hasOwnProperty(key)) { if (areAttributesEqual(resourceAttrs[key], value)) { continue; } // since we support at most 1 overwritten attribute per key here // we can hardcode the prefix to index 1 key = `overwritten1.${key}`; } addAttribute(attrPb, key, value); } } // ------------------------------------------------------------------------ function areAttributesEqual(attr, other) { if (attr === other) { return true; } if (Array.isArray(attr) && Array.isArray(other)) { if (attr.length !== other.length) { return false; } for (let idx = 0; idx < attr.length; idx++) { if (attr[idx] !== other[idx]) { return false; } } return true; } return false; } // ---------------------------------------------------------------------------- function addEvents(spanMsg, events) { for (const event of events) { const eventMsg = new pTrace.Span.Event(); eventMsg.name = event.name; eventMsg.time_unixnano = hrTimeToLong(event.time); if (event.attributes != null) { addAttributes(eventMsg.attributes, event.attributes); } spanMsg.events.push(eventMsg); } } // ---------------------------------------------------------------------------- function addLinks(spanMsg, links) { for (const link of links) { const linkMsg = new pTrace.Span.Link(); linkMsg.trace_id = Buffer.from(link.context.traceId, "hex"); linkMsg.span_id = Buffer.from(link.context.spanId, "hex"); if (link.attributes != null) { addAttributes(linkMsg.attributes, link.attributes); } if (link.context.isRemote) { const fw4Tag = (0, SpanEmbedder_1.getFw4Tag)(link.context.traceState); if (fw4Tag != null) { linkMsg.fwtag_encoded_link_id = fw4Tag.encodedLinkId; } } spanMsg.links.push(linkMsg); } } // ---------------------------------------------------------------------------- function addStatus(spanMsg, status) { const statusMsg = new pTrace.Status(); statusMsg.code = toStatusCodeProto(status.code); if (status.message != null) { statusMsg.message = status.message; } spanMsg.status = statusMsg; } // ---------------------------------------------------------------------------- /// Helper class to map spans to envelopes based on traceId/serverId class SpanMapper { constructor() { // ------------------------------------------------------------------------ this._traceIdMap = new Map(); this._serverIdMap = new Map(); } // ------------------------------------------------------------------------ /** * Serializes all spans into SpanExport message * @param spanExport the target SpanExport message */ serializeSpans(spanExport) { for (const [, pathInfoMap] of this._traceIdMap) { for (const [, spanInfo] of pathInfoMap) { this._addActivGateSpanEnvelope(spanExport, spanInfo); } } for (const [serverId, traceIdMap] of this._serverIdMap) { for (const [, pathInfoMap] of traceIdMap) { for (const [, spanInfo] of pathInfoMap) { this._addActivGateSpanEnvelope(spanExport, spanInfo, serverId); } } } } // ------------------------------------------------------------------------ processSpan(span) { var _a; const spanMsg = new pTrace.Span(); const metaData = (0, SpanEmbedder_1.getMetaData)(span); // trace_id and span_id are always needed spanMsg.trace_id = Buffer.from(span.spanContext().traceId, "hex"); spanMsg.span_id = Buffer.from(span.spanContext().spanId, "hex"); // Remaining data is only needed for full updates and finished spans const sendState = metaData.sendState; if ((sendState === SpanMetaData_1.SendState.Finished) || (sendState === SpanMetaData_1.SendState.Update)) { if (metaData.tenantParentSpanId != null) { spanMsg.tenant_parent_span_id = Buffer.from(metaData.tenantParentSpanId, "hex"); } if (metaData.encodedLinkId != null) { spanMsg.parent_fwtag_encoded_link_id = metaData.encodedLinkId; } if (((_a = span.parentSpanContext) === null || _a === void 0 ? void 0 : _a.spanId) != null) { spanMsg.parent_span_id = Buffer.from(span.parentSpanContext.spanId, "hex"); } if (metaData.mobileTag != null) { spanMsg.mobile_tag = metaData.mobileTag; } spanMsg.name = span.name; spanMsg.kind = toSpanKindProto(span.kind); spanMsg.start_time_unixnano = hrTimeToLong(span.startTime); if (sendState === SpanMetaData_1.SendState.Finished) { spanMsg.end_time_unixnano = hrTimeToLong(span.endTime); if (metaData.lastPropagationTime != null) { spanMsg.last_propagate_time_unixnano = hrTimeToLong(metaData.lastPropagationTime); } } addInstrumentationScope(spanMsg.attributes, span.instrumentationScope); addSpanAttributes(spanMsg.attributes, span); addEvents(spanMsg, span.events); addLinks(spanMsg, span.links); addStatus(spanMsg, span.status); } spanMsg.send_reason = toSendReason(sendState); spanMsg.update_sequence_no = metaData.seqNr; this._addSpanToMap(span.spanContext().traceId, spanMsg, (0, SpanEmbedder_1.getFw4Tag)(span.spanContext().traceState)); } // ------------------------------------------------------------------------ _addSpanToMap(traceId, spanMsg, fw4Tag) { const pathInfoMap = this._getPathInfoMap(fw4Tag.serverId, traceId); const pathInfo = fw4Tag.pathInfo; let info = pathInfoMap.get(pathInfo); if (info == null) { info = { pathInfo: fw4Tag.pathInfo, customTags: this._createCustomTagsMsg(fw4Tag.extensions.customBlob), spanContainer: new pCollectorTrace.SpanContainer() }; pathInfoMap.set(pathInfo, info); } info.spanContainer.spans.push(spanMsg); } // ------------------------------------------------------------------------ _getPathInfoMap(serverId, traceId) { const traceIdMap = this._getTraceIdMap(serverId); let pathInfoMap = traceIdMap.get(traceId); if (pathInfoMap == null) { pathInfoMap = new Map(); traceIdMap.set(traceId, pathInfoMap); } return pathInfoMap; } // ------------------------------------------------------------------------ _getTraceIdMap(serverId) { if (serverId === 0) { return this._traceIdMap; } let traceIdMap = this._serverIdMap.get(serverId); if (traceIdMap == null) { traceIdMap = new Map(); this._serverIdMap.set(serverId, traceIdMap); } return traceIdMap; } // ------------------------------------------------------------------------ _addActivGateSpanEnvelope(spanExport, spans, serverId) { const clusterSpanEnvelope = new pCollectorTrace.ClusterSpanEnvelope(); clusterSpanEnvelope.traceId = spans.spanContainer.spans[0].trace_id; // avoid reencoding clusterSpanEnvelope.pathInfo = spans.pathInfo; clusterSpanEnvelope.customTags = spans.customTags; clusterSpanEnvelope.spanContainer = pCollectorTrace.SpanContainer.encode(spans.spanContainer).finish(); const agSpanEnvelope = new pCollectorTrace.ActiveGateSpanEnvelope(); agSpanEnvelope.clusterSpanEnvelope = pCollectorTrace.ClusterSpanEnvelope.encode(clusterSpanEnvelope).finish(); // routing by serverId is preferred if (serverId != null) { agSpanEnvelope.serverId = serverId; } else { agSpanEnvelope.traceId = clusterSpanEnvelope.traceId; } spanExport.spans.push(agSpanEnvelope); } // ------------------------------------------------------------------------ _createCustomTagsMsg(customBlob) { if (customBlob.length > 0) { const customTag = new pTrace.CustomTag(); // NodeJs supports propagation of incoming custom tags only at this time customTag.direction = pTrace.CustomTag.Direction.Incoming; // first char in blob is the type (see OAAD tagging) and can be directly assigned // as enum values in ODIN match to OneAgent customTag.type = customBlob.charCodeAt(0); customTag.tagValue = Buffer.from(customBlob.substring(1), "binary"); return [customTag]; } return []; } } //# sourceMappingURL=Serialization.js.map