dd-trace
Version:
Datadog APM tracing client for JavaScript
181 lines (164 loc) • 5.53 kB
JavaScript
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