From 9db6352d305d207b96f55d41095a41a34711405d Mon Sep 17 00:00:00 2001 From: Siim Kallas Date: Mon, 6 Nov 2023 17:09:52 +0200 Subject: [PATCH] fix: otlp json encoding (#4220) Co-authored-by: Marc Pichler --- experimental/CHANGELOG.md | 3 + .../src/platform/browser/OTLPLogExporter.ts | 5 +- .../src/platform/node/OTLPLogExporter.ts | 5 +- .../exporter-logs-otlp-http/test/logHelper.ts | 10 +- .../src/platform/browser/OTLPTraceExporter.ts | 5 +- .../src/platform/node/OTLPTraceExporter.ts | 5 +- .../test/traceHelper.ts | 26 ++-- .../test/metricsHelper.ts | 33 +---- .../platform/browser/OTLPMetricExporter.ts | 2 +- .../src/platform/node/OTLPMetricExporter.ts | 2 +- .../test/metricsHelper.ts | 19 +-- .../test/metricsHelper.ts | 33 +---- .../otlp-transformer/src/common/index.ts | 72 +++++++++-- .../otlp-transformer/src/common/types.ts | 11 +- .../src/common/unsigned_long.ts | 122 ------------------ .../otlp-transformer/src/logs/index.ts | 33 ++--- .../otlp-transformer/src/logs/types.ts | 6 +- .../otlp-transformer/src/metrics/index.ts | 8 +- .../otlp-transformer/src/metrics/internal.ts | 60 ++++++--- .../otlp-transformer/src/metrics/types.ts | 14 +- .../otlp-transformer/src/trace/index.ts | 11 +- .../otlp-transformer/src/trace/internal.ts | 41 +++--- .../otlp-transformer/src/trace/types.ts | 8 +- .../otlp-transformer/test/common.test.ts | 65 ++++++++++ .../otlp-transformer/test/logs.test.ts | 28 ++-- .../otlp-transformer/test/metrics.test.ts | 38 +++--- .../otlp-transformer/test/trace.test.ts | 93 ++++++++++--- 27 files changed, 396 insertions(+), 362 deletions(-) delete mode 100644 experimental/packages/otlp-transformer/src/common/unsigned_long.ts diff --git a/experimental/CHANGELOG.md b/experimental/CHANGELOG.md index 687c0a90e2..172da0a498 100644 --- a/experimental/CHANGELOG.md +++ b/experimental/CHANGELOG.md @@ -13,6 +13,9 @@ All notable changes to experimental packages in this project will be documented * fix(sdk-node): remove the explicit dependency on @opentelemetry/exporter-jaeger that was kept on the previous release * '@opentelemetry/exporter-jaeger' is no longer be a dependency of this package. To continue using '@opentelemetry/exporter-jaeger', please install it manually. * NOTE: `@opentelemetry/exporter-jaeger` is deprecated, consider switching to one of the alternatives described [here](https://www.npmjs.com/package/@opentelemetry/exporter-jaeger) +* fix(otlp-transformer): OTLP json encoding [#4220](https://github.com/open-telemetry/opentelemetry-js/pull/4220) @seemk + * Fixes a bug in the OTLP (http/json) exporters where timestamps were not encoded as strings, causing the export to + be rejected by OTLP endpoints ### :books: (Refine Doc) diff --git a/experimental/packages/exporter-logs-otlp-http/src/platform/browser/OTLPLogExporter.ts b/experimental/packages/exporter-logs-otlp-http/src/platform/browser/OTLPLogExporter.ts index a7ecbbac95..d65ff9aeb8 100644 --- a/experimental/packages/exporter-logs-otlp-http/src/platform/browser/OTLPLogExporter.ts +++ b/experimental/packages/exporter-logs-otlp-http/src/platform/browser/OTLPLogExporter.ts @@ -48,7 +48,10 @@ export class OTLPLogExporter } convert(logRecords: ReadableLogRecord[]): IExportLogsServiceRequest { - return createExportLogsServiceRequest(logRecords, true); + return createExportLogsServiceRequest(logRecords, { + useHex: true, + useLongBits: false, + }); } getDefaultUrl(config: OTLPExporterConfigBase): string { diff --git a/experimental/packages/exporter-logs-otlp-http/src/platform/node/OTLPLogExporter.ts b/experimental/packages/exporter-logs-otlp-http/src/platform/node/OTLPLogExporter.ts index a1d101e87c..25a1b194ab 100644 --- a/experimental/packages/exporter-logs-otlp-http/src/platform/node/OTLPLogExporter.ts +++ b/experimental/packages/exporter-logs-otlp-http/src/platform/node/OTLPLogExporter.ts @@ -48,7 +48,10 @@ export class OTLPLogExporter } convert(logRecords: ReadableLogRecord[]): IExportLogsServiceRequest { - return createExportLogsServiceRequest(logRecords, true); + return createExportLogsServiceRequest(logRecords, { + useHex: true, + useLongBits: false, + }); } getDefaultUrl(config: OTLPExporterNodeConfigBase): string { diff --git a/experimental/packages/exporter-logs-otlp-http/test/logHelper.ts b/experimental/packages/exporter-logs-otlp-http/test/logHelper.ts index b5832f7cd4..bc4d17a4f0 100644 --- a/experimental/packages/exporter-logs-otlp-http/test/logHelper.ts +++ b/experimental/packages/exporter-logs-otlp-http/test/logHelper.ts @@ -20,7 +20,6 @@ import { Resource } from '@opentelemetry/resources'; import * as assert from 'assert'; import { VERSION } from '@opentelemetry/core'; import { - hrTimeToFixed64Nanos, IAnyValue, IExportLogsServiceRequest, IKeyValue, @@ -77,22 +76,17 @@ export function ensureExportedBodyIsCorrect(body?: IAnyValue) { ); } -function hrTimeToFixed64(hrTime: HrTime) { - const { low, high } = hrTimeToFixed64Nanos(hrTime); - return { low, high }; -} - export function ensureExportedLogRecordIsCorrect(logRecord: ILogRecord) { ensureExportedBodyIsCorrect(logRecord.body); ensureExportedAttributesAreCorrect(logRecord.attributes); assert.deepStrictEqual( logRecord.timeUnixNano, - hrTimeToFixed64(mockedReadableLogRecord.hrTime), + '1680253513123241635', 'timeUnixNano is wrong' ); assert.deepStrictEqual( logRecord.observedTimeUnixNano, - hrTimeToFixed64(mockedReadableLogRecord.hrTimeObserved), + '1680253513123241635', 'observedTimeUnixNano is wrong' ); assert.strictEqual( diff --git a/experimental/packages/exporter-trace-otlp-http/src/platform/browser/OTLPTraceExporter.ts b/experimental/packages/exporter-trace-otlp-http/src/platform/browser/OTLPTraceExporter.ts index 8a343306c4..fa164f65cf 100644 --- a/experimental/packages/exporter-trace-otlp-http/src/platform/browser/OTLPTraceExporter.ts +++ b/experimental/packages/exporter-trace-otlp-http/src/platform/browser/OTLPTraceExporter.ts @@ -47,7 +47,10 @@ export class OTLPTraceExporter ); } convert(spans: ReadableSpan[]): IExportTraceServiceRequest { - return createExportTraceServiceRequest(spans, true); + return createExportTraceServiceRequest(spans, { + useHex: true, + useLongBits: false, + }); } getDefaultUrl(config: OTLPExporterConfigBase): string { diff --git a/experimental/packages/exporter-trace-otlp-http/src/platform/node/OTLPTraceExporter.ts b/experimental/packages/exporter-trace-otlp-http/src/platform/node/OTLPTraceExporter.ts index f10fbd0ec0..e4d3273239 100644 --- a/experimental/packages/exporter-trace-otlp-http/src/platform/node/OTLPTraceExporter.ts +++ b/experimental/packages/exporter-trace-otlp-http/src/platform/node/OTLPTraceExporter.ts @@ -53,7 +53,10 @@ export class OTLPTraceExporter } convert(spans: ReadableSpan[]): IExportTraceServiceRequest { - return createExportTraceServiceRequest(spans, true); + return createExportTraceServiceRequest(spans, { + useHex: true, + useLongBits: false, + }); } getDefaultUrl(config: OTLPExporterNodeConfigBase): string { diff --git a/experimental/packages/exporter-trace-otlp-http/test/traceHelper.ts b/experimental/packages/exporter-trace-otlp-http/test/traceHelper.ts index 6726b49017..e89062c924 100644 --- a/experimental/packages/exporter-trace-otlp-http/test/traceHelper.ts +++ b/experimental/packages/exporter-trace-otlp-http/test/traceHelper.ts @@ -31,7 +31,6 @@ import { ILink, IResource, ISpan, - UnsignedLong, } from '@opentelemetry/otlp-transformer'; if (typeof Buffer === 'undefined') { @@ -244,59 +243,54 @@ export const multiInstrumentationLibraryTrace: ReadableSpan[] = [ }, ]; -function fixed64FromString(str: string) { - const { low, high } = UnsignedLong.fromString(str); - return { low, high }; -} - export function ensureEventsAreCorrect(events: IEvent[]) { assert.deepStrictEqual( events, [ { - timeUnixNano: fixed64FromString('1574120165429803070'), + timeUnixNano: '1574120165429803070', name: 'fetchStart', attributes: [], droppedAttributesCount: 0, }, { - timeUnixNano: fixed64FromString('1574120165429803070'), + timeUnixNano: '1574120165429803070', name: 'domainLookupStart', attributes: [], droppedAttributesCount: 0, }, { - timeUnixNano: fixed64FromString('1574120165429803070'), + timeUnixNano: '1574120165429803070', name: 'domainLookupEnd', attributes: [], droppedAttributesCount: 0, }, { - timeUnixNano: fixed64FromString('1574120165429803070'), + timeUnixNano: '1574120165429803070', name: 'connectStart', attributes: [], droppedAttributesCount: 0, }, { - timeUnixNano: fixed64FromString('1574120165429803070'), + timeUnixNano: '1574120165429803070', name: 'connectEnd', attributes: [], droppedAttributesCount: 0, }, { - timeUnixNano: fixed64FromString('1574120165435513070'), + timeUnixNano: '1574120165435513070', name: 'requestStart', attributes: [], droppedAttributesCount: 0, }, { - timeUnixNano: fixed64FromString('1574120165436923070'), + timeUnixNano: '1574120165436923070', name: 'responseStart', attributes: [], droppedAttributesCount: 0, }, { - timeUnixNano: fixed64FromString('1574120165438688070'), + timeUnixNano: '1574120165438688070', name: 'responseEnd', attributes: [], droppedAttributesCount: 0, @@ -372,12 +366,12 @@ export function ensureSpanIsCorrect(span: ISpan, useHex = true) { assert.strictEqual(span.kind, ESpanKind.SPAN_KIND_INTERNAL, 'kind is wrong'); assert.deepStrictEqual( span.startTimeUnixNano, - fixed64FromString('1574120165429803070'), + '1574120165429803070', 'startTimeUnixNano is wrong' ); assert.deepStrictEqual( span.endTimeUnixNano, - fixed64FromString('1574120165438688070'), + '1574120165438688070', 'endTimeUnixNano is wrong' ); assert.strictEqual( diff --git a/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/test/metricsHelper.ts b/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/test/metricsHelper.ts index e6332ff36f..14c8f3a3df 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/test/metricsHelper.ts +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/test/metricsHelper.ts @@ -33,11 +33,10 @@ import { View, } from '@opentelemetry/sdk-metrics'; import { - hrTimeToFixed64Nanos, + encodeAsString, IKeyValue, IMetric, IResource, - UnsignedLong, } from '@opentelemetry/otlp-transformer'; class TestMetricReader extends MetricReader { @@ -152,14 +151,8 @@ export function ensureExportedCounterIsCorrect( assert.strictEqual(dp.asInt, '1'); assert.strictEqual(dp.flags, 0); - assert.deepStrictEqual( - UnsignedLong.fromString(dp.startTimeUnixNano as string), - hrTimeToFixed64Nanos(startTime) - ); - assert.deepStrictEqual( - UnsignedLong.fromString(dp.timeUnixNano as string), - hrTimeToFixed64Nanos(time) - ); + assert.deepStrictEqual(dp.startTimeUnixNano, encodeAsString(startTime)); + assert.deepStrictEqual(dp.timeUnixNano as string, encodeAsString(time)); } export function ensureExportedObservableGaugeIsCorrect( @@ -179,14 +172,8 @@ export function ensureExportedObservableGaugeIsCorrect( assert.strictEqual(dp.asDouble, 6); assert.strictEqual(dp.flags, 0); - assert.deepStrictEqual( - UnsignedLong.fromString(dp.startTimeUnixNano as string), - hrTimeToFixed64Nanos(startTime) - ); - assert.deepStrictEqual( - UnsignedLong.fromString(dp.timeUnixNano as string), - hrTimeToFixed64Nanos(time) - ); + assert.deepStrictEqual(dp.startTimeUnixNano, encodeAsString(startTime)); + assert.deepStrictEqual(dp.timeUnixNano, encodeAsString(time)); } export function ensureExportedHistogramIsCorrect( @@ -215,14 +202,8 @@ export function ensureExportedHistogramIsCorrect( assert.strictEqual(dp.min, 7); assert.strictEqual(dp.max, 14); - assert.deepStrictEqual( - UnsignedLong.fromString(dp.startTimeUnixNano as string), - hrTimeToFixed64Nanos(startTime) - ); - assert.deepStrictEqual( - UnsignedLong.fromString(dp.timeUnixNano as string), - hrTimeToFixed64Nanos(time) - ); + assert.deepStrictEqual(dp.startTimeUnixNano, encodeAsString(startTime)); + assert.deepStrictEqual(dp.timeUnixNano, encodeAsString(time)); assert.deepStrictEqual(dp.bucketCounts, bucketCounts); assert.deepStrictEqual(dp.explicitBounds, explicitBounds); } diff --git a/experimental/packages/opentelemetry-exporter-metrics-otlp-http/src/platform/browser/OTLPMetricExporter.ts b/experimental/packages/opentelemetry-exporter-metrics-otlp-http/src/platform/browser/OTLPMetricExporter.ts index 73c99a0693..cff7a6ec00 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-http/src/platform/browser/OTLPMetricExporter.ts +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-http/src/platform/browser/OTLPMetricExporter.ts @@ -62,7 +62,7 @@ class OTLPExporterBrowserProxy extends OTLPExporterBrowserBase< } convert(metrics: ResourceMetrics[]): IExportMetricsServiceRequest { - return createExportMetricsServiceRequest(metrics); + return createExportMetricsServiceRequest(metrics, { useLongBits: false }); } } diff --git a/experimental/packages/opentelemetry-exporter-metrics-otlp-http/src/platform/node/OTLPMetricExporter.ts b/experimental/packages/opentelemetry-exporter-metrics-otlp-http/src/platform/node/OTLPMetricExporter.ts index f83e414e70..d051b7be9b 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-http/src/platform/node/OTLPMetricExporter.ts +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-http/src/platform/node/OTLPMetricExporter.ts @@ -52,7 +52,7 @@ class OTLPExporterNodeProxy extends OTLPExporterNodeBase< } convert(metrics: ResourceMetrics[]): IExportMetricsServiceRequest { - return createExportMetricsServiceRequest(metrics); + return createExportMetricsServiceRequest(metrics, { useLongBits: false }); } getDefaultUrl(config: OTLPExporterNodeConfigBase): string { diff --git a/experimental/packages/opentelemetry-exporter-metrics-otlp-http/test/metricsHelper.ts b/experimental/packages/opentelemetry-exporter-metrics-otlp-http/test/metricsHelper.ts index 9bbeee652f..5605a69b28 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-http/test/metricsHelper.ts +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-http/test/metricsHelper.ts @@ -34,7 +34,7 @@ import { View, } from '@opentelemetry/sdk-metrics'; import { - hrTimeToFixed64Nanos, + encodeAsString, IExportMetricsServiceRequest, IKeyValue, IMetric, @@ -207,11 +207,6 @@ export function ensureWebResourceIsCorrect(resource: IResource) { assert.strictEqual(resource.droppedAttributesCount, 0); } -function hrTimeToFixed64(hrTime: HrTime) { - const { low, high } = hrTimeToFixed64Nanos(hrTime); - return { low, high }; -} - export function ensureCounterIsCorrect( metric: IMetric, time: HrTime, @@ -228,8 +223,8 @@ export function ensureCounterIsCorrect( assert.deepStrictEqual(dp.attributes, []); assert.strictEqual(dp.asInt, 1); - assert.deepStrictEqual(dp.startTimeUnixNano, hrTimeToFixed64(startTime)); - assert.deepStrictEqual(dp.timeUnixNano, hrTimeToFixed64(time)); + assert.deepStrictEqual(dp.startTimeUnixNano, encodeAsString(startTime)); + assert.deepStrictEqual(dp.timeUnixNano, encodeAsString(time)); } export function ensureDoubleCounterIsCorrect( @@ -273,8 +268,8 @@ export function ensureObservableGaugeIsCorrect( assert.deepStrictEqual(dp.attributes, []); assert.strictEqual(dp.asDouble, value); - assert.deepStrictEqual(dp.startTimeUnixNano, hrTimeToFixed64(startTime)); - assert.deepStrictEqual(dp.timeUnixNano, hrTimeToFixed64(time)); + assert.deepStrictEqual(dp.startTimeUnixNano, encodeAsString(startTime)); + assert.deepStrictEqual(dp.timeUnixNano, encodeAsString(time)); } export function ensureHistogramIsCorrect( @@ -300,8 +295,8 @@ export function ensureHistogramIsCorrect( assert.deepStrictEqual(dp.bucketCounts, bucketCounts); assert.deepStrictEqual(dp.explicitBounds, explicitBounds); - assert.deepStrictEqual(dp.startTimeUnixNano, hrTimeToFixed64(startTime)); - assert.deepStrictEqual(dp.timeUnixNano, hrTimeToFixed64(time)); + assert.deepStrictEqual(dp.startTimeUnixNano, encodeAsString(startTime)); + assert.deepStrictEqual(dp.timeUnixNano, encodeAsString(time)); } export function ensureExportMetricsServiceRequestIsSet( diff --git a/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/test/metricsHelper.ts b/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/test/metricsHelper.ts index 6a47a9a8f6..5c9c29abee 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/test/metricsHelper.ts +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/test/metricsHelper.ts @@ -31,11 +31,10 @@ import { View, } from '@opentelemetry/sdk-metrics'; import { - hrTimeToFixed64Nanos, + encodeAsString, IExportMetricsServiceRequest, IKeyValue, IMetric, - UnsignedLong, } from '@opentelemetry/otlp-transformer'; import { Stream } from 'stream'; @@ -147,14 +146,8 @@ export function ensureExportedCounterIsCorrect( const [dp] = metric.sum.dataPoints; assert.strictEqual(dp.asInt, '1'); - assert.deepStrictEqual( - UnsignedLong.fromString(dp.startTimeUnixNano as string), - hrTimeToFixed64Nanos(startTime) - ); - assert.deepStrictEqual( - UnsignedLong.fromString(dp.timeUnixNano as string), - hrTimeToFixed64Nanos(time) - ); + assert.deepStrictEqual(dp.startTimeUnixNano, encodeAsString(startTime)); + assert.deepStrictEqual(dp.timeUnixNano, encodeAsString(time)); } export function ensureExportedObservableGaugeIsCorrect( @@ -169,14 +162,8 @@ export function ensureExportedObservableGaugeIsCorrect( const [dp] = metric.gauge.dataPoints; assert.strictEqual(dp.asDouble, 6); - assert.deepStrictEqual( - UnsignedLong.fromString(dp.startTimeUnixNano as string), - hrTimeToFixed64Nanos(startTime) - ); - assert.deepStrictEqual( - UnsignedLong.fromString(dp.timeUnixNano as string), - hrTimeToFixed64Nanos(time) - ); + assert.deepStrictEqual(dp.startTimeUnixNano, encodeAsString(startTime)); + assert.deepStrictEqual(dp.timeUnixNano, encodeAsString(time)); } export function ensureExportedHistogramIsCorrect( @@ -204,14 +191,8 @@ export function ensureExportedHistogramIsCorrect( assert.strictEqual(dp.max, 14); assert.deepStrictEqual(dp.explicitBounds, explicitBounds); assert.deepStrictEqual(dp.bucketCounts, bucketCounts); - assert.deepStrictEqual( - UnsignedLong.fromString(dp.startTimeUnixNano as string), - hrTimeToFixed64Nanos(startTime) - ); - assert.deepStrictEqual( - UnsignedLong.fromString(dp.timeUnixNano as string), - hrTimeToFixed64Nanos(time) - ); + assert.deepStrictEqual(dp.startTimeUnixNano, encodeAsString(startTime)); + assert.deepStrictEqual(dp.timeUnixNano, encodeAsString(time)); } export function ensureExportMetricsServiceRequestIsSet( diff --git a/experimental/packages/otlp-transformer/src/common/index.ts b/experimental/packages/otlp-transformer/src/common/index.ts index 19587be3ae..c759fafc4b 100644 --- a/experimental/packages/otlp-transformer/src/common/index.ts +++ b/experimental/packages/otlp-transformer/src/common/index.ts @@ -14,16 +14,72 @@ * limitations under the License. */ -import type { IFixed64 } from './types'; +import type { OtlpEncodingOptions, Fixed64, LongBits } from './types'; import { HrTime } from '@opentelemetry/api'; -import { UnsignedLong } from './unsigned_long'; +import { hexToBase64, hrTimeToNanoseconds } from '@opentelemetry/core'; -export * from './unsigned_long'; +const NANOSECONDS = BigInt(1_000_000_000); -const NANOSECONDS = UnsignedLong.fromU32(1_000_000_000); +export function hrTimeToNanos(hrTime: HrTime): bigint { + return BigInt(hrTime[0]) * NANOSECONDS + BigInt(hrTime[1]); +} + +export function toLongBits(value: bigint): LongBits { + const low = Number(BigInt.asUintN(32, value)); + const high = Number(BigInt.asUintN(32, value >> BigInt(32))); + return { low, high }; +} + +export function encodeAsLongBits(hrTime: HrTime): LongBits { + const nanos = hrTimeToNanos(hrTime); + return toLongBits(nanos); +} + +export function encodeAsString(hrTime: HrTime): string { + const nanos = hrTimeToNanos(hrTime); + return nanos.toString(); +} + +const encodeTimestamp = + typeof BigInt !== 'undefined' ? encodeAsString : hrTimeToNanoseconds; + +export type HrTimeEncodeFunction = (hrTime: HrTime) => Fixed64; +export type SpanContextEncodeFunction = (spanContext: string) => string; +export type OptionalSpanContextEncodeFunction = ( + spanContext: string | undefined +) => string | undefined; + +export interface Encoder { + encodeHrTime: HrTimeEncodeFunction; + encodeSpanContext: SpanContextEncodeFunction; + encodeOptionalSpanContext: OptionalSpanContextEncodeFunction; +} + +function identity(value: T): T { + return value; +} + +function optionalHexToBase64(str: string | undefined): string | undefined { + if (str === undefined) return undefined; + return hexToBase64(str); +} + +const DEFAULT_ENCODER: Encoder = { + encodeHrTime: encodeAsLongBits, + encodeSpanContext: hexToBase64, + encodeOptionalSpanContext: optionalHexToBase64, +}; + +export function getOtlpEncoder(options?: OtlpEncodingOptions): Encoder { + if (options === undefined) { + return DEFAULT_ENCODER; + } -export function hrTimeToFixed64Nanos(hrTime: HrTime): IFixed64 { - return UnsignedLong.fromU32(hrTime[0]) - .multiply(NANOSECONDS) - .add(UnsignedLong.fromU32(hrTime[1])); + const useLongBits = options.useLongBits ?? true; + const useHex = options.useHex ?? false; + return { + encodeHrTime: useLongBits ? encodeAsLongBits : encodeTimestamp, + encodeSpanContext: useHex ? identity : hexToBase64, + encodeOptionalSpanContext: useHex ? identity : optionalHexToBase64, + }; } diff --git a/experimental/packages/otlp-transformer/src/common/types.ts b/experimental/packages/otlp-transformer/src/common/types.ts index 6236da4ce6..732944f2e0 100644 --- a/experimental/packages/otlp-transformer/src/common/types.ts +++ b/experimental/packages/otlp-transformer/src/common/types.ts @@ -74,7 +74,16 @@ export interface IKeyValueList { values: IKeyValue[]; } -export interface IFixed64 { +export interface LongBits { low: number; high: number; } + +export type Fixed64 = LongBits | string | number; + +export interface OtlpEncodingOptions { + /** Convert trace and span IDs to hex strings. */ + useHex?: boolean; + /** Convert HrTime to 2 part 64 bit values. */ + useLongBits?: boolean; +} diff --git a/experimental/packages/otlp-transformer/src/common/unsigned_long.ts b/experimental/packages/otlp-transformer/src/common/unsigned_long.ts deleted file mode 100644 index d2802e3ba8..0000000000 --- a/experimental/packages/otlp-transformer/src/common/unsigned_long.ts +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright 2009 The Closure Library Authors - * Copyright 2020 Daniel Wirtz / The long.js Authors. - * 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. - */ - -// Original version by long.js: https://github.com/dcodeIO/long.js/ - -const TWO_PWR_32 = (1 << 16) * (1 << 16); - -export class UnsignedLong { - low: number; - high: number; - - constructor(low: number, high: number) { - this.low = low; - this.high = high; - } - - static fromU32(value: number): UnsignedLong { - return new UnsignedLong(value % TWO_PWR_32 | 0, 0); - } - - multiply(value: UnsignedLong): UnsignedLong { - const a48 = this.high >>> 16; - const a32 = this.high & 0xffff; - const a16 = this.low >>> 16; - const a00 = this.low & 0xffff; - - const b48 = value.high >>> 16; - const b32 = value.high & 0xffff; - const b16 = value.low >>> 16; - const b00 = value.low & 0xffff; - - let c48 = 0; - let c32 = 0; - let c16 = 0; - let c00 = 0; - c00 += a00 * b00; - c16 += c00 >>> 16; - c00 &= 0xffff; - c16 += a16 * b00; - c32 += c16 >>> 16; - c16 &= 0xffff; - c16 += a00 * b16; - c32 += c16 >>> 16; - c16 &= 0xffff; - c32 += a32 * b00; - c48 += c32 >>> 16; - c32 &= 0xffff; - c32 += a16 * b16; - c48 += c32 >>> 16; - c32 &= 0xffff; - c32 += a00 * b32; - c48 += c32 >>> 16; - c32 &= 0xffff; - c48 += a48 * b00 + a32 * b16 + a16 * b32 + a00 * b48; - c48 &= 0xffff; - - return new UnsignedLong((c16 << 16) | c00, (c48 << 16) | c32); - } - - add(value: UnsignedLong): UnsignedLong { - const a48 = this.high >>> 16; - const a32 = this.high & 0xffff; - const a16 = this.low >>> 16; - const a00 = this.low & 0xffff; - - const b48 = value.high >>> 16; - const b32 = value.high & 0xffff; - const b16 = value.low >>> 16; - const b00 = value.low & 0xffff; - - let c48 = 0; - let c32 = 0; - let c16 = 0; - let c00 = 0; - c00 += a00 + b00; - c16 += c00 >>> 16; - c00 &= 0xffff; - c16 += a16 + b16; - c32 += c16 >>> 16; - c16 &= 0xffff; - c32 += a32 + b32; - c48 += c32 >>> 16; - c32 &= 0xffff; - c48 += a48 + b48; - c48 &= 0xffff; - - return new UnsignedLong((c16 << 16) | c00, (c48 << 16) | c32); - } - - static fromString(str: string): UnsignedLong { - let result = UnsignedLong.fromU32(0); - - for (let i = 0; i < str.length; i += 8) { - const size = Math.min(8, str.length - i); - const value = parseInt(str.substring(i, i + size)); - if (size < 8) { - const power = UnsignedLong.fromU32(Math.pow(10, size)); - result = result.multiply(power).add(UnsignedLong.fromU32(value)); - } else { - result = result.multiply(UnsignedLong.fromU32(100_000_000)); - result = result.add(UnsignedLong.fromU32(value)); - } - } - - return result; - } -} diff --git a/experimental/packages/otlp-transformer/src/logs/index.ts b/experimental/packages/otlp-transformer/src/logs/index.ts index bffb7966dd..9ca1a40c28 100644 --- a/experimental/packages/otlp-transformer/src/logs/index.ts +++ b/experimental/packages/otlp-transformer/src/logs/index.ts @@ -22,19 +22,19 @@ import { IResourceLogs, } from './types'; import { IResource } from '@opentelemetry/resources'; -import { hrTimeToFixed64Nanos } from '../common'; +import { Encoder, getOtlpEncoder } from '../common'; import { toAnyValue, toAttributes, toKeyValue } from '../common/internal'; -import { hexToBase64 } from '@opentelemetry/core'; import { SeverityNumber } from '@opentelemetry/api-logs'; -import { IKeyValue } from '../common/types'; +import { OtlpEncodingOptions, IKeyValue } from '../common/types'; import { LogAttributes } from '@opentelemetry/api-logs'; export function createExportLogsServiceRequest( logRecords: ReadableLogRecord[], - useHex?: boolean + options?: OtlpEncodingOptions ): IExportLogsServiceRequest { + const encoder = getOtlpEncoder(options); return { - resourceLogs: logRecordsToResourceLogs(logRecords, useHex), + resourceLogs: logRecordsToResourceLogs(logRecords, encoder), }; } @@ -71,7 +71,7 @@ function createResourceMap( function logRecordsToResourceLogs( logRecords: ReadableLogRecord[], - useHex?: boolean + encoder: Encoder ): IResourceLogs[] { const resourceMap = createResourceMap(logRecords); return Array.from(resourceMap, ([resource, ismMap]) => ({ @@ -85,7 +85,7 @@ function logRecordsToResourceLogs( } = scopeLogs[0]; return { scope: { name, version }, - logRecords: scopeLogs.map(log => toLogRecord(log, useHex)), + logRecords: scopeLogs.map(log => toLogRecord(log, encoder)), schemaUrl, }; }), @@ -93,22 +93,18 @@ function logRecordsToResourceLogs( })); } -function toLogRecord(log: ReadableLogRecord, useHex?: boolean): ILogRecord { +function toLogRecord(log: ReadableLogRecord, encoder: Encoder): ILogRecord { return { - timeUnixNano: hrTimeToFixed64Nanos(log.hrTime), - observedTimeUnixNano: hrTimeToFixed64Nanos(log.hrTimeObserved), + timeUnixNano: encoder.encodeHrTime(log.hrTime), + observedTimeUnixNano: encoder.encodeHrTime(log.hrTimeObserved), severityNumber: toSeverityNumber(log.severityNumber), severityText: log.severityText, body: toAnyValue(log.body), attributes: toLogAttributes(log.attributes), droppedAttributesCount: 0, flags: log.spanContext?.traceFlags, - traceId: useHex - ? log.spanContext?.traceId - : optionalHexToBase64(log.spanContext?.traceId), - spanId: useHex - ? log.spanContext?.spanId - : optionalHexToBase64(log.spanContext?.spanId), + traceId: encoder.encodeOptionalSpanContext(log.spanContext?.traceId), + spanId: encoder.encodeOptionalSpanContext(log.spanContext?.spanId), }; } @@ -118,11 +114,6 @@ function toSeverityNumber( return severityNumber as number | undefined as ESeverityNumber | undefined; } -function optionalHexToBase64(str: string | undefined): string | undefined { - if (str === undefined) return undefined; - return hexToBase64(str); -} - export function toLogAttributes(attributes: LogAttributes): IKeyValue[] { return Object.keys(attributes).map(key => toKeyValue(key, attributes[key])); } diff --git a/experimental/packages/otlp-transformer/src/logs/types.ts b/experimental/packages/otlp-transformer/src/logs/types.ts index 7704c37083..2af77ad7c8 100644 --- a/experimental/packages/otlp-transformer/src/logs/types.ts +++ b/experimental/packages/otlp-transformer/src/logs/types.ts @@ -15,8 +15,8 @@ */ import type { + Fixed64, IAnyValue, - IFixed64, IInstrumentationScope, IKeyValue, } from '../common/types'; @@ -68,10 +68,10 @@ export interface IScopeLogs { /** Properties of a LogRecord. */ export interface ILogRecord { /** LogRecord timeUnixNano */ - timeUnixNano: IFixed64; + timeUnixNano: Fixed64; /** LogRecord observedTimeUnixNano */ - observedTimeUnixNano: IFixed64; + observedTimeUnixNano: Fixed64; /** LogRecord severityNumber */ severityNumber?: ESeverityNumber; diff --git a/experimental/packages/otlp-transformer/src/metrics/index.ts b/experimental/packages/otlp-transformer/src/metrics/index.ts index 5cef667c7e..9ef3c57d3d 100644 --- a/experimental/packages/otlp-transformer/src/metrics/index.ts +++ b/experimental/packages/otlp-transformer/src/metrics/index.ts @@ -15,12 +15,16 @@ */ import type { ResourceMetrics } from '@opentelemetry/sdk-metrics'; import type { IExportMetricsServiceRequest } from './types'; +import type { OtlpEncodingOptions } from '../common/types'; import { toResourceMetrics } from './internal'; export function createExportMetricsServiceRequest( - resourceMetrics: ResourceMetrics[] + resourceMetrics: ResourceMetrics[], + options?: OtlpEncodingOptions ): IExportMetricsServiceRequest { return { - resourceMetrics: resourceMetrics.map(metrics => toResourceMetrics(metrics)), + resourceMetrics: resourceMetrics.map(metrics => + toResourceMetrics(metrics, options) + ), }; } diff --git a/experimental/packages/otlp-transformer/src/metrics/internal.ts b/experimental/packages/otlp-transformer/src/metrics/internal.ts index 670741eb30..1a621a4213 100644 --- a/experimental/packages/otlp-transformer/src/metrics/internal.ts +++ b/experimental/packages/otlp-transformer/src/metrics/internal.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import type { OtlpEncodingOptions } from '../common/types'; import { ValueType } from '@opentelemetry/api'; import { AggregationTemporality, @@ -24,7 +25,6 @@ import { ResourceMetrics, ScopeMetrics, } from '@opentelemetry/sdk-metrics'; -import { hrTimeToFixed64Nanos } from '../common'; import { toAttributes } from '../common/internal'; import { EAggregationTemporality, @@ -35,34 +35,40 @@ import { IResourceMetrics, IScopeMetrics, } from './types'; +import { Encoder, getOtlpEncoder } from '../common'; export function toResourceMetrics( - resourceMetrics: ResourceMetrics + resourceMetrics: ResourceMetrics, + options?: OtlpEncodingOptions ): IResourceMetrics { + const encoder = getOtlpEncoder(options); return { resource: { attributes: toAttributes(resourceMetrics.resource.attributes), droppedAttributesCount: 0, }, schemaUrl: undefined, - scopeMetrics: toScopeMetrics(resourceMetrics.scopeMetrics), + scopeMetrics: toScopeMetrics(resourceMetrics.scopeMetrics, encoder), }; } -export function toScopeMetrics(scopeMetrics: ScopeMetrics[]): IScopeMetrics[] { +export function toScopeMetrics( + scopeMetrics: ScopeMetrics[], + encoder: Encoder +): IScopeMetrics[] { return Array.from( scopeMetrics.map(metrics => ({ scope: { name: metrics.scope.name, version: metrics.scope.version, }, - metrics: metrics.metrics.map(metricData => toMetric(metricData)), + metrics: metrics.metrics.map(metricData => toMetric(metricData, encoder)), schemaUrl: metrics.scope.schemaUrl, })) ); } -export function toMetric(metricData: MetricData): IMetric { +export function toMetric(metricData: MetricData, encoder: Encoder): IMetric { const out: IMetric = { name: metricData.descriptor.name, description: metricData.descriptor.description, @@ -78,24 +84,24 @@ export function toMetric(metricData: MetricData): IMetric { out.sum = { aggregationTemporality, isMonotonic: metricData.isMonotonic, - dataPoints: toSingularDataPoints(metricData), + dataPoints: toSingularDataPoints(metricData, encoder), }; break; case DataPointType.GAUGE: out.gauge = { - dataPoints: toSingularDataPoints(metricData), + dataPoints: toSingularDataPoints(metricData, encoder), }; break; case DataPointType.HISTOGRAM: out.histogram = { aggregationTemporality, - dataPoints: toHistogramDataPoints(metricData), + dataPoints: toHistogramDataPoints(metricData, encoder), }; break; case DataPointType.EXPONENTIAL_HISTOGRAM: out.exponentialHistogram = { aggregationTemporality, - dataPoints: toExponentialHistogramDataPoints(metricData), + dataPoints: toExponentialHistogramDataPoints(metricData, encoder), }; break; } @@ -108,12 +114,13 @@ function toSingularDataPoint( | DataPoint | DataPoint | DataPoint, - valueType: ValueType + valueType: ValueType, + encoder: Encoder ) { const out: INumberDataPoint = { attributes: toAttributes(dataPoint.attributes), - startTimeUnixNano: hrTimeToFixed64Nanos(dataPoint.startTime), - timeUnixNano: hrTimeToFixed64Nanos(dataPoint.endTime), + startTimeUnixNano: encoder.encodeHrTime(dataPoint.startTime), + timeUnixNano: encoder.encodeHrTime(dataPoint.endTime), }; switch (valueType) { @@ -128,13 +135,23 @@ function toSingularDataPoint( return out; } -function toSingularDataPoints(metricData: MetricData): INumberDataPoint[] { +function toSingularDataPoints( + metricData: MetricData, + encoder: Encoder +): INumberDataPoint[] { return metricData.dataPoints.map(dataPoint => { - return toSingularDataPoint(dataPoint, metricData.descriptor.valueType); + return toSingularDataPoint( + dataPoint, + metricData.descriptor.valueType, + encoder + ); }); } -function toHistogramDataPoints(metricData: MetricData): IHistogramDataPoint[] { +function toHistogramDataPoints( + metricData: MetricData, + encoder: Encoder +): IHistogramDataPoint[] { return metricData.dataPoints.map(dataPoint => { const histogram = dataPoint.value as Histogram; return { @@ -145,14 +162,15 @@ function toHistogramDataPoints(metricData: MetricData): IHistogramDataPoint[] { sum: histogram.sum, min: histogram.min, max: histogram.max, - startTimeUnixNano: hrTimeToFixed64Nanos(dataPoint.startTime), - timeUnixNano: hrTimeToFixed64Nanos(dataPoint.endTime), + startTimeUnixNano: encoder.encodeHrTime(dataPoint.startTime), + timeUnixNano: encoder.encodeHrTime(dataPoint.endTime), }; }); } function toExponentialHistogramDataPoints( - metricData: MetricData + metricData: MetricData, + encoder: Encoder ): IExponentialHistogramDataPoint[] { return metricData.dataPoints.map(dataPoint => { const histogram = dataPoint.value as ExponentialHistogram; @@ -172,8 +190,8 @@ function toExponentialHistogramDataPoints( }, scale: histogram.scale, zeroCount: histogram.zeroCount, - startTimeUnixNano: hrTimeToFixed64Nanos(dataPoint.startTime), - timeUnixNano: hrTimeToFixed64Nanos(dataPoint.endTime), + startTimeUnixNano: encoder.encodeHrTime(dataPoint.startTime), + timeUnixNano: encoder.encodeHrTime(dataPoint.endTime), }; }); } diff --git a/experimental/packages/otlp-transformer/src/metrics/types.ts b/experimental/packages/otlp-transformer/src/metrics/types.ts index 65a2348516..96a3dda798 100644 --- a/experimental/packages/otlp-transformer/src/metrics/types.ts +++ b/experimental/packages/otlp-transformer/src/metrics/types.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { IFixed64, IInstrumentationScope, IKeyValue } from '../common/types'; +import { Fixed64, IInstrumentationScope, IKeyValue } from '../common/types'; import { IResource } from '../resource/types'; /** Properties of an ExportMetricsServiceRequest. */ @@ -134,10 +134,10 @@ export interface INumberDataPoint { attributes: IKeyValue[]; /** NumberDataPoint startTimeUnixNano */ - startTimeUnixNano?: IFixed64 | string; + startTimeUnixNano?: Fixed64; /** NumberDataPoint timeUnixNano */ - timeUnixNano?: IFixed64 | string; + timeUnixNano?: Fixed64; /** NumberDataPoint asDouble */ asDouble?: number | null; @@ -158,10 +158,10 @@ export interface IHistogramDataPoint { attributes?: IKeyValue[]; /** HistogramDataPoint startTimeUnixNano */ - startTimeUnixNano?: IFixed64 | string; + startTimeUnixNano?: Fixed64; /** HistogramDataPoint timeUnixNano */ - timeUnixNano?: IFixed64 | string; + timeUnixNano?: Fixed64; /** HistogramDataPoint count */ count?: number; @@ -194,10 +194,10 @@ export interface IExponentialHistogramDataPoint { attributes?: IKeyValue[]; /** ExponentialHistogramDataPoint startTimeUnixNano */ - startTimeUnixNano?: IFixed64 | string; + startTimeUnixNano?: Fixed64; /** ExponentialHistogramDataPoint timeUnixNano */ - timeUnixNano?: IFixed64 | string; + timeUnixNano?: Fixed64; /** ExponentialHistogramDataPoint count */ count?: number; diff --git a/experimental/packages/otlp-transformer/src/trace/index.ts b/experimental/packages/otlp-transformer/src/trace/index.ts index ad06612a11..5db18a082e 100644 --- a/experimental/packages/otlp-transformer/src/trace/index.ts +++ b/experimental/packages/otlp-transformer/src/trace/index.ts @@ -15,6 +15,7 @@ */ import type { IResource } from '@opentelemetry/resources'; import type { ReadableSpan } from '@opentelemetry/sdk-trace-base'; +import type { OtlpEncodingOptions } from '../common/types'; import { toAttributes } from '../common/internal'; import { sdkSpanToOtlpSpan } from './internal'; import { @@ -22,13 +23,15 @@ import { IResourceSpans, IScopeSpans, } from './types'; +import { Encoder, getOtlpEncoder } from '../common'; export function createExportTraceServiceRequest( spans: ReadableSpan[], - useHex?: boolean + options?: OtlpEncodingOptions ): IExportTraceServiceRequest { + const encoder = getOtlpEncoder(options); return { - resourceSpans: spanRecordsToResourceSpans(spans, useHex), + resourceSpans: spanRecordsToResourceSpans(spans, encoder), }; } @@ -61,7 +64,7 @@ function createResourceMap(readableSpans: ReadableSpan[]) { function spanRecordsToResourceSpans( readableSpans: ReadableSpan[], - useHex?: boolean + encoder: Encoder ): IResourceSpans[] { const resourceMap = createResourceMap(readableSpans); const out: IResourceSpans[] = []; @@ -79,7 +82,7 @@ function spanRecordsToResourceSpans( const { name, version, schemaUrl } = scopeSpans[0].instrumentationLibrary; const spans = scopeSpans.map(readableSpan => - sdkSpanToOtlpSpan(readableSpan, useHex) + sdkSpanToOtlpSpan(readableSpan, encoder) ); scopeResourceSpans.push({ diff --git a/experimental/packages/otlp-transformer/src/trace/internal.ts b/experimental/packages/otlp-transformer/src/trace/internal.ts index d9c17855ce..a4236e5dca 100644 --- a/experimental/packages/otlp-transformer/src/trace/internal.ts +++ b/experimental/packages/otlp-transformer/src/trace/internal.ts @@ -15,64 +15,57 @@ */ import type { Link } from '@opentelemetry/api'; import type { ReadableSpan, TimedEvent } from '@opentelemetry/sdk-trace-base'; -import { hrTimeToFixed64Nanos } from '../common'; +import type { Encoder } from '../common'; import { toAttributes } from '../common/internal'; import { EStatusCode, IEvent, ILink, ISpan } from './types'; -import * as core from '@opentelemetry/core'; -export function sdkSpanToOtlpSpan(span: ReadableSpan, useHex?: boolean): ISpan { +export function sdkSpanToOtlpSpan(span: ReadableSpan, encoder: Encoder): ISpan { const ctx = span.spanContext(); const status = span.status; - const parentSpanId = useHex - ? span.parentSpanId - : span.parentSpanId != null - ? core.hexToBase64(span.parentSpanId) - : undefined; return { - traceId: useHex ? ctx.traceId : core.hexToBase64(ctx.traceId), - spanId: useHex ? ctx.spanId : core.hexToBase64(ctx.spanId), - parentSpanId: parentSpanId, + traceId: encoder.encodeSpanContext(ctx.traceId), + spanId: encoder.encodeSpanContext(ctx.spanId), + parentSpanId: encoder.encodeOptionalSpanContext(span.parentSpanId), traceState: ctx.traceState?.serialize(), name: span.name, // Span kind is offset by 1 because the API does not define a value for unset kind: span.kind == null ? 0 : span.kind + 1, - startTimeUnixNano: hrTimeToFixed64Nanos(span.startTime), - endTimeUnixNano: hrTimeToFixed64Nanos(span.endTime), + startTimeUnixNano: encoder.encodeHrTime(span.startTime), + endTimeUnixNano: encoder.encodeHrTime(span.endTime), attributes: toAttributes(span.attributes), droppedAttributesCount: span.droppedAttributesCount, - events: span.events.map(toOtlpSpanEvent), + events: span.events.map(event => toOtlpSpanEvent(event, encoder)), droppedEventsCount: span.droppedEventsCount, status: { // API and proto enums share the same values code: status.code as unknown as EStatusCode, message: status.message, }, - links: span.links.map(link => toOtlpLink(link, useHex)), + links: span.links.map(link => toOtlpLink(link, encoder)), droppedLinksCount: span.droppedLinksCount, }; } -export function toOtlpLink(link: Link, useHex?: boolean): ILink { +export function toOtlpLink(link: Link, encoder: Encoder): ILink { return { attributes: link.attributes ? toAttributes(link.attributes) : [], - spanId: useHex - ? link.context.spanId - : core.hexToBase64(link.context.spanId), - traceId: useHex - ? link.context.traceId - : core.hexToBase64(link.context.traceId), + spanId: encoder.encodeSpanContext(link.context.spanId), + traceId: encoder.encodeSpanContext(link.context.traceId), traceState: link.context.traceState?.serialize(), droppedAttributesCount: link.droppedAttributesCount || 0, }; } -export function toOtlpSpanEvent(timedEvent: TimedEvent): IEvent { +export function toOtlpSpanEvent( + timedEvent: TimedEvent, + encoder: Encoder +): IEvent { return { attributes: timedEvent.attributes ? toAttributes(timedEvent.attributes) : [], name: timedEvent.name, - timeUnixNano: hrTimeToFixed64Nanos(timedEvent.time), + timeUnixNano: encoder.encodeHrTime(timedEvent.time), droppedAttributesCount: timedEvent.droppedAttributesCount || 0, }; } diff --git a/experimental/packages/otlp-transformer/src/trace/types.ts b/experimental/packages/otlp-transformer/src/trace/types.ts index 294f45652f..b9618dd75f 100644 --- a/experimental/packages/otlp-transformer/src/trace/types.ts +++ b/experimental/packages/otlp-transformer/src/trace/types.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { IFixed64, IInstrumentationScope, IKeyValue } from '../common/types'; +import { Fixed64, IInstrumentationScope, IKeyValue } from '../common/types'; import { IResource } from '../resource/types'; /** Properties of an ExportTraceServiceRequest. */ @@ -81,10 +81,10 @@ export interface ISpan { kind: ESpanKind; /** Span startTimeUnixNano */ - startTimeUnixNano: IFixed64; + startTimeUnixNano: Fixed64; /** Span endTimeUnixNano */ - endTimeUnixNano: IFixed64; + endTimeUnixNano: Fixed64; /** Span attributes */ attributes: IKeyValue[]; @@ -166,7 +166,7 @@ export const enum EStatusCode { /** Properties of an Event. */ export interface IEvent { /** Event timeUnixNano */ - timeUnixNano: IFixed64; + timeUnixNano: Fixed64; /** Event name */ name: string; diff --git a/experimental/packages/otlp-transformer/test/common.test.ts b/experimental/packages/otlp-transformer/test/common.test.ts index dfcb24d6f5..d31267406c 100644 --- a/experimental/packages/otlp-transformer/test/common.test.ts +++ b/experimental/packages/otlp-transformer/test/common.test.ts @@ -14,9 +14,14 @@ * limitations under the License. */ +import { hexToBase64 } from '@opentelemetry/core'; +import { getOtlpEncoder } from '../src'; import { toAnyValue } from '../src/common/internal'; import * as assert from 'assert'; +const traceId = 'abcdef01234567890000000000000000'; +const spanId = '12341234abcdabcd'; + describe('common', () => { describe('toAnyValue', () => { it('serializes an array', () => { @@ -63,4 +68,64 @@ describe('common', () => { }); }); }); + + describe('otlp encoder', () => { + it('defaults to long timestamps and base64 encoding given no options', () => { + const encoder = getOtlpEncoder(); + assert.deepStrictEqual(encoder.encodeHrTime([1697978649, 99870675]), { + low: 3352011219, + high: 395341461, + }); + assert.deepStrictEqual( + encoder.encodeSpanContext(traceId), + hexToBase64(traceId) + ); + assert.deepStrictEqual( + encoder.encodeOptionalSpanContext(spanId), + hexToBase64(spanId) + ); + assert.deepStrictEqual( + encoder.encodeOptionalSpanContext(undefined), + undefined + ); + }); + + it('defaults to long timestamps and base64 encoding given empty options', () => { + const encoder = getOtlpEncoder({}); + assert.deepStrictEqual(encoder.encodeHrTime([1697978649, 99870675]), { + low: 3352011219, + high: 395341461, + }); + assert.deepStrictEqual( + encoder.encodeSpanContext(traceId), + hexToBase64(traceId) + ); + assert.deepStrictEqual( + encoder.encodeOptionalSpanContext(spanId), + hexToBase64(spanId) + ); + assert.deepStrictEqual( + encoder.encodeOptionalSpanContext(undefined), + undefined + ); + }); + + it('can encode HrTime as string', () => { + const encoder = getOtlpEncoder({ useLongBits: false }); + assert.deepStrictEqual( + encoder.encodeHrTime([1697978649, 99870675]), + '1697978649099870675' + ); + }); + + it('can encode span context as hex', () => { + const encoder = getOtlpEncoder({ useHex: true }); + assert.deepStrictEqual(encoder.encodeSpanContext(traceId), traceId); + assert.deepStrictEqual(encoder.encodeOptionalSpanContext(spanId), spanId); + assert.deepStrictEqual( + encoder.encodeOptionalSpanContext(undefined), + undefined + ); + }); + }); }); diff --git a/experimental/packages/otlp-transformer/test/logs.test.ts b/experimental/packages/otlp-transformer/test/logs.test.ts index 0937c7d152..85e55e9fd7 100644 --- a/experimental/packages/otlp-transformer/test/logs.test.ts +++ b/experimental/packages/otlp-transformer/test/logs.test.ts @@ -21,7 +21,6 @@ import { createExportLogsServiceRequest, ESeverityNumber, IExportLogsServiceRequest, - UnsignedLong, } from '../src'; import { ReadableLogRecord } from '@opentelemetry/sdk-logs'; import { SeverityNumber } from '@opentelemetry/api-logs'; @@ -50,8 +49,8 @@ function createExpectedLogJson(useHex: boolean): IExportLogsServiceRequest { scope: { name: 'scope_name_1', version: '0.1.0' }, logRecords: [ { - timeUnixNano: new UnsignedLong(-162521437, 391214506), - observedTimeUnixNano: new UnsignedLong(584929536, 391976663), + timeUnixNano: { low: 4132445859, high: 391214506 }, + observedTimeUnixNano: { low: 584929536, high: 391976663 }, severityNumber: ESeverityNumber.SEVERITY_NUMBER_ERROR, severityText: 'error', body: { stringValue: 'some_log_body' }, @@ -147,19 +146,26 @@ describe('Logs', () => { }); it('returns null on an empty list', () => { - assert.deepStrictEqual(createExportLogsServiceRequest([], true), { - resourceLogs: [], - }); + assert.deepStrictEqual( + createExportLogsServiceRequest([], { useHex: true }), + { + resourceLogs: [], + } + ); }); it('serializes a log record with useHex = true', () => { - const exportRequest = createExportLogsServiceRequest([log_1_1_1], true); + const exportRequest = createExportLogsServiceRequest([log_1_1_1], { + useHex: true, + }); assert.ok(exportRequest); assert.deepStrictEqual(exportRequest, createExpectedLogJson(true)); }); it('serializes a log record with useHex = false', () => { - const exportRequest = createExportLogsServiceRequest([log_1_1_1], false); + const exportRequest = createExportLogsServiceRequest([log_1_1_1], { + useHex: false, + }); assert.ok(exportRequest); assert.deepStrictEqual(exportRequest, createExpectedLogJson(false)); }); @@ -167,7 +173,7 @@ describe('Logs', () => { it('aggregates multiple logs with same resource and same scope', () => { const exportRequest = createExportLogsServiceRequest( [log_1_1_1, log_1_1_2], - false + { useHex: false } ); assert.ok(exportRequest); assert.strictEqual(exportRequest.resourceLogs?.length, 1); @@ -181,7 +187,7 @@ describe('Logs', () => { it('aggregates multiple logs with same resource and different scopes', () => { const exportRequest = createExportLogsServiceRequest( [log_1_1_1, log_1_2_1], - false + { useHex: false } ); assert.ok(exportRequest); assert.strictEqual(exportRequest.resourceLogs?.length, 1); @@ -191,7 +197,7 @@ describe('Logs', () => { it('aggregates multiple logs with different resources', () => { const exportRequest = createExportLogsServiceRequest( [log_1_1_1, log_2_1_1], - false + { useHex: false } ); assert.ok(exportRequest); assert.strictEqual(exportRequest.resourceLogs?.length, 2); diff --git a/experimental/packages/otlp-transformer/test/metrics.test.ts b/experimental/packages/otlp-transformer/test/metrics.test.ts index 15fd7d0531..526458e658 100644 --- a/experimental/packages/otlp-transformer/test/metrics.test.ts +++ b/experimental/packages/otlp-transformer/test/metrics.test.ts @@ -26,7 +26,7 @@ import * as assert from 'assert'; import { createExportMetricsServiceRequest } from '../src/metrics'; import { EAggregationTemporality } from '../src/metrics/types'; import { hrTime } from '@opentelemetry/core'; -import { hrTimeToFixed64Nanos } from '../src/common'; +import { encodeAsLongBits } from '../src'; const START_TIME = hrTime(); const END_TIME = hrTime(); @@ -351,8 +351,8 @@ describe('Metrics', () => { dataPoints: [ { attributes: expectedAttributes, - startTimeUnixNano: hrTimeToFixed64Nanos(START_TIME), - timeUnixNano: hrTimeToFixed64Nanos(END_TIME), + startTimeUnixNano: encodeAsLongBits(START_TIME), + timeUnixNano: encodeAsLongBits(END_TIME), asInt: 10, }, ], @@ -394,8 +394,8 @@ describe('Metrics', () => { dataPoints: [ { attributes: expectedAttributes, - startTimeUnixNano: hrTimeToFixed64Nanos(START_TIME), - timeUnixNano: hrTimeToFixed64Nanos(END_TIME), + startTimeUnixNano: encodeAsLongBits(START_TIME), + timeUnixNano: encodeAsLongBits(END_TIME), asInt: 10, }, ], @@ -438,8 +438,8 @@ describe('Metrics', () => { dataPoints: [ { attributes: expectedAttributes, - startTimeUnixNano: hrTimeToFixed64Nanos(START_TIME), - timeUnixNano: hrTimeToFixed64Nanos(END_TIME), + startTimeUnixNano: encodeAsLongBits(START_TIME), + timeUnixNano: encodeAsLongBits(END_TIME), asInt: 10, }, ], @@ -482,8 +482,8 @@ describe('Metrics', () => { dataPoints: [ { attributes: expectedAttributes, - startTimeUnixNano: hrTimeToFixed64Nanos(START_TIME), - timeUnixNano: hrTimeToFixed64Nanos(END_TIME), + startTimeUnixNano: encodeAsLongBits(START_TIME), + timeUnixNano: encodeAsLongBits(END_TIME), asInt: 10, }, ], @@ -524,8 +524,8 @@ describe('Metrics', () => { dataPoints: [ { attributes: expectedAttributes, - startTimeUnixNano: hrTimeToFixed64Nanos(START_TIME), - timeUnixNano: hrTimeToFixed64Nanos(END_TIME), + startTimeUnixNano: encodeAsLongBits(START_TIME), + timeUnixNano: encodeAsLongBits(END_TIME), asDouble: 10.5, }, ], @@ -582,8 +582,8 @@ describe('Metrics', () => { sum: 9, min: 1, max: 8, - startTimeUnixNano: hrTimeToFixed64Nanos(START_TIME), - timeUnixNano: hrTimeToFixed64Nanos(END_TIME), + startTimeUnixNano: encodeAsLongBits(START_TIME), + timeUnixNano: encodeAsLongBits(END_TIME), }, ], }, @@ -636,8 +636,8 @@ describe('Metrics', () => { sum: 9, min: undefined, max: undefined, - startTimeUnixNano: hrTimeToFixed64Nanos(START_TIME), - timeUnixNano: hrTimeToFixed64Nanos(END_TIME), + startTimeUnixNano: encodeAsLongBits(START_TIME), + timeUnixNano: encodeAsLongBits(END_TIME), }, ], }, @@ -702,8 +702,8 @@ describe('Metrics', () => { bucketCounts: [1, 0, 0, 0, 1, 0, 1, 0], }, negative: { offset: 0, bucketCounts: [0] }, - startTimeUnixNano: hrTimeToFixed64Nanos(START_TIME), - timeUnixNano: hrTimeToFixed64Nanos(END_TIME), + startTimeUnixNano: encodeAsLongBits(START_TIME), + timeUnixNano: encodeAsLongBits(END_TIME), }, ], }, @@ -764,8 +764,8 @@ describe('Metrics', () => { bucketCounts: [1, 0, 0, 0, 1, 0, 1, 0], }, negative: { offset: 0, bucketCounts: [0] }, - startTimeUnixNano: hrTimeToFixed64Nanos(START_TIME), - timeUnixNano: hrTimeToFixed64Nanos(END_TIME), + startTimeUnixNano: encodeAsLongBits(START_TIME), + timeUnixNano: encodeAsLongBits(END_TIME), }, ], }, diff --git a/experimental/packages/otlp-transformer/test/trace.test.ts b/experimental/packages/otlp-transformer/test/trace.test.ts index 380c9065c2..1eb786b30f 100644 --- a/experimental/packages/otlp-transformer/test/trace.test.ts +++ b/experimental/packages/otlp-transformer/test/trace.test.ts @@ -19,13 +19,26 @@ import { Resource } from '@opentelemetry/resources'; import { ReadableSpan } from '@opentelemetry/sdk-trace-base'; import * as assert from 'assert'; import { + OtlpEncodingOptions, createExportTraceServiceRequest, ESpanKind, EStatusCode, - UnsignedLong, } from '../src'; -function createExpectedSpanJson(useHex: boolean) { +function createExpectedSpanJson(options: OtlpEncodingOptions) { + const useHex = options.useHex ?? false; + const useLongBits = options.useLongBits ?? true; + + const startTime = useLongBits + ? { low: 1155450124, high: 382008859 } + : '1640715557342725388'; + const endTime = useLongBits + ? { low: 2455450124, high: 382008859 } + : '1640715558642725388'; + const eventTime = useLongBits + ? { low: 2355450124, high: 382008859 } + : '1640715558542725388'; + const traceId = useHex ? '00000000000000000000000000000001' : hexToBase64('00000000000000000000000000000001'); @@ -80,8 +93,8 @@ function createExpectedSpanJson(useHex: boolean) { ], }, ], - startTimeUnixNano: new UnsignedLong(1155450124, 382008859), - endTimeUnixNano: new UnsignedLong(-1839517172, 382008859), + startTimeUnixNano: startTime, + endTimeUnixNano: endTime, events: [ { droppedAttributesCount: 0, @@ -94,7 +107,7 @@ function createExpectedSpanJson(useHex: boolean) { }, ], name: 'some event', - timeUnixNano: new UnsignedLong(-1939517172, 382008859), + timeUnixNano: eventTime, }, ], attributes: [ @@ -184,26 +197,48 @@ describe('Trace', () => { }); it('returns null on an empty list', () => { - assert.deepStrictEqual(createExportTraceServiceRequest([], true), { - resourceSpans: [], - }); + assert.deepStrictEqual( + createExportTraceServiceRequest([], { useHex: true }), + { + resourceSpans: [], + } + ); }); it('serializes a span with useHex = true', () => { - const exportRequest = createExportTraceServiceRequest([span], true); + const exportRequest = createExportTraceServiceRequest([span], { + useHex: true, + }); assert.ok(exportRequest); - assert.deepStrictEqual(exportRequest, createExpectedSpanJson(true)); + assert.deepStrictEqual( + exportRequest, + createExpectedSpanJson({ useHex: true }) + ); }); it('serializes a span with useHex = false', () => { - const exportRequest = createExportTraceServiceRequest([span], false); + const exportRequest = createExportTraceServiceRequest([span], { + useHex: false, + }); assert.ok(exportRequest); - assert.deepStrictEqual(exportRequest, createExpectedSpanJson(false)); + assert.deepStrictEqual( + exportRequest, + createExpectedSpanJson({ useHex: false }) + ); + }); + + it('serializes a span with string timestamps', () => { + const options: OtlpEncodingOptions = { useLongBits: false }; + const exportRequest = createExportTraceServiceRequest([span], options); + assert.ok(exportRequest); + assert.deepStrictEqual(exportRequest, createExpectedSpanJson(options)); }); it('serializes a span without a parent with useHex = true', () => { (span as any).parentSpanId = undefined; - const exportRequest = createExportTraceServiceRequest([span], true); + const exportRequest = createExportTraceServiceRequest([span], { + useHex: true, + }); assert.ok(exportRequest); assert.strictEqual( exportRequest.resourceSpans?.[0].scopeSpans[0].spans?.[0].parentSpanId, @@ -213,7 +248,9 @@ describe('Trace', () => { it('serializes a span without a parent with useHex = false', () => { (span as any).parentSpanId = undefined; - const exportRequest = createExportTraceServiceRequest([span], false); + const exportRequest = createExportTraceServiceRequest([span], { + useHex: false, + }); assert.ok(exportRequest); assert.strictEqual( exportRequest.resourceSpans?.[0].scopeSpans[0].spans?.[0].parentSpanId, @@ -225,7 +262,9 @@ describe('Trace', () => { it('error', () => { span.status.code = SpanStatusCode.ERROR; span.status.message = 'error message'; - const exportRequest = createExportTraceServiceRequest([span], true); + const exportRequest = createExportTraceServiceRequest([span], { + useHex: true, + }); assert.ok(exportRequest); const spanStatus = exportRequest.resourceSpans?.[0].scopeSpans[0].spans?.[0].status; @@ -235,7 +274,9 @@ describe('Trace', () => { it('unset', () => { span.status.code = SpanStatusCode.UNSET; - const exportRequest = createExportTraceServiceRequest([span], true); + const exportRequest = createExportTraceServiceRequest([span], { + useHex: true, + }); assert.ok(exportRequest); assert.strictEqual( exportRequest.resourceSpans?.[0].scopeSpans[0].spans?.[0].status.code, @@ -247,7 +288,9 @@ describe('Trace', () => { describe('span kind', () => { it('consumer', () => { (span as any).kind = SpanKind.CONSUMER; - const exportRequest = createExportTraceServiceRequest([span], true); + const exportRequest = createExportTraceServiceRequest([span], { + useHex: true, + }); assert.ok(exportRequest); assert.strictEqual( exportRequest.resourceSpans?.[0].scopeSpans[0].spans?.[0].kind, @@ -256,7 +299,9 @@ describe('Trace', () => { }); it('internal', () => { (span as any).kind = SpanKind.INTERNAL; - const exportRequest = createExportTraceServiceRequest([span], true); + const exportRequest = createExportTraceServiceRequest([span], { + useHex: true, + }); assert.ok(exportRequest); assert.strictEqual( exportRequest.resourceSpans?.[0].scopeSpans[0].spans?.[0].kind, @@ -265,7 +310,9 @@ describe('Trace', () => { }); it('producer', () => { (span as any).kind = SpanKind.PRODUCER; - const exportRequest = createExportTraceServiceRequest([span], true); + const exportRequest = createExportTraceServiceRequest([span], { + useHex: true, + }); assert.ok(exportRequest); assert.strictEqual( exportRequest.resourceSpans?.[0].scopeSpans[0].spans?.[0].kind, @@ -274,7 +321,9 @@ describe('Trace', () => { }); it('server', () => { (span as any).kind = SpanKind.SERVER; - const exportRequest = createExportTraceServiceRequest([span], true); + const exportRequest = createExportTraceServiceRequest([span], { + useHex: true, + }); assert.ok(exportRequest); assert.strictEqual( exportRequest.resourceSpans?.[0].scopeSpans[0].spans?.[0].kind, @@ -283,7 +332,9 @@ describe('Trace', () => { }); it('unspecified', () => { (span as any).kind = undefined; - const exportRequest = createExportTraceServiceRequest([span], true); + const exportRequest = createExportTraceServiceRequest([span], { + useHex: true, + }); assert.ok(exportRequest); assert.strictEqual( exportRequest.resourceSpans?.[0].scopeSpans[0].spans?.[0].kind,