UNPKG

dd-trace

Version:

Datadog APM tracing client for JavaScript

181 lines (164 loc) 5.53 kB
'use strict' const log = require('../../log') /** * @typedef {import('@opentelemetry/api').Attributes} Attributes * @typedef {import('@opentelemetry/api').AttributeValue} AttributeValue */ /** * Base class for OTLP transformers. * * This implementation provides common functionality for transforming * data to OTLP format (protobuf or JSON). * * @class OtlpTransformerBase */ class OtlpTransformerBase { #resourceAttributes /** * Creates a new OtlpTransformerBase instance. * * @param {Attributes} resourceAttributes - Resource attributes * @param {string} protocol - OTLP protocol (http/protobuf or http/json) * @param {string} signalType - Signal type for warning messages (e.g., 'logs', 'metrics') */ constructor (resourceAttributes, protocol, signalType) { this.#resourceAttributes = this.transformAttributes(resourceAttributes) if (protocol === 'grpc') { log.warn( // eslint-disable-next-line @stylistic/max-len 'OTLP gRPC protocol is not supported for %s. Defaulting to http/protobuf. gRPC protobuf support may be added in a future release.', signalType ) protocol = 'http/protobuf' } this.protocol = protocol } /** * Groups items by instrumentation scope (name, version, schemaUrl, and attributes). * @param {Array} items - Array of items to group * @returns {Map<string, Array>} Map of instrumentation scope key to items * @protected */ groupByInstrumentationScope (items) { const grouped = new Map() for (const item of items) { const instrumentationScope = item.instrumentationScope || { name: '', version: '', schemaUrl: '', attributes: {} } const attrsKey = stableStringify(instrumentationScope.attributes || {}) const key = `${instrumentationScope.name}@${instrumentationScope.version}@` + `${instrumentationScope.schemaUrl}@${attrsKey}` const group = grouped.get(key) if (group === undefined) { grouped.set(key, [item]) } else { group.push(item) } } return grouped } /** * Transforms resource attributes to OTLP resource format. * @returns {object} OTLP resource object * @protected */ transformResource () { return { attributes: this.#resourceAttributes, droppedAttributesCount: 0, } } /** * Transforms attributes to OTLP KeyValue format. * @param {Attributes} attributes - Attributes to transform * @returns {object[]} Array of OTLP KeyValue objects * @protected */ transformAttributes (attributes) { return Object.entries(attributes).map(([key, value]) => ({ key, value: this.transformAnyValue(value), })) } /** * Transforms attributes to JSON format (simplified). * @param {object} attributes - Attributes to transform * @returns {object[]} Array of OTLP KeyValue objects with string values * @protected */ attributesToJson (attributes) { if (!attributes) return [] return Object.entries(attributes).map(([key, value]) => ({ key, value: { stringValue: String(value) }, })) } /** * Transforms any value to OTLP AnyValue format. * Supports: strings, numbers (int/double), booleans, arrays. * Objects are filtered out by sanitizeAttributes before reaching this method. * @param {AttributeValue | null | undefined} value - Value to transform * @returns {object} OTLP AnyValue object * @protected */ transformAnyValue (value) { if (typeof value === 'string') { return { stringValue: value } } else if (typeof value === 'number') { if (Number.isInteger(value)) { return { intValue: value } } return { doubleValue: value } } else if (typeof value === 'boolean') { return { boolValue: value } } else if (Array.isArray(value)) { return { arrayValue: { values: value.map(v => this.transformAnyValue(v)), }, } } // Fallback for any unexpected types return { stringValue: String(value) } } /** * Serializes data to protobuf format. * @param {object} protoType - Protobuf type from protobuf_loader * @param {object} data - Data to serialize * @returns {Buffer} Protobuf-encoded data * @protected */ serializeToProtobuf (protoType, data) { const message = protoType.create(data) return protoType.encode(message).finish() } /** * Serializes data to JSON format. * @param {object} data - Data to serialize * @returns {Buffer} JSON-encoded data * @protected */ serializeToJson (data) { return Buffer.from(JSON.stringify(data)) } } /** * Stable stringification of OpenTelemetry Attributes. * Ensures consistent serialization regardless of key order by sorting keys. * Supports string keys with primitive values or arrays of primitives. * * @param {Attributes} attributes - Attributes object to stringify * @returns {string} Stable string representation */ function stableStringify (attributes) { if (attributes == null) { return JSON.stringify(attributes) } // Attributes are sorted by key to ensure consistent serialization regardless of key order. // Keys are always strings and values are always strings, numbers, booleans, // or arrays of strings, numbers, or booleans. return Object.keys(attributes) .sort() .map(key => `${key}:${JSON.stringify(attributes[key])}`) .join(',') } module.exports = OtlpTransformerBase module.exports.stableStringify = stableStringify