@opentelemetry/otlp-transformer
Version:
Transform OpenTelemetry SDK data into OTLP
209 lines • 9.73 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.writeResource = exports.writeInstrumentationScope = exports.writeAnyValue = exports.writeKeyValue = exports.writeAttributes = exports.writeHrTimeAsFixed64 = void 0;
/**
* Write HrTime [seconds, nanoseconds] directly as fixed64 to the serializer.
* Converts to nanoseconds and writes as 64-bit little-endian integer without allocations.
*
* HrTime represents: total_nanos = seconds * 1_000_000_000 + nanoseconds
* We need to split this into low (bits 0-31) and high (bits 32-63).
*
* @param serializer - The protobuf writer
* @param hrTime - HrTime tuple [seconds, nanoseconds]
*/
function writeHrTimeAsFixed64(serializer, hrTime) {
const seconds = hrTime[0];
const nanos = hrTime[1];
// Calculate total nanoseconds (seconds * 1_000_000_000 + nanos) split into [low32, high32].
//
// We cannot use `seconds * 1e9` directly because for Unix timestamps
// (seconds > ~9_007_199) the product exceeds Number.MAX_SAFE_INTEGER and loses
// integer precision in IEEE 754 double arithmetic.
//
// So we split `seconds` into its lower 16 bits and remaining upper bits so
// every multiplication stays
// within the safe-integer range (< 2^53):
// secondsLower16Bits * 1e9 <= 65535 * 1e9 ≈ 6.55e13 < 2^53
// secondsUpperBits * 1e9 <= 65535 * 1e9 ≈ 6.55e13 < 2^53
//
// Then recombine:
// seconds * 1e9 =
// secondsUpperBits * 1e9 * 2^16 +
// secondsLower16Bits * 1e9
const nanosPerSecond = 1000000000;
// Split `seconds` into low 16 bits and the remaining upper bits.
// This avoids the signed 32-bit seconds limit behind the Year 2038 problem:
// the practical limit here is the encoded fixed64 range. Callers must keep
// `seconds * 1e9 + nanos` within uint64, i.e. up to
// [18_446_744_073, 709_551_615]. Beyond that, the serialized value wraps.
const secondsLower16Bits = seconds & 0xffff; // bits 0-15 of seconds
const secondsUpperBits = (seconds / 0x10000) >>> 0; // bits 16+ of seconds
const nanosFromLower16Bits = secondsLower16Bits * nanosPerSecond; // exact integer, < 2^53
const nanosFromUpperBits = secondsUpperBits * nanosPerSecond; // exact integer, < 2^53
// Split the lower-16-bit contribution into [low32, high32].
const lower16ContributionLow32 = nanosFromLower16Bits >>> 0;
const lower16ContributionHigh32 = Math.floor(nanosFromLower16Bits / 0x100000000);
// The upper-bits contribution is shifted left by 16 bits when recombined.
const upperBitsContributionLow32 = ((nanosFromUpperBits & 0xffff) * 0x10000) >>> 0;
const upperBitsContributionHigh32 = (nanosFromUpperBits / 0x10000) >>> 0;
// Add the two contributions plus the sub-second nanoseconds with carry propagation.
const low32WithCarry = lower16ContributionLow32 + upperBitsContributionLow32 + nanos;
const totalLow = low32WithCarry >>> 0;
const carry = Math.floor(low32WithCarry / 0x100000000); // 0, 1, or 2
const totalHigh = (lower16ContributionHigh32 + upperBitsContributionHigh32 + carry) >>> 0;
serializer.writeFixed64(totalLow, totalHigh);
}
exports.writeHrTimeAsFixed64 = writeHrTimeAsFixed64;
/**
* Write Attributes directly to protobuf as repeated KeyValue
*/
function writeAttributes(writer, attributes, fieldNumber) {
for (const key in attributes) {
if (!Object.prototype.hasOwnProperty.call(attributes, key)) {
continue;
}
const value = attributes[key];
writer.writeTag(fieldNumber, 2); // repeated KeyValue attributes (field varies, length-delimited)
const kvStart = writer.startLengthDelimited();
const startPos = writer.pos;
writeKeyValue(writer, key, value);
writer.finishLengthDelimited(kvStart, writer.pos - startPos);
}
}
exports.writeAttributes = writeAttributes;
/**
* Write a KeyValue pair directly to protobuf
*/
function writeKeyValue(writer, key, value) {
writer.writeTag(1, 2); // KeyValue.key (field 1, string, length-delimited)
writer.writeString(key);
writer.writeTag(2, 2); // KeyValue.value (field 2, AnyValue, length-delimited)
const valueStart = writer.startLengthDelimited();
const startPos = writer.pos;
writeAnyValue(writer, value);
writer.finishLengthDelimited(valueStart, writer.pos - startPos);
}
exports.writeKeyValue = writeKeyValue;
// int64 range bounds for int_value vs double_value encoding.
// -(2^63) is the signed int64 minimum; 2^63 is one past the maximum.
// Both are exact IEEE 754 doubles (powers of two), so comparisons are precise.
const MIN_64_BIT_INT = -(2 ** 63);
const MAX_64_BIT_INT = 2 ** 63;
/**
* Write an AnyValue directly from raw attribute value to protobuf
*/
function writeAnyValue(writer, value) {
const t = typeof value;
if (t === 'string') {
writer.writeTag(1, 2); // AnyValue.string_value (field 1, length-delimited)
writer.writeString(value);
}
else if (t === 'boolean') {
writer.writeTag(2, 0); // AnyValue.bool_value (field 2, varint)
writer.writeVarint(value ? 1 : 0);
}
else if (t === 'number') {
const numValue = value;
// Encode as int_value (int64) when the number is an integer within the int64
// range. Number.isSafeInteger() would be too conservative — integers beyond
// 2^53-1 that are exactly representable in IEEE 754 (e.g. powers of two)
// still fit in int64 and must not be downgraded to double_value.
if (Number.isInteger(numValue) &&
numValue >= MIN_64_BIT_INT &&
numValue < MAX_64_BIT_INT) {
writer.writeTag(3, 0); // AnyValue.int_value (field 3, varint)
writer.writeVarint(numValue);
}
else {
writer.writeTag(4, 1); // AnyValue.double_value (field 4, fixed64)
writer.writeDouble(numValue);
}
}
else if (value instanceof Uint8Array) {
writer.writeTag(7, 2); // AnyValue.bytes_value (field 7, length-delimited)
writer.writeBytes(value);
}
else if (Array.isArray(value)) {
writer.writeTag(5, 2); // AnyValue.array_value (field 5, ArrayValue, length-delimited)
const arrayStart = writer.startLengthDelimited();
const arrayStartPos = writer.pos;
for (const item of value) {
writer.writeTag(1, 2); // ArrayValue.values (field 1, AnyValue, length-delimited)
const itemStart = writer.startLengthDelimited();
const itemStartPos = writer.pos;
writeAnyValue(writer, item);
writer.finishLengthDelimited(itemStart, writer.pos - itemStartPos);
}
writer.finishLengthDelimited(arrayStart, writer.pos - arrayStartPos);
}
else if (t === 'object' && value != null) {
writer.writeTag(6, 2); // AnyValue.kvlist_value (field 6, KeyValueList, length-delimited)
const kvlistStart = writer.startLengthDelimited();
const kvlistStartPos = writer.pos;
const obj = value;
for (const k in obj) {
if (!Object.prototype.hasOwnProperty.call(obj, k)) {
continue;
}
const v = obj[k];
writer.writeTag(1, 2); // KeyValueList.values (field 1, KeyValue, length-delimited)
const kvStart = writer.startLengthDelimited();
const kvStartPos = writer.pos;
writer.writeTag(1, 2); // KeyValue.key (field 1, string, length-delimited)
writer.writeString(k);
writer.writeTag(2, 2); // KeyValue.value (field 2, AnyValue, length-delimited)
const valueStart = writer.startLengthDelimited();
const valueStartPos = writer.pos;
writeAnyValue(writer, v);
writer.finishLengthDelimited(valueStart, writer.pos - valueStartPos);
writer.finishLengthDelimited(kvStart, writer.pos - kvStartPos);
}
writer.finishLengthDelimited(kvlistStart, writer.pos - kvlistStartPos);
}
// Else: unsupported type, write nothing
}
exports.writeAnyValue = writeAnyValue;
/**
* Write an InstrumentationScope message.
*
* Proto fields (InstrumentationScope):
* 1 name string (wire type 2)
* 2 version string (wire type 2)
*/
function writeInstrumentationScope(writer, scope, fieldNumber) {
writer.writeTag(fieldNumber, 2);
const start = writer.startLengthDelimited();
const startPos = writer.pos;
// name (field 1, string)
writer.writeTag(1, 2);
writer.writeString(scope.name);
// version (field 2, string) - skip if empty
if (scope.version) {
writer.writeTag(2, 2);
writer.writeString(scope.version);
}
writer.finishLengthDelimited(start, writer.pos - startPos);
}
exports.writeInstrumentationScope = writeInstrumentationScope;
/**
* Write a Resource message and its enclosing tag.
*
* Proto fields (Resource):
* 1 attributes repeated KeyValue (wire type 2)
* 2 dropped_attributes_count uint32 (wire type 0)
*/
function writeResource(writer, resource, fieldNumber) {
writer.writeTag(fieldNumber, 2);
const resourceStart = writer.startLengthDelimited();
const resourceStartPos = writer.pos;
// attributes (field 1, repeated KeyValue)
if (resource.attributes) {
writeAttributes(writer, resource.attributes, 1);
}
// dropped_attributes_count (field 2, uint32) - set to 0 as we don't track this
writer.writeTag(2, 0);
writer.writeVarint(0);
writer.finishLengthDelimited(resourceStart, writer.pos - resourceStartPos);
}
exports.writeResource = writeResource;
//# sourceMappingURL=common-serializer.js.map