src/vs/platform/otel/node/otlp/otlpJsonDecode.ts
245 LOC · 224 covered · 21 uncovered · 69 ranges · 38 concepts · 16 introducers · 20 tests
File neighbourhood
The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.
Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file
In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.
Graph controls are ready.
Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.
/*---------------------------------------------------------------------------------------------
otlpJsonDecode.ts ×9
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ICompletedSpanData, ISpanEventRecord, SpanStatusCode } from '../../common/spanData.js';
import {
IOtlpAnyValue,
IOtlpEvent,
IOtlpExportTraceServiceRequest,
IOtlpKeyValue,
IOtlpSpan,
OtlpStatusCode,
} from './otlpJsonTypes.js';
type AttrValue = string | number | boolean | string[];
const HEX_RE = /^[0-9a-fA-F]+$/;
const ALL_ZERO_TRACE_ID = '00000000000000000000000000000000';
const ALL_ZERO_SPAN_ID = '0000000000000000';
/**
* Decode an OTLP/HTTP JSON `ExportTraceServiceRequest` into flat
* {@link ICompletedSpanData} records.
*
* The decoder is intentionally lenient: malformed individual spans are skipped
* and reported via `partialFailures`, so the receiver can return a 200 with
* `partial_success` populated (per OTLP/HTTP spec) rather than failing the
* whole batch.
*
* Resource attributes (e.g. `service.name`) are merged into each span's
* attribute map. Span-level attributes win on key collision. This keeps the
* SQLite schema flat and avoids a separate resource table.
*
* Unknown OTLP fields (e.g. `links`, `traceState`) are dropped silently.
*/
export interface IDecodeResult {
readonly spans: readonly ICompletedSpanData[];
readonly rejected: number;
readonly errors: readonly string[];
}
export function decodeExportTraceRequest(request: IOtlpExportTraceServiceRequest | undefined): IDecodeResult {
}
const spans: ICompletedSpanData[] = [];
const errors: string[] = [];
let rejected = 0;
for (const rs of request.resourceSpans) {
continue;
}
for (const ss of rs.scopeSpans ?? []) {
if (!ss) {
continue;
}
try {
const decoded = decodeSpan(span, resourceAttrs);
if (decoded) {
} else {
rejected++;
}
errors.push(e instanceof Error ? e.message : String(e));
}
}
}
return { spans, rejected, errors };
}
function decodeSpan(span: IOtlpSpan | undefined, resourceAttrs: Record<string, AttrValue>): ICompletedSpanData | undefined {
otlpJsonDecode.ts ×14
if (!span) {
return undefined;
}
const traceId = (span.traceId ?? '').toLowerCase();
const spanId = (span.spanId ?? '').toLowerCase();
if (!isValidHex(traceId, 32) || traceId === ALL_ZERO_TRACE_ID) {
}
}
let parentSpanId: string | undefined;
if (span.parentSpanId) {
if (isValidHex(ps, 16) && ps !== ALL_ZERO_SPAN_ID) {
parentSpanId = ps;
}
}
const startTime = nanosToMillis(span.startTimeUnixNano);
const endTime = nanosToMillis(span.endTimeUnixNano);
}
const attributes: Record<string, AttrValue> = { ...resourceAttrs };
}
const events: ISpanEventRecord[] = [];
if (decoded) {
events.push(decoded);
}
}
return {
name: span.name ?? '',
traceId,
spanId,
parentSpanId,
startTime,
endTime,
status,
attributes,
events,
};
}
function decodeEvent(ev: IOtlpEvent | undefined): ISpanEventRecord | undefined {
otlpJsonDecode.ts ×8
if (!ev) {
return undefined;
}
if (timestamp === undefined) {
return undefined;
}
for (const kv of ev.attributes ?? []) {
setAttribute(attributes, kv);
}
return {
name: ev.name ?? '',
timestamp,
attributes: Object.keys(attributes).length > 0 ? attributes : undefined,
};
}
function decodeStatus(code: OtlpStatusCode | undefined, message: string | undefined): ICompletedSpanData['status'] {
otlpJsonDecode.ts ×12
switch (code) {
case OtlpStatusCode.OK:
default:
}
function decodeAttributes(kvs: readonly IOtlpKeyValue[] | undefined): Record<string, AttrValue> {
otlpJsonDecode.ts ×14
const out: Record<string, AttrValue> = {};
if (!kvs) {
}
setAttribute(out, kv);
}
return out;
}
function setAttribute(target: Record<string, AttrValue>, kv: IOtlpKeyValue | undefined): void {
otlpJsonDecode.ts ×7
if (!kv || typeof kv.key !== 'string' || kv.key.length === 0) {
return;
}
if (value !== undefined) {
target[kv.key] = value;
}
}
function decodeAnyValue(v: IOtlpAnyValue | undefined): AttrValue | undefined {
otlpJsonDecode.ts ×7
if (!v) {
return undefined;
}
return v.stringValue;
}
}
// intValue is a stringified int64; precision beyond Number.MAX_SAFE_INTEGER is lost
const n = typeof v.intValue === 'string' ? Number(v.intValue) : v.intValue;
return Number.isFinite(n) ? n : undefined;
}
if (typeof v.doubleValue === 'number') {
}
// Only flat arrays of strings are first-class in ICompletedSpanData.attributes.
otlpJsonDecode.ts ×3
// For mixed/numeric/nested arrays, fall back to JSON for fidelity.
const items = v.arrayValue.values.map(decodeAnyValue);
if (items.every((x): x is string => typeof x === 'string')) {
}
}
const obj: Record<string, AttrValue | undefined> = {};
for (const kv of v.kvlistValue.values) {
if (kv && typeof kv.key === 'string') {
obj[kv.key] = decodeAnyValue(kv.value);
}
}
return JSON.stringify(obj);
}
if (typeof v.bytesValue === 'string') {
return v.bytesValue;
}
return undefined;
}
if (s === undefined || s === '' || s === '0') {
}
// Avoid BigInt churn: parse as decimal string, truncate the last 6 digits (ns → ms).
otlpJsonDecode.ts ×12
// `s` is a non-negative integer per the spec.
const trimmed = s.length <= 6 ? '0' : s.slice(0, -6);
const n = Number(trimmed);
return Number.isFinite(n) ? n : undefined;
}
return s.length === len && HEX_RE.test(s);
}