|
| 1 | +/* |
| 2 | + * Copyright The OpenTelemetry Authors |
| 3 | + * |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | + * you may not use this file except in compliance with the License. |
| 6 | + * You may obtain a copy of the License at |
| 7 | + * |
| 8 | + * https://www.apache.org/licenses/LICENSE-2.0 |
| 9 | + * |
| 10 | + * Unless required by applicable law or agreed to in writing, software |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | + * See the License for the specific language governing permissions and |
| 14 | + * limitations under the License. |
| 15 | + */ |
| 16 | +import type { Attributes, HrTime } from '@opentelemetry/api'; |
| 17 | +import type { AnyValue, LogAttributes } from '@opentelemetry/api-logs'; |
| 18 | +import type { IProtobufWriter } from './i-protobuf-writer'; |
| 19 | + |
| 20 | +/** |
| 21 | + * Write HrTime [seconds, nanoseconds] directly as fixed64 to the serializer. |
| 22 | + * Converts to nanoseconds and writes as 64-bit little-endian integer without allocations. |
| 23 | + * |
| 24 | + * HrTime represents: total_nanos = seconds * 1_000_000_000 + nanoseconds |
| 25 | + * We need to split this into low (bits 0-31) and high (bits 32-63). |
| 26 | + * |
| 27 | + * @param serializer - The protobuf writer |
| 28 | + * @param hrTime - HrTime tuple [seconds, nanoseconds] |
| 29 | + */ |
| 30 | +export function writeHrTimeAsFixed64( |
| 31 | + serializer: IProtobufWriter, |
| 32 | + hrTime: HrTime |
| 33 | +): void { |
| 34 | + const seconds = hrTime[0]; |
| 35 | + const nanos = hrTime[1]; |
| 36 | + |
| 37 | + // Calculate total nanoseconds split into 32-bit parts |
| 38 | + // We use the fact that 1e9 < 2^30, so multiplication is safe for reasonable timestamps |
| 39 | + |
| 40 | + // For the low 32 bits: (seconds * 1e9 + nanos) & 0xFFFFFFFF |
| 41 | + // For the high 32 bits: floor((seconds * 1e9 + nanos) / 2^32) |
| 42 | + |
| 43 | + // Calculate seconds * 1e9 split into parts |
| 44 | + const secNanos = seconds * 1e9; |
| 45 | + const secNanosLow = secNanos >>> 0; // Low 32 bits of seconds * 1e9 |
| 46 | + const secNanosHigh = (secNanos / 0x100000000) >>> 0; // High bits from seconds * 1e9 |
| 47 | + |
| 48 | + // Add nanoseconds to the low part |
| 49 | + const totalLow = (secNanosLow + nanos) >>> 0; |
| 50 | + |
| 51 | + // Check for overflow from low to high (carry bit) |
| 52 | + const carry = secNanosLow + nanos >= 0x100000000 ? 1 : 0; |
| 53 | + const totalHigh = (secNanosHigh + carry) >>> 0; |
| 54 | + |
| 55 | + serializer.writeFixed64(totalLow, totalHigh); |
| 56 | +} |
| 57 | + |
| 58 | +/** |
| 59 | + * Write Attributes directly to protobuf as repeated KeyValue |
| 60 | + */ |
| 61 | +export function writeAttributes( |
| 62 | + writer: IProtobufWriter, |
| 63 | + attributes: Attributes | LogAttributes, |
| 64 | + fieldNumber: number |
| 65 | +): void { |
| 66 | + for (const key in attributes) { |
| 67 | + if (!Object.prototype.hasOwnProperty.call(attributes, key)) { |
| 68 | + continue; |
| 69 | + } |
| 70 | + const value = attributes[key]; |
| 71 | + writer.writeTag(fieldNumber, 2); |
| 72 | + const kvStart = writer.startLengthDelimited(); |
| 73 | + const startPos = writer.pos; |
| 74 | + writeKeyValue(writer, key, value); |
| 75 | + writer.finishLengthDelimited(kvStart, writer.pos - startPos); |
| 76 | + } |
| 77 | +} |
| 78 | + |
| 79 | +/** |
| 80 | + * Write a KeyValue pair directly to protobuf |
| 81 | + */ |
| 82 | +export function writeKeyValue( |
| 83 | + writer: IProtobufWriter, |
| 84 | + key: string, |
| 85 | + value: AnyValue |
| 86 | +): void { |
| 87 | + writer.writeTag(1, 2); |
| 88 | + writer.writeString(key); |
| 89 | + writer.writeTag(2, 2); |
| 90 | + const valueStart = writer.startLengthDelimited(); |
| 91 | + const startPos = writer.pos; |
| 92 | + writeAnyValue(writer, value); |
| 93 | + writer.finishLengthDelimited(valueStart, writer.pos - startPos); |
| 94 | +} |
| 95 | + |
| 96 | +/** |
| 97 | + * Write an AnyValue directly from raw attribute value to protobuf |
| 98 | + */ |
| 99 | +export function writeAnyValue(writer: IProtobufWriter, value: AnyValue): void { |
| 100 | + const t = typeof value; |
| 101 | + if (t === 'string') { |
| 102 | + writer.writeTag(1, 2); |
| 103 | + writer.writeString(value as string); |
| 104 | + } else if (t === 'boolean') { |
| 105 | + writer.writeTag(2, 0); |
| 106 | + writer.writeVarint((value as boolean) ? 1 : 0); |
| 107 | + } else if (t === 'number') { |
| 108 | + // Use isSafeInteger to avoid precision loss with large integers |
| 109 | + // Numbers outside the safe integer range should be serialized as doubles |
| 110 | + if (!Number.isSafeInteger(value as number)) { |
| 111 | + writer.writeTag(4, 1); |
| 112 | + writer.writeDouble(value as number); |
| 113 | + } else { |
| 114 | + writer.writeTag(3, 0); |
| 115 | + writer.writeVarint(value as number); |
| 116 | + } |
| 117 | + } else if (value instanceof Uint8Array) { |
| 118 | + writer.writeTag(7, 2); |
| 119 | + writer.writeBytes(value); |
| 120 | + } else if (Array.isArray(value)) { |
| 121 | + writer.writeTag(5, 2); |
| 122 | + const arrayStart = writer.startLengthDelimited(); |
| 123 | + const arrayStartPos = writer.pos; |
| 124 | + for (const item of value) { |
| 125 | + writer.writeTag(1, 2); |
| 126 | + const itemStart = writer.startLengthDelimited(); |
| 127 | + const itemStartPos = writer.pos; |
| 128 | + writeAnyValue(writer, item); |
| 129 | + writer.finishLengthDelimited(itemStart, writer.pos - itemStartPos); |
| 130 | + } |
| 131 | + writer.finishLengthDelimited(arrayStart, writer.pos - arrayStartPos); |
| 132 | + } else if (t === 'object' && value != null) { |
| 133 | + writer.writeTag(6, 2); |
| 134 | + const kvlistStart = writer.startLengthDelimited(); |
| 135 | + const kvlistStartPos = writer.pos; |
| 136 | + const obj = value as Record<string, AnyValue>; |
| 137 | + for (const k in obj) { |
| 138 | + if (!Object.prototype.hasOwnProperty.call(obj, k)) { |
| 139 | + continue; |
| 140 | + } |
| 141 | + const v = obj[k]; |
| 142 | + writer.writeTag(1, 2); |
| 143 | + const kvStart = writer.startLengthDelimited(); |
| 144 | + const kvStartPos = writer.pos; |
| 145 | + writer.writeTag(1, 2); |
| 146 | + writer.writeString(k); |
| 147 | + writer.writeTag(2, 2); |
| 148 | + const valueStart = writer.startLengthDelimited(); |
| 149 | + const valueStartPos = writer.pos; |
| 150 | + writeAnyValue(writer, v); |
| 151 | + writer.finishLengthDelimited(valueStart, writer.pos - valueStartPos); |
| 152 | + writer.finishLengthDelimited(kvStart, writer.pos - kvStartPos); |
| 153 | + } |
| 154 | + writer.finishLengthDelimited(kvlistStart, writer.pos - kvlistStartPos); |
| 155 | + } |
| 156 | + // Else: unsupported type, write nothing |
| 157 | +} |
0 commit comments