From 4cfa580fea5dbf7dcbf5efd5cc19e04ef602c095 Mon Sep 17 00:00:00 2001 From: overbalance Date: Tue, 2 Dec 2025 16:29:12 -0600 Subject: [PATCH 01/15] refactor: add protobuf-es --- .../packages/otlp-transformer/buf.gen.yaml | 8 + .../packages/otlp-transformer/buf.yaml | 3 + .../packages/otlp-transformer/package.json | 11 +- .../src/common/internal-types.ts | 2 +- .../common/protobuf/protobuf-export-type.ts | 22 - .../otlp-transformer/src/common/utils.ts | 32 + .../otlp-transformer/src/logs/internal.ts | 8 +- .../src/logs/protobuf/logs.ts | 38 +- .../otlp-transformer/src/metrics/internal.ts | 10 +- .../src/metrics/protobuf/metrics.ts | 39 +- .../otlp-transformer/src/trace/internal.ts | 8 +- .../src/trace/protobuf/trace.ts | 34 +- package-lock.json | 551 +++++++----------- 13 files changed, 360 insertions(+), 406 deletions(-) create mode 100644 experimental/packages/otlp-transformer/buf.gen.yaml create mode 100644 experimental/packages/otlp-transformer/buf.yaml delete mode 100644 experimental/packages/otlp-transformer/src/common/protobuf/protobuf-export-type.ts diff --git a/experimental/packages/otlp-transformer/buf.gen.yaml b/experimental/packages/otlp-transformer/buf.gen.yaml new file mode 100644 index 00000000000..2dfb1e5164e --- /dev/null +++ b/experimental/packages/otlp-transformer/buf.gen.yaml @@ -0,0 +1,8 @@ +version: v2 +plugins: + - local: protoc-gen-es + out: src/generated + opt: + - target=ts +inputs: + - directory: protos diff --git a/experimental/packages/otlp-transformer/buf.yaml b/experimental/packages/otlp-transformer/buf.yaml new file mode 100644 index 00000000000..04309e4f879 --- /dev/null +++ b/experimental/packages/otlp-transformer/buf.yaml @@ -0,0 +1,3 @@ +version: v2 +modules: + - path: protos diff --git a/experimental/packages/otlp-transformer/package.json b/experimental/packages/otlp-transformer/package.json index d2f34afa203..ac728fa96ef 100644 --- a/experimental/packages/otlp-transformer/package.json +++ b/experimental/packages/otlp-transformer/package.json @@ -17,9 +17,7 @@ "compile": "tsc --build tsconfig.json tsconfig.esm.json tsconfig.esnext.json", "clean": "tsc --build --clean tsconfig.json tsconfig.esm.json tsconfig.esnext.json", "protos": "npm run submodule && npm run protos:generate", - "protos:generate:js": "pbjs -t static-module -p ./protos -w commonjs --null-defaults -o ./src/generated/root.js ./protos/opentelemetry/proto/common/v1/common.proto ./protos/opentelemetry/proto/resource/v1/resource.proto ./protos/opentelemetry/proto/trace/v1/trace.proto ./protos/opentelemetry/proto/collector/trace/v1/trace_service.proto ./protos/opentelemetry/proto/metrics/v1/metrics.proto ./protos/opentelemetry/proto/collector/metrics/v1/metrics_service.proto ./protos/opentelemetry/proto/logs/v1/logs.proto ./protos/opentelemetry/proto/collector/logs/v1/logs_service.proto", - "protos:generate:ts": "pbts -o ./src/generated/root.d.ts ./src/generated/root.js", - "protos:generate": "npm run protos:generate:js && npm run protos:generate:ts", + "protos:generate": "buf generate", "lint": "eslint . --ext .ts", "lint:fix": "eslint . --ext .ts --fix", "tdd": "npm run test -- --watch-extensions ts --watch", @@ -63,6 +61,8 @@ "@opentelemetry/api": "^1.3.0" }, "devDependencies": { + "@bufbuild/buf": "1.47.2", + "@bufbuild/protoc-gen-es": "2.2.3", "@opentelemetry/api": "1.9.0", "@types/mocha": "10.0.10", "@types/webpack-env": "1.16.3", @@ -75,19 +75,18 @@ "karma-webpack": "5.0.1", "mocha": "11.7.5", "nyc": "17.1.0", - "protobufjs-cli": "1.1.3", "ts-loader": "9.5.4", "typescript": "5.0.4", "webpack": "5.101.3" }, "dependencies": { + "@bufbuild/protobuf": "2.2.3", "@opentelemetry/api-logs": "0.208.0", "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0", "@opentelemetry/sdk-logs": "0.208.0", "@opentelemetry/sdk-metrics": "2.2.0", - "@opentelemetry/sdk-trace-base": "2.2.0", - "protobufjs": "^7.3.0" + "@opentelemetry/sdk-trace-base": "2.2.0" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/otlp-transformer", "sideEffects": false diff --git a/experimental/packages/otlp-transformer/src/common/internal-types.ts b/experimental/packages/otlp-transformer/src/common/internal-types.ts index 315fa46f984..1e3e35dcd25 100644 --- a/experimental/packages/otlp-transformer/src/common/internal-types.ts +++ b/experimental/packages/otlp-transformer/src/common/internal-types.ts @@ -91,7 +91,7 @@ export interface LongBits { high: number; } -export type Fixed64 = LongBits | string | number; +export type Fixed64 = LongBits | string | number | bigint; export interface OtlpEncodingOptions { /** Convert trace and span IDs to hex strings. */ diff --git a/experimental/packages/otlp-transformer/src/common/protobuf/protobuf-export-type.ts b/experimental/packages/otlp-transformer/src/common/protobuf/protobuf-export-type.ts deleted file mode 100644 index c84d5311c13..00000000000 --- a/experimental/packages/otlp-transformer/src/common/protobuf/protobuf-export-type.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * - * 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 - * - * https://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. - */ - -import * as protobuf from 'protobufjs'; - -export interface ExportType unknown }> { - encode(message: T, writer?: protobuf.Writer): protobuf.Writer; - decode(reader: protobuf.Reader | Uint8Array, length?: number): R; -} diff --git a/experimental/packages/otlp-transformer/src/common/utils.ts b/experimental/packages/otlp-transformer/src/common/utils.ts index f4f8cb65e41..13ec8919616 100644 --- a/experimental/packages/otlp-transformer/src/common/utils.ts +++ b/experimental/packages/otlp-transformer/src/common/utils.ts @@ -68,12 +68,44 @@ function optionalHexToBinary(str: string | undefined): Uint8Array | undefined { return hexToBinary(str); } +/** + * Convert hex string to base64 (for protobuf JSON format). + */ +export function hexToBase64(hex: string): string { + const bytes = hexToBinary(hex); + // Works in both Node.js and browser + if (typeof Buffer !== 'undefined') { + return Buffer.from(bytes).toString('base64'); + } + // Browser fallback + let binary = ''; + for (let i = 0; i < bytes.length; i++) { + binary += String.fromCharCode(bytes[i]); + } + return btoa(binary); +} + +function optionalHexToBase64(str: string | undefined): string | undefined { + if (str === undefined) return undefined; + return hexToBase64(str); +} + const DEFAULT_ENCODER: Encoder = { encodeHrTime: encodeAsLongBits, encodeSpanContext: hexToBinary, encodeOptionalSpanContext: optionalHexToBinary, }; +/** + * Encoder for protobuf JSON format (used with fromJson). + * Uses string timestamps and base64 for bytes. + */ +export const PROTOBUF_JSON_ENCODER: Encoder = { + encodeHrTime: encodeAsString, + encodeSpanContext: hexToBase64, + encodeOptionalSpanContext: optionalHexToBase64, +}; + export function getOtlpEncoder(options?: OtlpEncodingOptions): Encoder { if (options === undefined) { return DEFAULT_ENCODER; diff --git a/experimental/packages/otlp-transformer/src/logs/internal.ts b/experimental/packages/otlp-transformer/src/logs/internal.ts index 510206d37c9..86c62745aef 100644 --- a/experimental/packages/otlp-transformer/src/logs/internal.ts +++ b/experimental/packages/otlp-transformer/src/logs/internal.ts @@ -35,14 +35,18 @@ import { LogAttributes } from '@opentelemetry/api-logs'; export function createExportLogsServiceRequest( logRecords: ReadableLogRecord[], - options?: OtlpEncodingOptions + options?: OtlpEncodingOptions | Encoder ): IExportLogsServiceRequest { - const encoder = getOtlpEncoder(options); + const encoder = isEncoder(options) ? options : getOtlpEncoder(options); return { resourceLogs: logRecordsToResourceLogs(logRecords, encoder), }; } +function isEncoder(obj: OtlpEncodingOptions | Encoder | undefined): obj is Encoder { + return obj !== undefined && 'encodeHrTime' in obj; +} + function createResourceMap( logRecords: ReadableLogRecord[] ): Map> { diff --git a/experimental/packages/otlp-transformer/src/logs/protobuf/logs.ts b/experimental/packages/otlp-transformer/src/logs/protobuf/logs.ts index a5b32453b68..36ef894f3a4 100644 --- a/experimental/packages/otlp-transformer/src/logs/protobuf/logs.ts +++ b/experimental/packages/otlp-transformer/src/logs/protobuf/logs.ts @@ -13,21 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import * as root from '../../generated/root'; - -import { IExportLogsServiceRequest } from '../internal-types'; +import { toBinary, fromBinary, fromJson } from '@bufbuild/protobuf'; +import type { JsonValue } from '@bufbuild/protobuf'; import { IExportLogsServiceResponse } from '../export-response'; - import { createExportLogsServiceRequest } from '../internal'; import { ReadableLogRecord } from '@opentelemetry/sdk-logs'; -import { ExportType } from '../../common/protobuf/protobuf-export-type'; import { ISerializer } from '../../i-serializer'; - -const logsResponseType = root.opentelemetry.proto.collector.logs.v1 - .ExportLogsServiceResponse as ExportType; - -const logsRequestType = root.opentelemetry.proto.collector.logs.v1 - .ExportLogsServiceRequest as ExportType; +import { + ExportLogsServiceRequestSchema, + ExportLogsServiceResponseSchema, +} from '../../generated/opentelemetry/proto/collector/logs/v1/logs_service_pb'; +import { PROTOBUF_JSON_ENCODER } from '../../common/utils'; /* * @experimental this serializer may receive breaking changes in minor versions, pin this package's version when using this constant @@ -37,10 +33,24 @@ export const ProtobufLogsSerializer: ISerializer< IExportLogsServiceResponse > = { serializeRequest: (arg: ReadableLogRecord[]) => { - const request = createExportLogsServiceRequest(arg); - return logsRequestType.encode(request).finish(); + const request = createExportLogsServiceRequest(arg, PROTOBUF_JSON_ENCODER); + const message = fromJson( + ExportLogsServiceRequestSchema, + request as unknown as JsonValue + ); + return toBinary(ExportLogsServiceRequestSchema, message); }, deserializeResponse: (arg: Uint8Array) => { - return logsResponseType.decode(arg); + const response = fromBinary(ExportLogsServiceResponseSchema, arg); + return { + partialSuccess: response.partialSuccess + ? { + rejectedLogRecords: Number( + response.partialSuccess.rejectedLogRecords + ), + errorMessage: response.partialSuccess.errorMessage, + } + : undefined, + }; }, }; diff --git a/experimental/packages/otlp-transformer/src/metrics/internal.ts b/experimental/packages/otlp-transformer/src/metrics/internal.ts index 50feb9c60fa..52d4d127554 100644 --- a/experimental/packages/otlp-transformer/src/metrics/internal.ts +++ b/experimental/packages/otlp-transformer/src/metrics/internal.ts @@ -44,9 +44,9 @@ import { export function toResourceMetrics( resourceMetrics: ResourceMetrics, - options?: OtlpEncodingOptions + options?: OtlpEncodingOptions | Encoder ): IResourceMetrics { - const encoder = getOtlpEncoder(options); + const encoder = isEncoder(options) ? options : getOtlpEncoder(options); const processedResource = createResource(resourceMetrics.resource); return { resource: processedResource, @@ -55,6 +55,10 @@ export function toResourceMetrics( }; } +function isEncoder(obj: OtlpEncodingOptions | Encoder | undefined): obj is Encoder { + return obj !== undefined && 'encodeHrTime' in obj; +} + export function toScopeMetrics( scopeMetrics: ScopeMetrics[], encoder: Encoder @@ -209,7 +213,7 @@ function toAggregationTemporality( export function createExportMetricsServiceRequest( resourceMetrics: ResourceMetrics[], - options?: OtlpEncodingOptions + options?: OtlpEncodingOptions | Encoder ): IExportMetricsServiceRequest { return { resourceMetrics: resourceMetrics.map(metrics => diff --git a/experimental/packages/otlp-transformer/src/metrics/protobuf/metrics.ts b/experimental/packages/otlp-transformer/src/metrics/protobuf/metrics.ts index ee21ca93eb2..63bed0c9b9b 100644 --- a/experimental/packages/otlp-transformer/src/metrics/protobuf/metrics.ts +++ b/experimental/packages/otlp-transformer/src/metrics/protobuf/metrics.ts @@ -14,29 +14,44 @@ * limitations under the License. */ -import * as root from '../../generated/root'; +import { toBinary, fromBinary, fromJson } from '@bufbuild/protobuf'; +import type { JsonValue } from '@bufbuild/protobuf'; import { ISerializer } from '../../i-serializer'; -import { IExportMetricsServiceRequest } from '../internal-types'; -import { ExportType } from '../../common/protobuf/protobuf-export-type'; import { createExportMetricsServiceRequest } from '../internal'; import { ResourceMetrics } from '@opentelemetry/sdk-metrics'; import { IExportMetricsServiceResponse } from '../export-response'; - -const metricsResponseType = root.opentelemetry.proto.collector.metrics.v1 - .ExportMetricsServiceResponse as ExportType; - -const metricsRequestType = root.opentelemetry.proto.collector.metrics.v1 - .ExportMetricsServiceRequest as ExportType; +import { + ExportMetricsServiceRequestSchema, + ExportMetricsServiceResponseSchema, +} from '../../generated/opentelemetry/proto/collector/metrics/v1/metrics_service_pb'; +import { PROTOBUF_JSON_ENCODER } from '../../common/utils'; export const ProtobufMetricsSerializer: ISerializer< ResourceMetrics, IExportMetricsServiceResponse > = { serializeRequest: (arg: ResourceMetrics) => { - const request = createExportMetricsServiceRequest([arg]); - return metricsRequestType.encode(request).finish(); + const request = createExportMetricsServiceRequest( + [arg], + PROTOBUF_JSON_ENCODER + ); + const message = fromJson( + ExportMetricsServiceRequestSchema, + request as unknown as JsonValue + ); + return toBinary(ExportMetricsServiceRequestSchema, message); }, deserializeResponse: (arg: Uint8Array) => { - return metricsResponseType.decode(arg); + const response = fromBinary(ExportMetricsServiceResponseSchema, arg); + return { + partialSuccess: response.partialSuccess + ? { + rejectedDataPoints: Number( + response.partialSuccess.rejectedDataPoints + ), + errorMessage: response.partialSuccess.errorMessage, + } + : undefined, + }; }, }; diff --git a/experimental/packages/otlp-transformer/src/trace/internal.ts b/experimental/packages/otlp-transformer/src/trace/internal.ts index cbfef6f28d1..e4431926bbf 100644 --- a/experimental/packages/otlp-transformer/src/trace/internal.ts +++ b/experimental/packages/otlp-transformer/src/trace/internal.ts @@ -125,14 +125,18 @@ export function toOtlpSpanEvent( export function createExportTraceServiceRequest( spans: ReadableSpan[], - options?: OtlpEncodingOptions + options?: OtlpEncodingOptions | Encoder ): IExportTraceServiceRequest { - const encoder = getOtlpEncoder(options); + const encoder = isEncoder(options) ? options : getOtlpEncoder(options); return { resourceSpans: spanRecordsToResourceSpans(spans, encoder), }; } +function isEncoder(obj: OtlpEncodingOptions | Encoder | undefined): obj is Encoder { + return obj !== undefined && 'encodeHrTime' in obj; +} + function createResourceMap(readableSpans: ReadableSpan[]) { const resourceMap: Map> = new Map(); for (const record of readableSpans) { diff --git a/experimental/packages/otlp-transformer/src/trace/protobuf/trace.ts b/experimental/packages/otlp-transformer/src/trace/protobuf/trace.ts index 87d5f481fe0..d304c992ce4 100644 --- a/experimental/packages/otlp-transformer/src/trace/protobuf/trace.ts +++ b/experimental/packages/otlp-transformer/src/trace/protobuf/trace.ts @@ -14,29 +14,39 @@ * limitations under the License. */ -import * as root from '../../generated/root'; +import { toBinary, fromBinary, fromJson } from '@bufbuild/protobuf'; +import type { JsonValue } from '@bufbuild/protobuf'; import { ISerializer } from '../../i-serializer'; -import { ExportType } from '../../common/protobuf/protobuf-export-type'; -import { IExportTraceServiceRequest } from '../internal-types'; import { ReadableSpan } from '@opentelemetry/sdk-trace-base'; import { createExportTraceServiceRequest } from '../internal'; import { IExportTraceServiceResponse } from '../export-response'; - -const traceResponseType = root.opentelemetry.proto.collector.trace.v1 - .ExportTraceServiceResponse as ExportType; - -const traceRequestType = root.opentelemetry.proto.collector.trace.v1 - .ExportTraceServiceRequest as ExportType; +import { + ExportTraceServiceRequestSchema, + ExportTraceServiceResponseSchema, +} from '../../generated/opentelemetry/proto/collector/trace/v1/trace_service_pb'; +import { PROTOBUF_JSON_ENCODER } from '../../common/utils'; export const ProtobufTraceSerializer: ISerializer< ReadableSpan[], IExportTraceServiceResponse > = { serializeRequest: (arg: ReadableSpan[]) => { - const request = createExportTraceServiceRequest(arg); - return traceRequestType.encode(request).finish(); + const request = createExportTraceServiceRequest(arg, PROTOBUF_JSON_ENCODER); + const message = fromJson( + ExportTraceServiceRequestSchema, + request as unknown as JsonValue + ); + return toBinary(ExportTraceServiceRequestSchema, message); }, deserializeResponse: (arg: Uint8Array) => { - return traceResponseType.decode(arg); + const response = fromBinary(ExportTraceServiceResponseSchema, arg); + return { + partialSuccess: response.partialSuccess + ? { + rejectedSpans: Number(response.partialSuccess.rejectedSpans), + errorMessage: response.partialSuccess.errorMessage, + } + : undefined, + }; }, }; diff --git a/package-lock.json b/package-lock.json index 25843624546..e32447d34e8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1229,15 +1229,17 @@ "version": "0.208.0", "license": "Apache-2.0", "dependencies": { + "@bufbuild/protobuf": "2.2.3", "@opentelemetry/api-logs": "0.208.0", "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0", "@opentelemetry/sdk-logs": "0.208.0", "@opentelemetry/sdk-metrics": "2.2.0", - "@opentelemetry/sdk-trace-base": "2.2.0", - "protobufjs": "^7.3.0" + "@opentelemetry/sdk-trace-base": "2.2.0" }, "devDependencies": { + "@bufbuild/buf": "1.47.2", + "@bufbuild/protoc-gen-es": "2.2.3", "@opentelemetry/api": "1.9.0", "@types/mocha": "10.0.10", "@types/webpack-env": "1.16.3", @@ -1250,7 +1252,6 @@ "karma-webpack": "5.0.1", "mocha": "11.7.5", "nyc": "17.1.0", - "protobufjs-cli": "1.1.3", "ts-loader": "9.5.4", "typescript": "5.0.4", "webpack": "5.101.3" @@ -1262,6 +1263,150 @@ "@opentelemetry/api": "^1.3.0" } }, + "experimental/packages/otlp-transformer/node_modules/@bufbuild/buf": { + "version": "1.47.2", + "resolved": "https://registry.npmjs.org/@bufbuild/buf/-/buf-1.47.2.tgz", + "integrity": "sha512-glY5kCAoO4+a7HvDb+BLOdoHSdCk4mdXdkp53H8JFz7maOnkxCiHHXgRX+taFyEu25N8ybn7NjZFrZSdRwq2sA==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "bin": { + "buf": "bin/buf", + "protoc-gen-buf-breaking": "bin/protoc-gen-buf-breaking", + "protoc-gen-buf-lint": "bin/protoc-gen-buf-lint" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@bufbuild/buf-darwin-arm64": "1.47.2", + "@bufbuild/buf-darwin-x64": "1.47.2", + "@bufbuild/buf-linux-aarch64": "1.47.2", + "@bufbuild/buf-linux-armv7": "1.47.2", + "@bufbuild/buf-linux-x64": "1.47.2", + "@bufbuild/buf-win32-arm64": "1.47.2", + "@bufbuild/buf-win32-x64": "1.47.2" + } + }, + "experimental/packages/otlp-transformer/node_modules/@bufbuild/buf-darwin-arm64": { + "version": "1.47.2", + "resolved": "https://registry.npmjs.org/@bufbuild/buf-darwin-arm64/-/buf-darwin-arm64-1.47.2.tgz", + "integrity": "sha512-74WerFn06y+azgVfsnzhfbI5wla/OLPDnIvaNJBWHaqya/3bfascJkDylW2GVNHmwG1K/cscpmcc/RJPaO7ntQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "experimental/packages/otlp-transformer/node_modules/@bufbuild/buf-darwin-x64": { + "version": "1.47.2", + "resolved": "https://registry.npmjs.org/@bufbuild/buf-darwin-x64/-/buf-darwin-x64-1.47.2.tgz", + "integrity": "sha512-adAiOacOQe8Ym/YXPCEiq9mrPeKRmDtF2TgqPWTcDy6mF7TqR7hMJINkEEuMd1EeACmXnzMOnXlm9ICtvdYgPg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "experimental/packages/otlp-transformer/node_modules/@bufbuild/buf-linux-aarch64": { + "version": "1.47.2", + "resolved": "https://registry.npmjs.org/@bufbuild/buf-linux-aarch64/-/buf-linux-aarch64-1.47.2.tgz", + "integrity": "sha512-52vY+Owffr5diw2PyfQJqH+Fld6zW6NhNZak4zojvc2MjZKubWM0TfNyM9jXz2YrwyB+cyxkabE60nBI80m37w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "experimental/packages/otlp-transformer/node_modules/@bufbuild/buf-linux-armv7": { + "version": "1.47.2", + "resolved": "https://registry.npmjs.org/@bufbuild/buf-linux-armv7/-/buf-linux-armv7-1.47.2.tgz", + "integrity": "sha512-g9KtpObDeHZ/VG/0b5ZCieOao7L/WYZ0fPqFSs4N07D3APgEDhJG6vLyUcDgJMDgyLcgkNjNz0+XdYQb/tXyQw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "experimental/packages/otlp-transformer/node_modules/@bufbuild/buf-linux-x64": { + "version": "1.47.2", + "resolved": "https://registry.npmjs.org/@bufbuild/buf-linux-x64/-/buf-linux-x64-1.47.2.tgz", + "integrity": "sha512-MODCK2BzD1Mgoyr+5Sp8xA8qMNdytj8hYheyhA5NnCGTkQf8sfqAjpBSAAmKk6Zar8HOlVXML6tzE/ioDFFGwQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "experimental/packages/otlp-transformer/node_modules/@bufbuild/buf-win32-arm64": { + "version": "1.47.2", + "resolved": "https://registry.npmjs.org/@bufbuild/buf-win32-arm64/-/buf-win32-arm64-1.47.2.tgz", + "integrity": "sha512-563YKYWJl3LrCY3G3+zuhb8HwOs6DzWslwGPFkKV2hwHyWyvd1DR1JjiLvw9zX64IKNctQ0HempSqc3kcboaqQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "experimental/packages/otlp-transformer/node_modules/@bufbuild/buf-win32-x64": { + "version": "1.47.2", + "resolved": "https://registry.npmjs.org/@bufbuild/buf-win32-x64/-/buf-win32-x64-1.47.2.tgz", + "integrity": "sha512-Sqcdv7La2xBDh3bTdEYb2f4UTMMqCcYe/D0RELhvQ5wDn6I35V3/2YT1OF5fRuf0BZLCo0OdO37S9L47uHSz2g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, "experimental/packages/sampler-composite": { "name": "@opentelemetry/sampler-composite", "version": "0.208.0", @@ -3184,6 +3329,63 @@ "node": ">=12" } }, + "node_modules/@bufbuild/protobuf": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.2.3.tgz", + "integrity": "sha512-tFQoXHJdkEOSwj5tRIZSPNUuXK3RaR7T1nUrPgbYX1pUbvqqaaZAsfo+NXBPsz5rZMSKVFrgK1WL8Q/MSLvprg==", + "license": "(Apache-2.0 AND BSD-3-Clause)" + }, + "node_modules/@bufbuild/protoc-gen-es": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@bufbuild/protoc-gen-es/-/protoc-gen-es-2.2.3.tgz", + "integrity": "sha512-hdhIV9NmwXXy24DcbnArauv6L5Dv2PjkO9gz2DUhiZ9HPRpP+rmpT8zo5LohjJiuA7YIQGGKKWSKpRg+xcdLSQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@bufbuild/protobuf": "^2.2.3", + "@bufbuild/protoplugin": "2.2.3" + }, + "bin": { + "protoc-gen-es": "bin/protoc-gen-es" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@bufbuild/protobuf": "2.2.3" + }, + "peerDependenciesMeta": { + "@bufbuild/protobuf": { + "optional": true + } + } + }, + "node_modules/@bufbuild/protoplugin": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@bufbuild/protoplugin/-/protoplugin-2.2.3.tgz", + "integrity": "sha512-UsV7mj6NJTZrqIYJK+jNFnnj5tOS7wgNXKyMjebFEpf+OX6pfXE+nx+QPjumOfu4GxdVPfEDnKuwISgqlXSQqw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@bufbuild/protobuf": "2.2.3", + "@typescript/vfs": "^1.5.2", + "typescript": "5.4.5" + } + }, + "node_modules/@bufbuild/protoplugin/node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/@bundled-es-modules/cookie": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@bundled-es-modules/cookie/-/cookie-2.0.1.tgz", @@ -4194,19 +4396,6 @@ "url": "https://opencollective.com/js-sdsl" } }, - "node_modules/@jsdoc/salty": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.9.tgz", - "integrity": "sha512-yYxMVH7Dqw6nO0d5NIV8OQWnitU8k6vXH8NtgqAfIa/IUqRMxRv/NUJJ08VEKbAakwxlgBl5PJdrU0dMPStsnw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "lodash": "^4.17.21" - }, - "engines": { - "node": ">=v12.0.0" - } - }, "node_modules/@jsonjoy.com/base64": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", @@ -6888,31 +7077,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/linkify-it": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", - "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/markdown-it": { - "version": "14.1.2", - "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", - "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/linkify-it": "^5", - "@types/mdurl": "^2" - } - }, - "node_modules/@types/mdurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", - "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/methods": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", @@ -7471,6 +7635,19 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@typescript/vfs": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/@typescript/vfs/-/vfs-1.6.2.tgz", + "integrity": "sha512-hoBwJwcbKHmvd2QVebiytN1aELvpk9B74B4L1mFm/XT1Q/VOYAWl2vQ9AWRFtQq8zmz6enTpfTV8WRc4ATjW/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1" + }, + "peerDependencies": { + "typescript": "*" + } + }, "node_modules/@ungap/structured-clone": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", @@ -8567,13 +8744,6 @@ "node": ">= 6" } }, - "node_modules/bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "dev": true, - "license": "MIT" - }, "node_modules/body-parser": { "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", @@ -9160,19 +9330,6 @@ ], "license": "CC-BY-4.0" }, - "node_modules/catharsis": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", - "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", - "dev": true, - "license": "MIT", - "dependencies": { - "lodash": "^4.17.15" - }, - "engines": { - "node": ">= 10" - } - }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -11163,93 +11320,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/escodegen": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", - "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=4.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/escodegen/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/escodegen/node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/escodegen/node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/escodegen/node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/escodegen/node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/eslint": { "version": "8.57.1", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", @@ -14448,82 +14518,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/js2xmlparser": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", - "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "xmlcreate": "^2.0.4" - } - }, - "node_modules/jsdoc": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.5.tgz", - "integrity": "sha512-P4C6MWP9yIlMiK8nwoZvxN84vb6MsnXcHuy7XzVOvQoCizWX5JFCBsWIIWKXBltpoRZXddUOVQmCTOZt9yDj9g==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@babel/parser": "^7.20.15", - "@jsdoc/salty": "^0.2.1", - "@types/markdown-it": "^14.1.1", - "bluebird": "^3.7.2", - "catharsis": "^0.9.0", - "escape-string-regexp": "^2.0.0", - "js2xmlparser": "^4.0.2", - "klaw": "^3.0.0", - "markdown-it": "^14.1.0", - "markdown-it-anchor": "^8.6.7", - "marked": "^4.0.10", - "mkdirp": "^1.0.4", - "requizzle": "^0.2.3", - "strip-json-comments": "^3.1.0", - "underscore": "~1.13.2" - }, - "bin": { - "jsdoc": "jsdoc.js" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/jsdoc/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/jsdoc/node_modules/marked": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", - "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", - "dev": true, - "license": "MIT", - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 12" - } - }, - "node_modules/jsdoc/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -15135,16 +15129,6 @@ "node": ">=0.10.0" } }, - "node_modules/klaw": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", - "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.1.9" - } - }, "node_modules/launch-editor": { "version": "2.12.0", "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.12.0.tgz", @@ -16280,17 +16264,6 @@ "markdown-it": "bin/markdown-it.mjs" } }, - "node_modules/markdown-it-anchor": { - "version": "8.6.7", - "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.7.tgz", - "integrity": "sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==", - "dev": true, - "license": "Unlicense", - "peerDependencies": { - "@types/markdown-it": "*", - "markdown-it": "*" - } - }, "node_modules/markdown-it/node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -20171,69 +20144,6 @@ "node": ">=12.0.0" } }, - "node_modules/protobufjs-cli": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.1.3.tgz", - "integrity": "sha512-MqD10lqF+FMsOayFiNOdOGNlXc4iKDCf0ZQPkPR+gizYh9gqUeGTWulABUCdI+N67w5RfJ6xhgX4J8pa8qmMXQ==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "chalk": "^4.0.0", - "escodegen": "^1.13.0", - "espree": "^9.0.0", - "estraverse": "^5.1.0", - "glob": "^8.0.0", - "jsdoc": "^4.0.0", - "minimist": "^1.2.0", - "semver": "^7.1.2", - "tmp": "^0.2.1", - "uglify-js": "^3.7.7" - }, - "bin": { - "pbjs": "bin/pbjs", - "pbts": "bin/pbts" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "protobufjs": "^7.0.0" - } - }, - "node_modules/protobufjs-cli/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/protobufjs-cli/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/protocols": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/protocols/-/protocols-2.0.2.tgz", @@ -20855,16 +20765,6 @@ "dev": true, "license": "MIT" }, - "node_modules/requizzle": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz", - "integrity": "sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==", - "dev": true, - "license": "MIT", - "dependencies": { - "lodash": "^4.17.21" - } - }, "node_modules/resolve": { "version": "1.22.11", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", @@ -23465,6 +23365,7 @@ "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", "dev": true, "license": "BSD-2-Clause", + "optional": true, "bin": { "uglifyjs": "bin/uglifyjs" }, @@ -23472,13 +23373,6 @@ "node": ">=0.8.0" } }, - "node_modules/underscore": { - "version": "1.13.7", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz", - "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==", - "dev": true, - "license": "MIT" - }, "node_modules/undici": { "version": "7.16.0", "resolved": "https://registry.npmjs.org/undici/-/undici-7.16.0.tgz", @@ -25096,13 +24990,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/xmlcreate": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", - "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", - "dev": true, - "license": "Apache-2.0" - }, "node_modules/xorshift": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/xorshift/-/xorshift-1.2.0.tgz", From b421e3ef9b586eee534c55db315df2630c4594df Mon Sep 17 00:00:00 2001 From: overbalance Date: Tue, 2 Dec 2025 17:49:32 -0600 Subject: [PATCH 02/15] fix(otlp-transformer): fix protobuf serializers and update tests for protobuf-es Update protobuf serializers to use fromJson + toBinary pattern with PROTOBUF_JSON_ENCODER for correct serialization. Add JSON.parse/stringify to strip undefined values that fromJson doesn't accept. Update tests to use protobuf-es APIs with toJson(alwaysEmitImplicit: true) and match protobuf JSON format expectations (string enums, string 64-bit integers, base64 bytes, all default fields). --- .../src/logs/protobuf/logs.ts | 3 +- .../src/metrics/protobuf/metrics.ts | 3 +- .../src/trace/protobuf/trace.ts | 3 +- .../otlp-transformer/test/logs.test.ts | 87 ++++++------ .../otlp-transformer/test/metrics.test.ts | 131 +++++++++++++----- .../otlp-transformer/test/trace.test.ts | 91 ++++++------ 6 files changed, 193 insertions(+), 125 deletions(-) diff --git a/experimental/packages/otlp-transformer/src/logs/protobuf/logs.ts b/experimental/packages/otlp-transformer/src/logs/protobuf/logs.ts index 36ef894f3a4..2eb26790276 100644 --- a/experimental/packages/otlp-transformer/src/logs/protobuf/logs.ts +++ b/experimental/packages/otlp-transformer/src/logs/protobuf/logs.ts @@ -34,9 +34,10 @@ export const ProtobufLogsSerializer: ISerializer< > = { serializeRequest: (arg: ReadableLogRecord[]) => { const request = createExportLogsServiceRequest(arg, PROTOBUF_JSON_ENCODER); + // JSON.parse(JSON.stringify(...)) removes undefined values which fromJson doesn't accept const message = fromJson( ExportLogsServiceRequestSchema, - request as unknown as JsonValue + JSON.parse(JSON.stringify(request)) as JsonValue ); return toBinary(ExportLogsServiceRequestSchema, message); }, diff --git a/experimental/packages/otlp-transformer/src/metrics/protobuf/metrics.ts b/experimental/packages/otlp-transformer/src/metrics/protobuf/metrics.ts index 63bed0c9b9b..4494998986d 100644 --- a/experimental/packages/otlp-transformer/src/metrics/protobuf/metrics.ts +++ b/experimental/packages/otlp-transformer/src/metrics/protobuf/metrics.ts @@ -35,9 +35,10 @@ export const ProtobufMetricsSerializer: ISerializer< [arg], PROTOBUF_JSON_ENCODER ); + // JSON.parse(JSON.stringify(...)) removes undefined values which fromJson doesn't accept const message = fromJson( ExportMetricsServiceRequestSchema, - request as unknown as JsonValue + JSON.parse(JSON.stringify(request)) as JsonValue ); return toBinary(ExportMetricsServiceRequestSchema, message); }, diff --git a/experimental/packages/otlp-transformer/src/trace/protobuf/trace.ts b/experimental/packages/otlp-transformer/src/trace/protobuf/trace.ts index d304c992ce4..4e44e5df306 100644 --- a/experimental/packages/otlp-transformer/src/trace/protobuf/trace.ts +++ b/experimental/packages/otlp-transformer/src/trace/protobuf/trace.ts @@ -32,9 +32,10 @@ export const ProtobufTraceSerializer: ISerializer< > = { serializeRequest: (arg: ReadableSpan[]) => { const request = createExportTraceServiceRequest(arg, PROTOBUF_JSON_ENCODER); + // JSON.parse(JSON.stringify(...)) removes undefined values which fromJson doesn't accept const message = fromJson( ExportTraceServiceRequestSchema, - request as unknown as JsonValue + JSON.parse(JSON.stringify(request)) as JsonValue ); return toBinary(ExportTraceServiceRequestSchema, message); }, diff --git a/experimental/packages/otlp-transformer/test/logs.test.ts b/experimental/packages/otlp-transformer/test/logs.test.ts index 4ddd4ff3b45..f0e2b30c69f 100644 --- a/experimental/packages/otlp-transformer/test/logs.test.ts +++ b/experimental/packages/otlp-transformer/test/logs.test.ts @@ -19,8 +19,12 @@ import { Resource, resourceFromAttributes } from '@opentelemetry/resources'; import * as assert from 'assert'; import { ReadableLogRecord } from '@opentelemetry/sdk-logs'; import { SeverityNumber } from '@opentelemetry/api-logs'; -import { toBase64 } from './utils'; -import * as root from '../src/generated/root'; +import { hexToBase64 } from '../src/common/utils'; +import { fromBinary, toBinary, create, toJson } from '@bufbuild/protobuf'; +import { + ExportLogsServiceRequestSchema, + ExportLogsServiceResponseSchema, +} from '../src/generated/opentelemetry/proto/collector/logs/v1/logs_service_pb'; import { OtlpEncodingOptions } from '../src/common/internal-types'; import { ESeverityNumber, @@ -97,9 +101,15 @@ function createExpectedLogJson( }; } -function createExpectedLogProtobuf(): IExportLogsServiceRequest { - const traceId = toBase64('00000000000000000000000000000001'); - const spanId = toBase64('0000000000000002'); +// Returns untyped object for JSON comparison (toJson output differs from typed interface) +function createExpectedLogProtobuf(): unknown { + // protobuf JSON format uses base64 for bytes + const traceId = hexToBase64('00000000000000000000000000000001'); + const spanId = hexToBase64('0000000000000002'); + + // protobuf JSON format uses string representation for 64-bit integers + const timeUnixNano = '1680253513123241635'; + const observedTimeUnixNano = '1683526948965142784'; return { resourceLogs: [ @@ -112,15 +122,23 @@ function createExpectedLogProtobuf(): IExportLogsServiceRequest { }, ], droppedAttributesCount: 0, + entityRefs: [], }, + schemaUrl: '', scopeLogs: [ { - scope: { name: 'scope_name_1', version: '0.1.0' }, + scope: { + name: 'scope_name_1', + version: '0.1.0', + attributes: [], + droppedAttributesCount: 0, + }, logRecords: [ { - timeUnixNano: 1680253513123241700, - observedTimeUnixNano: 1683526948965142800, - severityNumber: ESeverityNumber.SEVERITY_NUMBER_ERROR, + timeUnixNano: timeUnixNano, + observedTimeUnixNano: observedTimeUnixNano, + // protobuf-es toJson outputs some enums as strings + severityNumber: 'SEVERITY_NUMBER_ERROR', severityText: 'error', body: { stringValue: 'some_log_body' }, eventName: 'some.event.name', @@ -325,39 +343,27 @@ describe('Logs', () => { it('serializes an export request', () => { const serialized = ProtobufLogsSerializer.serializeRequest([log_1_1_1]); assert.ok(serialized, 'serialized response is undefined'); - const decoded = - root.opentelemetry.proto.collector.logs.v1.ExportLogsServiceRequest.decode( - serialized - ); - + const decoded = fromBinary(ExportLogsServiceRequestSchema, serialized); const expected = createExpectedLogProtobuf(); - const decodedObj = - root.opentelemetry.proto.collector.logs.v1.ExportLogsServiceRequest.toObject( - decoded, - { - // This incurs some precision loss that's taken into account in createExpectedLogsProtobuf() - // Using String here will incur the same precision loss on browser only, using Number to prevent having to - // have different assertions for browser and Node.js - longs: Number, - // Convert to String (Base64) as otherwise the type will be different for Node.js (Buffer) and Browser (Uint8Array) - // and this fails assertions. - bytes: String, - } - ); - - assert.deepStrictEqual(decodedObj, expected); + // toJson converts to protobuf JSON format (strings for 64-bit ints, base64 for bytes) + // alwaysEmitImplicit includes default values like droppedAttributesCount: 0 + const decodedJson = toJson(ExportLogsServiceRequestSchema, decoded, { + alwaysEmitImplicit: true, + }); + assert.deepStrictEqual(decodedJson, expected); }); it('deserializes a response', () => { - const protobufSerializedResponse = - root.opentelemetry.proto.collector.logs.v1.ExportLogsServiceResponse.encode( - { - partialSuccess: { - errorMessage: 'foo', - rejectedLogRecords: 1, - }, - } - ).finish(); + const response = create(ExportLogsServiceResponseSchema, { + partialSuccess: { + errorMessage: 'foo', + rejectedLogRecords: BigInt(1), + }, + }); + const protobufSerializedResponse = toBinary( + ExportLogsServiceResponseSchema, + response + ); const deserializedResponse = ProtobufLogsSerializer.deserializeResponse( protobufSerializedResponse @@ -368,10 +374,7 @@ describe('Logs', () => { 'partialSuccess not present in the deserialized message' ); assert.equal(deserializedResponse.partialSuccess.errorMessage, 'foo'); - assert.equal( - Number(deserializedResponse.partialSuccess.rejectedLogRecords), - 1 - ); + assert.equal(deserializedResponse.partialSuccess.rejectedLogRecords, 1); }); it('does not throw when deserializing an empty response', () => { diff --git a/experimental/packages/otlp-transformer/test/metrics.test.ts b/experimental/packages/otlp-transformer/test/metrics.test.ts index ffd100febb3..d99ba6a18dd 100644 --- a/experimental/packages/otlp-transformer/test/metrics.test.ts +++ b/experimental/packages/otlp-transformer/test/metrics.test.ts @@ -24,8 +24,12 @@ import { import * as assert from 'assert'; import { createExportMetricsServiceRequest } from '../src/metrics/internal'; import { EAggregationTemporality } from '../src/metrics/internal-types'; -import { hrTime, hrTimeToNanoseconds } from '@opentelemetry/core'; -import * as root from '../src/generated/root'; +import { hrTime } from '@opentelemetry/core'; +import { fromBinary, toBinary, create, toJson } from '@bufbuild/protobuf'; +import { + ExportMetricsServiceRequestSchema, + ExportMetricsServiceResponseSchema, +} from '../src/generated/opentelemetry/proto/collector/metrics/v1/metrics_service_pb'; import { encodeAsLongBits, encodeAsString } from '../src/common/utils'; import { ProtobufMetricsSerializer } from '../src/metrics/protobuf'; import { JsonMetricsSerializer } from '../src/metrics/json'; @@ -812,43 +816,102 @@ describe('Metrics', () => { ]) ); assert.ok(serialized, 'serialized response is undefined'); - const decoded = - root.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceRequest.decode( - serialized - ); + const decoded = fromBinary(ExportMetricsServiceRequestSchema, serialized); + // toJson converts to protobuf JSON format (strings for 64-bit ints) + // alwaysEmitImplicit includes default values like droppedAttributesCount: 0 + const decodedJson = toJson(ExportMetricsServiceRequestSchema, decoded, { + alwaysEmitImplicit: true, + }); - const decodedObj = - root.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceRequest.toObject( - decoded, - { - longs: Number, - } - ); + // protobuf JSON format uses string representation for 64-bit integers + // and string enums for aggregationTemporality + const expectedProtobufAttributes = [ + { + key: 'string-attribute', + value: { + stringValue: 'some attribute value', + }, + }, + { + key: 'int-attribute', + value: { + intValue: '1', // 64-bit int as string in protobuf JSON + }, + }, + { + key: 'double-attribute', + value: { + doubleValue: 1.1, + }, + }, + { + key: 'boolean-attribute', + value: { + boolValue: true, + }, + }, + { + key: 'array-attribute', + value: { + arrayValue: { + values: [ + { + stringValue: 'attribute value 1', + }, + { + stringValue: 'attribute value 2', + }, + ], + }, + }, + }, + ]; const expected = { resourceMetrics: [ { - resource: expectedResource, + resource: { + attributes: [ + { + key: 'resource-attribute', + value: { + stringValue: 'resource attribute value', + }, + }, + ], + droppedAttributesCount: 0, + entityRefs: [], + }, + schemaUrl: '', scopeMetrics: [ { - scope: expectedScope, + scope: { + name: 'mylib', + version: '0.1.0', + attributes: [], + droppedAttributesCount: 0, + }, schemaUrl: expectedSchemaUrl, metrics: [ { name: 'counter', description: 'this is a description', unit: '1', + metadata: [], sum: { dataPoints: [ { - attributes: expectedAttributes, - startTimeUnixNano: hrTimeToNanoseconds(START_TIME), - timeUnixNano: hrTimeToNanoseconds(END_TIME), - asInt: 10, + attributes: expectedProtobufAttributes, + // Use encodeAsString which preserves full precision via BigInt + startTimeUnixNano: encodeAsString(START_TIME), + timeUnixNano: encodeAsString(END_TIME), + asInt: '10', + exemplars: [], + flags: 0, }, ], - aggregationTemporality: - EAggregationTemporality.AGGREGATION_TEMPORALITY_DELTA, + // protobuf-es toJson outputs enums as strings + aggregationTemporality: 'AGGREGATION_TEMPORALITY_DELTA', isMonotonic: true, }, }, @@ -858,19 +921,20 @@ describe('Metrics', () => { }, ], }; - assert.deepStrictEqual(decodedObj, expected); + assert.deepStrictEqual(decodedJson, expected); }); it('deserializes a response', () => { - const protobufSerializedResponse = - root.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceResponse.encode( - { - partialSuccess: { - errorMessage: 'foo', - rejectedDataPoints: 1, - }, - } - ).finish(); + const response = create(ExportMetricsServiceResponseSchema, { + partialSuccess: { + errorMessage: 'foo', + rejectedDataPoints: BigInt(1), + }, + }); + const protobufSerializedResponse = toBinary( + ExportMetricsServiceResponseSchema, + response + ); const deserializedResponse = ProtobufMetricsSerializer.deserializeResponse( @@ -882,10 +946,7 @@ describe('Metrics', () => { 'partialSuccess not present in the deserialized message' ); assert.equal(deserializedResponse.partialSuccess.errorMessage, 'foo'); - assert.equal( - Number(deserializedResponse.partialSuccess.rejectedDataPoints), - 1 - ); + assert.equal(deserializedResponse.partialSuccess.rejectedDataPoints, 1); }); it('does not throw when deserializing an empty response', () => { diff --git a/experimental/packages/otlp-transformer/test/trace.test.ts b/experimental/packages/otlp-transformer/test/trace.test.ts index 5a1aaf13a23..77bf7f3d82b 100644 --- a/experimental/packages/otlp-transformer/test/trace.test.ts +++ b/experimental/packages/otlp-transformer/test/trace.test.ts @@ -13,13 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import * as root from '../src/generated/root'; +import { fromBinary, toBinary, create, toJson } from '@bufbuild/protobuf'; +import { + ExportTraceServiceRequestSchema, + ExportTraceServiceResponseSchema, +} from '../src/generated/opentelemetry/proto/collector/trace/v1/trace_service_pb'; import { SpanKind, SpanStatusCode, TraceFlags } from '@opentelemetry/api'; import { TraceState } from '@opentelemetry/core'; import { Resource, resourceFromAttributes } from '@opentelemetry/resources'; import { ReadableSpan } from '@opentelemetry/sdk-trace-base'; import * as assert from 'assert'; -import { toBase64 } from './utils'; +import { hexToBase64 } from '../src/common/utils'; import { OtlpEncodingOptions } from '../src/common/internal-types'; import { ESpanKind, EStatusCode } from '../src/trace/internal-types'; import { createExportTraceServiceRequest } from '../src/trace/internal'; @@ -142,15 +146,17 @@ function createExpectedSpanJson(options: OtlpEncodingOptions) { } function createExpectedSpanProtobuf() { - const startTime = 1640715557342725400; - const endTime = 1640715558642725400; - const eventTime = 1640715558542725400; - - const traceId = toBase64('00000000000000000000000000000001'); - const spanId = toBase64('0000000000000002'); - const parentSpanId = toBase64('0000000000000001'); - const linkSpanId = toBase64('0000000000000003'); - const linkTraceId = toBase64('00000000000000000000000000000002'); + // protobuf JSON format uses string representation for 64-bit integers + const startTime = '1640715557342725388'; + const endTime = '1640715558642725388'; + const eventTime = '1640715558542725388'; + + // protobuf JSON format uses base64 for bytes + const traceId = hexToBase64('00000000000000000000000000000001'); + const spanId = hexToBase64('0000000000000002'); + const parentSpanId = hexToBase64('0000000000000001'); + const linkSpanId = hexToBase64('0000000000000003'); + const linkTraceId = hexToBase64('00000000000000000000000000000002'); return { resourceSpans: [ @@ -163,10 +169,17 @@ function createExpectedSpanProtobuf() { }, ], droppedAttributesCount: 0, + entityRefs: [], }, + schemaUrl: '', scopeSpans: [ { - scope: { name: 'myLib', version: '0.1.0' }, + scope: { + name: 'myLib', + version: '0.1.0', + attributes: [], + droppedAttributesCount: 0, + }, spans: [ { traceId: traceId, @@ -174,7 +187,7 @@ function createExpectedSpanProtobuf() { traceState: 'span=bar', parentSpanId: parentSpanId, name: 'span-name', - kind: ESpanKind.SPAN_KIND_CLIENT, + kind: 'SPAN_KIND_CLIENT', links: [ { droppedAttributesCount: 0, @@ -219,7 +232,8 @@ function createExpectedSpanProtobuf() { droppedEventsCount: 0, droppedLinksCount: 0, status: { - code: EStatusCode.STATUS_CODE_OK, + code: 'STATUS_CODE_OK', + message: '', }, flags: 0x101, // TraceFlags (0x01) | HAS_IS_REMOTE }, @@ -477,37 +491,27 @@ describe('Trace', () => { it('serializes an export request', () => { const serialized = ProtobufTraceSerializer.serializeRequest([span]); assert.ok(serialized, 'serialized response is undefined'); - const decoded = - root.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest.decode( - serialized - ); + const decoded = fromBinary(ExportTraceServiceRequestSchema, serialized); const expected = createExpectedSpanProtobuf(); - const decodedObj = - root.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest.toObject( - decoded, - { - // This incurs some precision loss that's taken into account in createExpectedSpanProtobuf() - // Using String here will incur the same precision loss on browser only, using Number to prevent having to - // have different assertions for browser and Node.js - longs: Number, - // Convert to String (Base64) as otherwise the type will be different for Node.js (Buffer) and Browser (Uint8Array) - // and this fails assertions. - bytes: String, - } - ); - assert.deepStrictEqual(decodedObj, expected); + // toJson converts to protobuf JSON format (strings for 64-bit ints, base64 for bytes) + // alwaysEmitImplicit includes default values like droppedAttributesCount: 0 + const decodedJson = toJson(ExportTraceServiceRequestSchema, decoded, { + alwaysEmitImplicit: true, + }); + assert.deepStrictEqual(decodedJson, expected); }); it('deserializes a response', () => { - const protobufSerializedResponse = - root.opentelemetry.proto.collector.trace.v1.ExportTraceServiceResponse.encode( - { - partialSuccess: { - errorMessage: 'foo', - rejectedSpans: 1, - }, - } - ).finish(); + const response = create(ExportTraceServiceResponseSchema, { + partialSuccess: { + errorMessage: 'foo', + rejectedSpans: BigInt(1), + }, + }); + const protobufSerializedResponse = toBinary( + ExportTraceServiceResponseSchema, + response + ); const deserializedResponse = ProtobufTraceSerializer.deserializeResponse( protobufSerializedResponse @@ -518,10 +522,7 @@ describe('Trace', () => { 'partialSuccess not present in the deserialized message' ); assert.equal(deserializedResponse.partialSuccess.errorMessage, 'foo'); - assert.equal( - Number(deserializedResponse.partialSuccess.rejectedSpans), - 1 - ); + assert.equal(deserializedResponse.partialSuccess.rejectedSpans, 1); }); it('does not throw when deserializing an empty response', () => { From 8a057cc9a762b7247d971dc5cdefa549f934ecf6 Mon Sep 17 00:00:00 2001 From: overbalance Date: Tue, 2 Dec 2025 18:03:01 -0600 Subject: [PATCH 03/15] test(otlp-transformer): simplify protobuf test expected values --- .../src/common/internal-types.ts | 2 +- .../otlp-transformer/src/common/utils.ts | 6 ++ .../otlp-transformer/src/logs/internal.ts | 6 +- .../otlp-transformer/src/metrics/internal.ts | 6 +- .../otlp-transformer/src/trace/internal.ts | 6 +- .../otlp-transformer/test/logs.test.ts | 18 +---- .../otlp-transformer/test/metrics.test.ts | 58 +++------------ .../otlp-transformer/test/trace.test.ts | 72 +++++++------------ .../packages/otlp-transformer/test/utils.ts | 32 --------- 9 files changed, 49 insertions(+), 157 deletions(-) delete mode 100644 experimental/packages/otlp-transformer/test/utils.ts diff --git a/experimental/packages/otlp-transformer/src/common/internal-types.ts b/experimental/packages/otlp-transformer/src/common/internal-types.ts index 1e3e35dcd25..315fa46f984 100644 --- a/experimental/packages/otlp-transformer/src/common/internal-types.ts +++ b/experimental/packages/otlp-transformer/src/common/internal-types.ts @@ -91,7 +91,7 @@ export interface LongBits { high: number; } -export type Fixed64 = LongBits | string | number | bigint; +export type Fixed64 = LongBits | string | number; export interface OtlpEncodingOptions { /** Convert trace and span IDs to hex strings. */ diff --git a/experimental/packages/otlp-transformer/src/common/utils.ts b/experimental/packages/otlp-transformer/src/common/utils.ts index 13ec8919616..cfcc9830f1c 100644 --- a/experimental/packages/otlp-transformer/src/common/utils.ts +++ b/experimental/packages/otlp-transformer/src/common/utils.ts @@ -106,6 +106,12 @@ export const PROTOBUF_JSON_ENCODER: Encoder = { encodeOptionalSpanContext: optionalHexToBase64, }; +export function isEncoder( + obj: OtlpEncodingOptions | Encoder | undefined +): obj is Encoder { + return obj !== undefined && 'encodeHrTime' in obj; +} + export function getOtlpEncoder(options?: OtlpEncodingOptions): Encoder { if (options === undefined) { return DEFAULT_ENCODER; diff --git a/experimental/packages/otlp-transformer/src/logs/internal.ts b/experimental/packages/otlp-transformer/src/logs/internal.ts index 86c62745aef..27d3afb4269 100644 --- a/experimental/packages/otlp-transformer/src/logs/internal.ts +++ b/experimental/packages/otlp-transformer/src/logs/internal.ts @@ -22,7 +22,7 @@ import { IResourceLogs, } from './internal-types'; import { Resource } from '@opentelemetry/resources'; -import { Encoder, getOtlpEncoder } from '../common/utils'; +import { Encoder, getOtlpEncoder, isEncoder } from '../common/utils'; import { createInstrumentationScope, createResource, @@ -43,10 +43,6 @@ export function createExportLogsServiceRequest( }; } -function isEncoder(obj: OtlpEncodingOptions | Encoder | undefined): obj is Encoder { - return obj !== undefined && 'encodeHrTime' in obj; -} - function createResourceMap( logRecords: ReadableLogRecord[] ): Map> { diff --git a/experimental/packages/otlp-transformer/src/metrics/internal.ts b/experimental/packages/otlp-transformer/src/metrics/internal.ts index 52d4d127554..ee842f859c5 100644 --- a/experimental/packages/otlp-transformer/src/metrics/internal.ts +++ b/experimental/packages/otlp-transformer/src/metrics/internal.ts @@ -35,7 +35,7 @@ import { IResourceMetrics, IScopeMetrics, } from './internal-types'; -import { Encoder, getOtlpEncoder } from '../common/utils'; +import { Encoder, getOtlpEncoder, isEncoder } from '../common/utils'; import { createInstrumentationScope, createResource, @@ -55,10 +55,6 @@ export function toResourceMetrics( }; } -function isEncoder(obj: OtlpEncodingOptions | Encoder | undefined): obj is Encoder { - return obj !== undefined && 'encodeHrTime' in obj; -} - export function toScopeMetrics( scopeMetrics: ScopeMetrics[], encoder: Encoder diff --git a/experimental/packages/otlp-transformer/src/trace/internal.ts b/experimental/packages/otlp-transformer/src/trace/internal.ts index e4431926bbf..856f134917b 100644 --- a/experimental/packages/otlp-transformer/src/trace/internal.ts +++ b/experimental/packages/otlp-transformer/src/trace/internal.ts @@ -32,7 +32,7 @@ import { ISpan, } from './internal-types'; import { OtlpEncodingOptions } from '../common/internal-types'; -import { getOtlpEncoder } from '../common/utils'; +import { getOtlpEncoder, isEncoder } from '../common/utils'; // Span flags constants matching the OTLP specification const SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK = 0x100; @@ -133,10 +133,6 @@ export function createExportTraceServiceRequest( }; } -function isEncoder(obj: OtlpEncodingOptions | Encoder | undefined): obj is Encoder { - return obj !== undefined && 'encodeHrTime' in obj; -} - function createResourceMap(readableSpans: ReadableSpan[]) { const resourceMap: Map> = new Map(); for (const record of readableSpans) { diff --git a/experimental/packages/otlp-transformer/test/logs.test.ts b/experimental/packages/otlp-transformer/test/logs.test.ts index f0e2b30c69f..58f48b84538 100644 --- a/experimental/packages/otlp-transformer/test/logs.test.ts +++ b/experimental/packages/otlp-transformer/test/logs.test.ts @@ -121,23 +121,15 @@ function createExpectedLogProtobuf(): unknown { value: { stringValue: 'some attribute value' }, }, ], - droppedAttributesCount: 0, - entityRefs: [], }, - schemaUrl: '', scopeLogs: [ { - scope: { - name: 'scope_name_1', - version: '0.1.0', - attributes: [], - droppedAttributesCount: 0, - }, + scope: { name: 'scope_name_1', version: '0.1.0' }, logRecords: [ { timeUnixNano: timeUnixNano, observedTimeUnixNano: observedTimeUnixNano, - // protobuf-es toJson outputs some enums as strings + // protobuf-es toJson outputs enums as strings severityNumber: 'SEVERITY_NUMBER_ERROR', severityText: 'error', body: { stringValue: 'some_log_body' }, @@ -148,7 +140,6 @@ function createExpectedLogProtobuf(): unknown { value: { stringValue: 'some attribute value' }, }, ], - droppedAttributesCount: 0, flags: 1, traceId: traceId, spanId: spanId, @@ -346,10 +337,7 @@ describe('Logs', () => { const decoded = fromBinary(ExportLogsServiceRequestSchema, serialized); const expected = createExpectedLogProtobuf(); // toJson converts to protobuf JSON format (strings for 64-bit ints, base64 for bytes) - // alwaysEmitImplicit includes default values like droppedAttributesCount: 0 - const decodedJson = toJson(ExportLogsServiceRequestSchema, decoded, { - alwaysEmitImplicit: true, - }); + const decodedJson = toJson(ExportLogsServiceRequestSchema, decoded); assert.deepStrictEqual(decodedJson, expected); }); diff --git a/experimental/packages/otlp-transformer/test/metrics.test.ts b/experimental/packages/otlp-transformer/test/metrics.test.ts index d99ba6a18dd..9419f155f48 100644 --- a/experimental/packages/otlp-transformer/test/metrics.test.ts +++ b/experimental/packages/otlp-transformer/test/metrics.test.ts @@ -818,49 +818,24 @@ describe('Metrics', () => { assert.ok(serialized, 'serialized response is undefined'); const decoded = fromBinary(ExportMetricsServiceRequestSchema, serialized); // toJson converts to protobuf JSON format (strings for 64-bit ints) - // alwaysEmitImplicit includes default values like droppedAttributesCount: 0 - const decodedJson = toJson(ExportMetricsServiceRequestSchema, decoded, { - alwaysEmitImplicit: true, - }); + const decodedJson = toJson(ExportMetricsServiceRequestSchema, decoded); // protobuf JSON format uses string representation for 64-bit integers - // and string enums for aggregationTemporality const expectedProtobufAttributes = [ { key: 'string-attribute', - value: { - stringValue: 'some attribute value', - }, - }, - { - key: 'int-attribute', - value: { - intValue: '1', // 64-bit int as string in protobuf JSON - }, - }, - { - key: 'double-attribute', - value: { - doubleValue: 1.1, - }, - }, - { - key: 'boolean-attribute', - value: { - boolValue: true, - }, + value: { stringValue: 'some attribute value' }, }, + { key: 'int-attribute', value: { intValue: '1' } }, + { key: 'double-attribute', value: { doubleValue: 1.1 } }, + { key: 'boolean-attribute', value: { boolValue: true } }, { key: 'array-attribute', value: { arrayValue: { values: [ - { - stringValue: 'attribute value 1', - }, - { - stringValue: 'attribute value 2', - }, + { stringValue: 'attribute value 1' }, + { stringValue: 'attribute value 2' }, ], }, }, @@ -874,40 +849,27 @@ describe('Metrics', () => { attributes: [ { key: 'resource-attribute', - value: { - stringValue: 'resource attribute value', - }, + value: { stringValue: 'resource attribute value' }, }, ], - droppedAttributesCount: 0, - entityRefs: [], }, - schemaUrl: '', scopeMetrics: [ { - scope: { - name: 'mylib', - version: '0.1.0', - attributes: [], - droppedAttributesCount: 0, - }, + scope: { name: 'mylib', version: '0.1.0' }, schemaUrl: expectedSchemaUrl, metrics: [ { name: 'counter', description: 'this is a description', unit: '1', - metadata: [], sum: { dataPoints: [ { attributes: expectedProtobufAttributes, - // Use encodeAsString which preserves full precision via BigInt + // encodeAsString preserves full precision via BigInt startTimeUnixNano: encodeAsString(START_TIME), timeUnixNano: encodeAsString(END_TIME), asInt: '10', - exemplars: [], - flags: 0, }, ], // protobuf-es toJson outputs enums as strings diff --git a/experimental/packages/otlp-transformer/test/trace.test.ts b/experimental/packages/otlp-transformer/test/trace.test.ts index 77bf7f3d82b..2aae3150a32 100644 --- a/experimental/packages/otlp-transformer/test/trace.test.ts +++ b/experimental/packages/otlp-transformer/test/trace.test.ts @@ -168,18 +168,10 @@ function createExpectedSpanProtobuf() { value: { stringValue: 'resource attribute value' }, }, ], - droppedAttributesCount: 0, - entityRefs: [], }, - schemaUrl: '', scopeSpans: [ { - scope: { - name: 'myLib', - version: '0.1.0', - attributes: [], - droppedAttributesCount: 0, - }, + scope: { name: 'myLib', version: '0.1.0' }, spans: [ { traceId: traceId, @@ -187,55 +179,46 @@ function createExpectedSpanProtobuf() { traceState: 'span=bar', parentSpanId: parentSpanId, name: 'span-name', + // protobuf-es toJson outputs enums as strings kind: 'SPAN_KIND_CLIENT', - links: [ + startTimeUnixNano: startTime, + endTimeUnixNano: endTime, + attributes: [ { - droppedAttributesCount: 0, - spanId: linkSpanId, - traceId: linkTraceId, - traceState: 'link=foo', - attributes: [ - { - key: 'link-attribute', - value: { - stringValue: 'string value', - }, - }, - ], - flags: 0x101, // TraceFlags (0x01) | HAS_IS_REMOTE + key: 'string-attribute', + value: { stringValue: 'some attribute value' }, }, ], - startTimeUnixNano: startTime, - endTimeUnixNano: endTime, events: [ { - droppedAttributesCount: 0, + name: 'some event', + timeUnixNano: eventTime, attributes: [ { key: 'event-attribute', - value: { - stringValue: 'some string value', - }, + value: { stringValue: 'some string value' }, }, ], - name: 'some event', - timeUnixNano: eventTime, }, ], - attributes: [ + links: [ { - key: 'string-attribute', - value: { stringValue: 'some attribute value' }, + traceId: linkTraceId, + spanId: linkSpanId, + traceState: 'link=foo', + attributes: [ + { + key: 'link-attribute', + value: { stringValue: 'string value' }, + }, + ], + // TraceFlags (0x01) | HAS_IS_REMOTE + flags: 0x101, }, ], - droppedAttributesCount: 0, - droppedEventsCount: 0, - droppedLinksCount: 0, - status: { - code: 'STATUS_CODE_OK', - message: '', - }, - flags: 0x101, // TraceFlags (0x01) | HAS_IS_REMOTE + status: { code: 'STATUS_CODE_OK' }, + // TraceFlags (0x01) | HAS_IS_REMOTE + flags: 0x101, }, ], schemaUrl: 'http://url.to.schema', @@ -494,10 +477,7 @@ describe('Trace', () => { const decoded = fromBinary(ExportTraceServiceRequestSchema, serialized); const expected = createExpectedSpanProtobuf(); // toJson converts to protobuf JSON format (strings for 64-bit ints, base64 for bytes) - // alwaysEmitImplicit includes default values like droppedAttributesCount: 0 - const decodedJson = toJson(ExportTraceServiceRequestSchema, decoded, { - alwaysEmitImplicit: true, - }); + const decodedJson = toJson(ExportTraceServiceRequestSchema, decoded); assert.deepStrictEqual(decodedJson, expected); }); diff --git a/experimental/packages/otlp-transformer/test/utils.ts b/experimental/packages/otlp-transformer/test/utils.ts deleted file mode 100644 index f9856f5f507..00000000000 --- a/experimental/packages/otlp-transformer/test/utils.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * - * 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 - * - * https://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. - */ - -import { hexToBinary } from '../src/common/hex-to-binary'; - -/** - * utility function to convert a string representing a hex value to a base64 string - * that represents the bytes of that hex value. This is needed as we need to support Node.js 14 - * where btoa() does not exist, and the Browser, where Buffer does not exist. - * @param hexStr - */ -export function toBase64(hexStr: string) { - if (typeof btoa !== 'undefined') { - const decoder = new TextDecoder('utf8'); - return btoa(decoder.decode(hexToBinary(hexStr))); - } - - return Buffer.from(hexToBinary(hexStr)).toString('base64'); -} From 12e5f624031c5caa342808e60c8299ea04bb6811 Mon Sep 17 00:00:00 2001 From: overbalance Date: Tue, 2 Dec 2025 18:03:09 -0600 Subject: [PATCH 04/15] fix(bundler-tests): add webpack 4 aliases for @bufbuild/protobuf subpath exports --- bundler-tests/browser/webpack-4/webpack.config.mjs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/bundler-tests/browser/webpack-4/webpack.config.mjs b/bundler-tests/browser/webpack-4/webpack.config.mjs index b9e8adf90cd..728b0876d73 100644 --- a/bundler-tests/browser/webpack-4/webpack.config.mjs +++ b/bundler-tests/browser/webpack-4/webpack.config.mjs @@ -14,9 +14,16 @@ export default { }, resolve: { alias: { - // Webpack 4 doesn't support package.json exports field, so we need to manually map the browser-http subpath + // Webpack 4 doesn't support package.json exports field, so we need to manually map subpath exports '@opentelemetry/otlp-exporter-base/browser-http': '@opentelemetry/otlp-exporter-base/build/esm/index-browser-http.js', + // @bufbuild/protobuf subpath exports + '@bufbuild/protobuf/codegenv1': + '@bufbuild/protobuf/dist/esm/codegenv1/index.js', + '@bufbuild/protobuf/wkt': '@bufbuild/protobuf/dist/esm/wkt/index.js', + '@bufbuild/protobuf/wire': '@bufbuild/protobuf/dist/esm/wire/index.js', + '@bufbuild/protobuf/reflect': + '@bufbuild/protobuf/dist/esm/reflect/index.js', }, }, module: { From 90b240e3be2de44c930ab60c2eedfa5ce8bc47c9 Mon Sep 17 00:00:00 2001 From: overbalance Date: Tue, 2 Dec 2025 18:10:35 -0600 Subject: [PATCH 05/15] fix(otlp-transformer): remove duplicate copyright notice in trace/internal.ts --- .../otlp-transformer/src/trace/internal.ts | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/experimental/packages/otlp-transformer/src/trace/internal.ts b/experimental/packages/otlp-transformer/src/trace/internal.ts index 856f134917b..3e4a140e690 100644 --- a/experimental/packages/otlp-transformer/src/trace/internal.ts +++ b/experimental/packages/otlp-transformer/src/trace/internal.ts @@ -107,22 +107,6 @@ export function toOtlpSpanEvent( }; } -/* - * Copyright The OpenTelemetry Authors - * - * 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 - * - * https://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. - */ - export function createExportTraceServiceRequest( spans: ReadableSpan[], options?: OtlpEncodingOptions | Encoder From 315c04fef7ffd8eac40956c96834318fa8d7ddc0 Mon Sep 17 00:00:00 2001 From: overbalance Date: Tue, 2 Dec 2025 17:56:44 -0600 Subject: [PATCH 06/15] docs: add changelog entry for protobuf-es migration --- experimental/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/experimental/CHANGELOG.md b/experimental/CHANGELOG.md index c829edeaef1..de1662d0464 100644 --- a/experimental/CHANGELOG.md +++ b/experimental/CHANGELOG.md @@ -40,6 +40,7 @@ For notes on migrating to 2.x / 0.200.x see [the upgrade guide](doc/upgrade-to-2 * refactor(opentelemetry-sdk-node): simplify calculation of validPropagators [#6143](https://github.com/open-telemetry/opentelemetry-js/pull/6143) @cjihrig * test(instrumentation-http): replace uses of deprecated abort() [#6149](https://github.com/open-telemetry/opentelemetry-js/pull/6149) @cjihrig * refactor(configuration): simplify boolean check [#6158](https://github.com/open-telemetry/opentelemetry-js/pull/6158) @cjihrig +* refactor(otlp-transformer): migrate from protobufjs to protobuf-es [#6179](https://github.com/open-telemetry/opentelemetry-js/pull/6179) @overbalance ## 0.208.0 From 299bb4dc6f4445c5c36df203cbd0d11e4b8a73ed Mon Sep 17 00:00:00 2001 From: overbalance Date: Tue, 2 Dec 2025 18:35:40 -0600 Subject: [PATCH 07/15] refactor(otlp-transformer): use fromJsonString instead of fromJson with JSON round-trip --- .../packages/otlp-transformer/src/logs/protobuf/logs.ts | 8 +++----- .../otlp-transformer/src/metrics/protobuf/metrics.ts | 8 +++----- .../packages/otlp-transformer/src/trace/protobuf/trace.ts | 8 +++----- 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/experimental/packages/otlp-transformer/src/logs/protobuf/logs.ts b/experimental/packages/otlp-transformer/src/logs/protobuf/logs.ts index 2eb26790276..80c67aff4ea 100644 --- a/experimental/packages/otlp-transformer/src/logs/protobuf/logs.ts +++ b/experimental/packages/otlp-transformer/src/logs/protobuf/logs.ts @@ -13,8 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { toBinary, fromBinary, fromJson } from '@bufbuild/protobuf'; -import type { JsonValue } from '@bufbuild/protobuf'; +import { toBinary, fromBinary, fromJsonString } from '@bufbuild/protobuf'; import { IExportLogsServiceResponse } from '../export-response'; import { createExportLogsServiceRequest } from '../internal'; import { ReadableLogRecord } from '@opentelemetry/sdk-logs'; @@ -34,10 +33,9 @@ export const ProtobufLogsSerializer: ISerializer< > = { serializeRequest: (arg: ReadableLogRecord[]) => { const request = createExportLogsServiceRequest(arg, PROTOBUF_JSON_ENCODER); - // JSON.parse(JSON.stringify(...)) removes undefined values which fromJson doesn't accept - const message = fromJson( + const message = fromJsonString( ExportLogsServiceRequestSchema, - JSON.parse(JSON.stringify(request)) as JsonValue + JSON.stringify(request) ); return toBinary(ExportLogsServiceRequestSchema, message); }, diff --git a/experimental/packages/otlp-transformer/src/metrics/protobuf/metrics.ts b/experimental/packages/otlp-transformer/src/metrics/protobuf/metrics.ts index 4494998986d..ee61370362d 100644 --- a/experimental/packages/otlp-transformer/src/metrics/protobuf/metrics.ts +++ b/experimental/packages/otlp-transformer/src/metrics/protobuf/metrics.ts @@ -14,8 +14,7 @@ * limitations under the License. */ -import { toBinary, fromBinary, fromJson } from '@bufbuild/protobuf'; -import type { JsonValue } from '@bufbuild/protobuf'; +import { toBinary, fromBinary, fromJsonString } from '@bufbuild/protobuf'; import { ISerializer } from '../../i-serializer'; import { createExportMetricsServiceRequest } from '../internal'; import { ResourceMetrics } from '@opentelemetry/sdk-metrics'; @@ -35,10 +34,9 @@ export const ProtobufMetricsSerializer: ISerializer< [arg], PROTOBUF_JSON_ENCODER ); - // JSON.parse(JSON.stringify(...)) removes undefined values which fromJson doesn't accept - const message = fromJson( + const message = fromJsonString( ExportMetricsServiceRequestSchema, - JSON.parse(JSON.stringify(request)) as JsonValue + JSON.stringify(request) ); return toBinary(ExportMetricsServiceRequestSchema, message); }, diff --git a/experimental/packages/otlp-transformer/src/trace/protobuf/trace.ts b/experimental/packages/otlp-transformer/src/trace/protobuf/trace.ts index 4e44e5df306..cc7210a3702 100644 --- a/experimental/packages/otlp-transformer/src/trace/protobuf/trace.ts +++ b/experimental/packages/otlp-transformer/src/trace/protobuf/trace.ts @@ -14,8 +14,7 @@ * limitations under the License. */ -import { toBinary, fromBinary, fromJson } from '@bufbuild/protobuf'; -import type { JsonValue } from '@bufbuild/protobuf'; +import { toBinary, fromBinary, fromJsonString } from '@bufbuild/protobuf'; import { ISerializer } from '../../i-serializer'; import { ReadableSpan } from '@opentelemetry/sdk-trace-base'; import { createExportTraceServiceRequest } from '../internal'; @@ -32,10 +31,9 @@ export const ProtobufTraceSerializer: ISerializer< > = { serializeRequest: (arg: ReadableSpan[]) => { const request = createExportTraceServiceRequest(arg, PROTOBUF_JSON_ENCODER); - // JSON.parse(JSON.stringify(...)) removes undefined values which fromJson doesn't accept - const message = fromJson( + const message = fromJsonString( ExportTraceServiceRequestSchema, - JSON.parse(JSON.stringify(request)) as JsonValue + JSON.stringify(request) ); return toBinary(ExportTraceServiceRequestSchema, message); }, From 16930bbf8a40263786f522581acdf71ea2312a56 Mon Sep 17 00:00:00 2001 From: overbalance Date: Tue, 2 Dec 2025 18:37:03 -0600 Subject: [PATCH 08/15] perf(otlp-transformer): optimize hexToBase64 browser fallback --- .../packages/otlp-transformer/src/common/utils.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/experimental/packages/otlp-transformer/src/common/utils.ts b/experimental/packages/otlp-transformer/src/common/utils.ts index cfcc9830f1c..377f21ed4f2 100644 --- a/experimental/packages/otlp-transformer/src/common/utils.ts +++ b/experimental/packages/otlp-transformer/src/common/utils.ts @@ -77,12 +77,8 @@ export function hexToBase64(hex: string): string { if (typeof Buffer !== 'undefined') { return Buffer.from(bytes).toString('base64'); } - // Browser fallback - let binary = ''; - for (let i = 0; i < bytes.length; i++) { - binary += String.fromCharCode(bytes[i]); - } - return btoa(binary); + // Browser fallback using spread to avoid string concatenation in loop + return btoa(String.fromCharCode(...bytes)); } function optionalHexToBase64(str: string | undefined): string | undefined { From f169a4662470e83b139bdb11b8b80be636fcdb64 Mon Sep 17 00:00:00 2001 From: overbalance Date: Tue, 2 Dec 2025 18:37:36 -0600 Subject: [PATCH 09/15] docs(otlp-transformer): mark isEncoder as internal --- experimental/packages/otlp-transformer/src/common/utils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/experimental/packages/otlp-transformer/src/common/utils.ts b/experimental/packages/otlp-transformer/src/common/utils.ts index 377f21ed4f2..92854fbd0e8 100644 --- a/experimental/packages/otlp-transformer/src/common/utils.ts +++ b/experimental/packages/otlp-transformer/src/common/utils.ts @@ -102,6 +102,7 @@ export const PROTOBUF_JSON_ENCODER: Encoder = { encodeOptionalSpanContext: optionalHexToBase64, }; +/** @internal */ export function isEncoder( obj: OtlpEncodingOptions | Encoder | undefined ): obj is Encoder { From f012358a572dca51c0bfde92349d1b14cb328ea8 Mon Sep 17 00:00:00 2001 From: overbalance Date: Tue, 2 Dec 2025 18:42:13 -0600 Subject: [PATCH 10/15] test(otlp-transformer): add empty input serialization tests Add tests for empty input arrays in all three Protobuf serializers: - ProtobufTracesSerializer - ProtobufLogsSerializer - ProtobufMetricsSerializer Also clarify webpack 4 alias comment for protobuf subpath exports. --- .../browser/webpack-4/webpack.config.mjs | 2 +- .../packages/otlp-transformer/test/logs.test.ts | 7 +++++++ .../otlp-transformer/test/metrics.test.ts | 15 +++++++++++++++ .../packages/otlp-transformer/test/trace.test.ts | 7 +++++++ 4 files changed, 30 insertions(+), 1 deletion(-) diff --git a/bundler-tests/browser/webpack-4/webpack.config.mjs b/bundler-tests/browser/webpack-4/webpack.config.mjs index 728b0876d73..ff6970bf540 100644 --- a/bundler-tests/browser/webpack-4/webpack.config.mjs +++ b/bundler-tests/browser/webpack-4/webpack.config.mjs @@ -17,7 +17,7 @@ export default { // Webpack 4 doesn't support package.json exports field, so we need to manually map subpath exports '@opentelemetry/otlp-exporter-base/browser-http': '@opentelemetry/otlp-exporter-base/build/esm/index-browser-http.js', - // @bufbuild/protobuf subpath exports + // @bufbuild/protobuf subpath exports (used by @opentelemetry/otlp-transformer) '@bufbuild/protobuf/codegenv1': '@bufbuild/protobuf/dist/esm/codegenv1/index.js', '@bufbuild/protobuf/wkt': '@bufbuild/protobuf/dist/esm/wkt/index.js', diff --git a/experimental/packages/otlp-transformer/test/logs.test.ts b/experimental/packages/otlp-transformer/test/logs.test.ts index 58f48b84538..db1a6cfbbbb 100644 --- a/experimental/packages/otlp-transformer/test/logs.test.ts +++ b/experimental/packages/otlp-transformer/test/logs.test.ts @@ -341,6 +341,13 @@ describe('Logs', () => { assert.deepStrictEqual(decodedJson, expected); }); + it('serializes an empty request', () => { + const serialized = ProtobufLogsSerializer.serializeRequest([]); + assert.ok(serialized, 'serialized response is undefined'); + const decoded = fromBinary(ExportLogsServiceRequestSchema, serialized); + assert.deepStrictEqual(toJson(ExportLogsServiceRequestSchema, decoded), {}); + }); + it('deserializes a response', () => { const response = create(ExportLogsServiceResponseSchema, { partialSuccess: { diff --git a/experimental/packages/otlp-transformer/test/metrics.test.ts b/experimental/packages/otlp-transformer/test/metrics.test.ts index 9419f155f48..f4b65f595fb 100644 --- a/experimental/packages/otlp-transformer/test/metrics.test.ts +++ b/experimental/packages/otlp-transformer/test/metrics.test.ts @@ -886,6 +886,21 @@ describe('Metrics', () => { assert.deepStrictEqual(decodedJson, expected); }); + it('serializes an empty request', () => { + const serialized = ProtobufMetricsSerializer.serializeRequest( + createResourceMetrics([]) + ); + assert.ok(serialized, 'serialized response is undefined'); + const decoded = fromBinary(ExportMetricsServiceRequestSchema, serialized); + const decodedJson = toJson(ExportMetricsServiceRequestSchema, decoded); + // Empty metrics still has resource and scope, just no metric data + assert.ok(decodedJson, 'decoded response should exist'); + assert.ok( + typeof decodedJson === 'object' && decodedJson !== null, + 'decoded should be an object' + ); + }); + it('deserializes a response', () => { const response = create(ExportMetricsServiceResponseSchema, { partialSuccess: { diff --git a/experimental/packages/otlp-transformer/test/trace.test.ts b/experimental/packages/otlp-transformer/test/trace.test.ts index 2aae3150a32..e4c07b8e9ad 100644 --- a/experimental/packages/otlp-transformer/test/trace.test.ts +++ b/experimental/packages/otlp-transformer/test/trace.test.ts @@ -481,6 +481,13 @@ describe('Trace', () => { assert.deepStrictEqual(decodedJson, expected); }); + it('serializes an empty request', () => { + const serialized = ProtobufTraceSerializer.serializeRequest([]); + assert.ok(serialized, 'serialized response is undefined'); + const decoded = fromBinary(ExportTraceServiceRequestSchema, serialized); + assert.deepStrictEqual(toJson(ExportTraceServiceRequestSchema, decoded), {}); + }); + it('deserializes a response', () => { const response = create(ExportTraceServiceResponseSchema, { partialSuccess: { From 63f09f5baef27322cf6922b92fb1792021e93416 Mon Sep 17 00:00:00 2001 From: overbalance Date: Tue, 2 Dec 2025 18:44:10 -0600 Subject: [PATCH 11/15] lint --- experimental/packages/otlp-transformer/test/logs.test.ts | 5 ++++- experimental/packages/otlp-transformer/test/trace.test.ts | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/experimental/packages/otlp-transformer/test/logs.test.ts b/experimental/packages/otlp-transformer/test/logs.test.ts index db1a6cfbbbb..b3bdf6148f1 100644 --- a/experimental/packages/otlp-transformer/test/logs.test.ts +++ b/experimental/packages/otlp-transformer/test/logs.test.ts @@ -345,7 +345,10 @@ describe('Logs', () => { const serialized = ProtobufLogsSerializer.serializeRequest([]); assert.ok(serialized, 'serialized response is undefined'); const decoded = fromBinary(ExportLogsServiceRequestSchema, serialized); - assert.deepStrictEqual(toJson(ExportLogsServiceRequestSchema, decoded), {}); + assert.deepStrictEqual( + toJson(ExportLogsServiceRequestSchema, decoded), + {} + ); }); it('deserializes a response', () => { diff --git a/experimental/packages/otlp-transformer/test/trace.test.ts b/experimental/packages/otlp-transformer/test/trace.test.ts index e4c07b8e9ad..5b272a5a3f8 100644 --- a/experimental/packages/otlp-transformer/test/trace.test.ts +++ b/experimental/packages/otlp-transformer/test/trace.test.ts @@ -485,7 +485,10 @@ describe('Trace', () => { const serialized = ProtobufTraceSerializer.serializeRequest([]); assert.ok(serialized, 'serialized response is undefined'); const decoded = fromBinary(ExportTraceServiceRequestSchema, serialized); - assert.deepStrictEqual(toJson(ExportTraceServiceRequestSchema, decoded), {}); + assert.deepStrictEqual( + toJson(ExportTraceServiceRequestSchema, decoded), + {} + ); }); it('deserializes a response', () => { From c8a0d603b65ba9c37560fdf4c25a4151039503de Mon Sep 17 00:00:00 2001 From: overbalance Date: Tue, 2 Dec 2025 18:50:58 -0600 Subject: [PATCH 12/15] Update metrics.test.ts --- experimental/packages/otlp-transformer/test/metrics.test.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/experimental/packages/otlp-transformer/test/metrics.test.ts b/experimental/packages/otlp-transformer/test/metrics.test.ts index f4b65f595fb..e6b1046342f 100644 --- a/experimental/packages/otlp-transformer/test/metrics.test.ts +++ b/experimental/packages/otlp-transformer/test/metrics.test.ts @@ -320,8 +320,7 @@ describe('Metrics', () => { scopeMetrics: [ { scope: { - name: 'mylib', - version: '0.1.0', + ...expectedScope, schemaUrl: expectedSchemaUrl, }, metrics: metricData, @@ -855,7 +854,7 @@ describe('Metrics', () => { }, scopeMetrics: [ { - scope: { name: 'mylib', version: '0.1.0' }, + scope: expectedScope, schemaUrl: expectedSchemaUrl, metrics: [ { From dbe3fe679ba6fb520d7639857c2175f9c31d68ba Mon Sep 17 00:00:00 2001 From: overbalance Date: Tue, 2 Dec 2025 19:04:07 -0600 Subject: [PATCH 13/15] refactor(otlp-transformer): rename isEncoder to isOtlpEncoder --- experimental/packages/otlp-transformer/src/common/utils.ts | 2 +- experimental/packages/otlp-transformer/src/logs/internal.ts | 4 ++-- .../packages/otlp-transformer/src/metrics/internal.ts | 4 ++-- experimental/packages/otlp-transformer/src/trace/internal.ts | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/experimental/packages/otlp-transformer/src/common/utils.ts b/experimental/packages/otlp-transformer/src/common/utils.ts index 92854fbd0e8..de9edc334dc 100644 --- a/experimental/packages/otlp-transformer/src/common/utils.ts +++ b/experimental/packages/otlp-transformer/src/common/utils.ts @@ -103,7 +103,7 @@ export const PROTOBUF_JSON_ENCODER: Encoder = { }; /** @internal */ -export function isEncoder( +export function isOtlpEncoder( obj: OtlpEncodingOptions | Encoder | undefined ): obj is Encoder { return obj !== undefined && 'encodeHrTime' in obj; diff --git a/experimental/packages/otlp-transformer/src/logs/internal.ts b/experimental/packages/otlp-transformer/src/logs/internal.ts index 27d3afb4269..68809ceadfc 100644 --- a/experimental/packages/otlp-transformer/src/logs/internal.ts +++ b/experimental/packages/otlp-transformer/src/logs/internal.ts @@ -22,7 +22,7 @@ import { IResourceLogs, } from './internal-types'; import { Resource } from '@opentelemetry/resources'; -import { Encoder, getOtlpEncoder, isEncoder } from '../common/utils'; +import { Encoder, getOtlpEncoder, isOtlpEncoder } from '../common/utils'; import { createInstrumentationScope, createResource, @@ -37,7 +37,7 @@ export function createExportLogsServiceRequest( logRecords: ReadableLogRecord[], options?: OtlpEncodingOptions | Encoder ): IExportLogsServiceRequest { - const encoder = isEncoder(options) ? options : getOtlpEncoder(options); + const encoder = isOtlpEncoder(options) ? options : getOtlpEncoder(options); return { resourceLogs: logRecordsToResourceLogs(logRecords, encoder), }; diff --git a/experimental/packages/otlp-transformer/src/metrics/internal.ts b/experimental/packages/otlp-transformer/src/metrics/internal.ts index ee842f859c5..ee06c3de298 100644 --- a/experimental/packages/otlp-transformer/src/metrics/internal.ts +++ b/experimental/packages/otlp-transformer/src/metrics/internal.ts @@ -35,7 +35,7 @@ import { IResourceMetrics, IScopeMetrics, } from './internal-types'; -import { Encoder, getOtlpEncoder, isEncoder } from '../common/utils'; +import { Encoder, getOtlpEncoder, isOtlpEncoder } from '../common/utils'; import { createInstrumentationScope, createResource, @@ -46,7 +46,7 @@ export function toResourceMetrics( resourceMetrics: ResourceMetrics, options?: OtlpEncodingOptions | Encoder ): IResourceMetrics { - const encoder = isEncoder(options) ? options : getOtlpEncoder(options); + const encoder = isOtlpEncoder(options) ? options : getOtlpEncoder(options); const processedResource = createResource(resourceMetrics.resource); return { resource: processedResource, diff --git a/experimental/packages/otlp-transformer/src/trace/internal.ts b/experimental/packages/otlp-transformer/src/trace/internal.ts index 3e4a140e690..42d1fc853fb 100644 --- a/experimental/packages/otlp-transformer/src/trace/internal.ts +++ b/experimental/packages/otlp-transformer/src/trace/internal.ts @@ -32,7 +32,7 @@ import { ISpan, } from './internal-types'; import { OtlpEncodingOptions } from '../common/internal-types'; -import { getOtlpEncoder, isEncoder } from '../common/utils'; +import { getOtlpEncoder, isOtlpEncoder } from '../common/utils'; // Span flags constants matching the OTLP specification const SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK = 0x100; @@ -111,7 +111,7 @@ export function createExportTraceServiceRequest( spans: ReadableSpan[], options?: OtlpEncodingOptions | Encoder ): IExportTraceServiceRequest { - const encoder = isEncoder(options) ? options : getOtlpEncoder(options); + const encoder = isOtlpEncoder(options) ? options : getOtlpEncoder(options); return { resourceSpans: spanRecordsToResourceSpans(spans, encoder), }; From 79d8132dd3141bebd9c5cb8a07f0d7f3d54dd213 Mon Sep 17 00:00:00 2001 From: overbalance Date: Wed, 3 Dec 2025 10:25:39 -0600 Subject: [PATCH 14/15] docs(bundler-tests): add protobuf version compatibility comment --- bundler-tests/browser/webpack-4/webpack.config.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/bundler-tests/browser/webpack-4/webpack.config.mjs b/bundler-tests/browser/webpack-4/webpack.config.mjs index ff6970bf540..05f3a82b490 100644 --- a/bundler-tests/browser/webpack-4/webpack.config.mjs +++ b/bundler-tests/browser/webpack-4/webpack.config.mjs @@ -18,6 +18,7 @@ export default { '@opentelemetry/otlp-exporter-base/browser-http': '@opentelemetry/otlp-exporter-base/build/esm/index-browser-http.js', // @bufbuild/protobuf subpath exports (used by @opentelemetry/otlp-transformer) + // These paths are compatible with @bufbuild/protobuf v2.x '@bufbuild/protobuf/codegenv1': '@bufbuild/protobuf/dist/esm/codegenv1/index.js', '@bufbuild/protobuf/wkt': '@bufbuild/protobuf/dist/esm/wkt/index.js', From 4d5201680fec6c38b63d34346c74c913e8e2d1e8 Mon Sep 17 00:00:00 2001 From: overbalance Date: Wed, 3 Dec 2025 10:30:27 -0600 Subject: [PATCH 15/15] refactor(otlp-transformer): remove legacy BigInt availability check --- experimental/packages/otlp-transformer/src/common/utils.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/experimental/packages/otlp-transformer/src/common/utils.ts b/experimental/packages/otlp-transformer/src/common/utils.ts index de9edc334dc..3fc4f58c932 100644 --- a/experimental/packages/otlp-transformer/src/common/utils.ts +++ b/experimental/packages/otlp-transformer/src/common/utils.ts @@ -16,7 +16,6 @@ import type { OtlpEncodingOptions, Fixed64, LongBits } from './internal-types'; import { HrTime } from '@opentelemetry/api'; -import { hrTimeToNanoseconds } from '@opentelemetry/core'; import { hexToBinary } from './hex-to-binary'; export function hrTimeToNanos(hrTime: HrTime): bigint { @@ -42,9 +41,6 @@ export function encodeAsString(hrTime: HrTime): string { return nanos.toString(); } -const encodeTimestamp = - typeof BigInt !== 'undefined' ? encodeAsString : hrTimeToNanoseconds; - export type HrTimeEncodeFunction = (hrTime: HrTime) => Fixed64; export type SpanContextEncodeFunction = ( spanContext: string @@ -117,7 +113,7 @@ export function getOtlpEncoder(options?: OtlpEncodingOptions): Encoder { const useLongBits = options.useLongBits ?? true; const useHex = options.useHex ?? false; return { - encodeHrTime: useLongBits ? encodeAsLongBits : encodeTimestamp, + encodeHrTime: useLongBits ? encodeAsLongBits : encodeAsString, encodeSpanContext: useHex ? identity : hexToBinary, encodeOptionalSpanContext: useHex ? identity : optionalHexToBinary, };