/* This file contains all the types and logic related to the W3C Trace Context standard. https://www.w3.org/TR/trace-context/ */ import log from "../log/mod"; import { runtime } from "../jsruntime"; type TraceVersion = "00"; type TraceFlag = "00" | "01"; type TraceIDStr = Lowercase<`${string & { length: 32 }}`>; type SpanIDStr = Lowercase<`${string & { length: 16 }}`>; /** * Traceparent is the header used to propagate trace context between services. It is formatted as follows: * * --- * * So for example: * 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01 */ export type TraceParent = `${TraceVersion}-${TraceIDStr}-${SpanIDStr}-${TraceFlag}`; /** * Fixed 16-byte array for the 128-bit trace ID. */ export type TraceID = Uint8Array; /** * Fixed 8-byte array for the 64-bit span ID. */ export type SpanID = Uint8Array; /** * The HTTP Header used to propagate trace context between services. */ export const TraceParentHeader = "traceparent"; /** * The HTTP Header used to propagate trace state between services. */ export const TraceStateHeader = "tracestate"; /** * Generate a new trace ID. */ export function newTraceID(): TraceID { const id = new Uint8Array(16); runtime().getRandomValues(id); return id; } /** * Generate a new span ID. */ export function newSpanID(): SpanID { const id = new Uint8Array(8); runtime().getRandomValues(id); return id; } /** * Parses the traceparent header, returning the trace context if the header is valid, or undefined if the header is * invalid. */ export function parseTraceParent( traceParent: string, ): [TraceID | undefined, SpanID | undefined] { try { const totalLength = 55; const traceIDSep = 35; const spanIDSep = 51; // Check that the header is the correct length and format. if ( traceParent.length != totalLength || // The header must be exactly 55 characters long. traceParent[0] !== "0" || traceParent[1] !== "0" || traceParent[2] !== "-" || // We only support version 00 traceParent[traceIDSep] !== "-" || traceParent[spanIDSep] !== "-" ) { return [undefined, undefined]; } // Parse the trace ID. const traceID = new Uint8Array(16); for (let i = 0; i < 16; i++) { const byte = parseInt(traceParent.substring(3 + i * 2, 5 + i * 2), 16); if (isNaN(byte)) { return [undefined, undefined]; } traceID[i] = byte; } // Parse the span ID. const spanID = new Uint8Array(8); for (let i = 0; i < 8; i++) { const byte = parseInt(traceParent.substring(36 + i * 2, 38 + i * 2), 16); if (isNaN(byte)) { return [undefined, undefined]; } spanID[i] = byte; } return [traceID, spanID]; } catch (e: unknown) { log.error(e, "failed to parse traceparent header"); return [undefined, undefined]; } } /** * Generates a traceparent header from the given trace ID and span ID. */ export function traceParent(traceID: TraceID, spanID: SpanID): TraceParent { const traceIDStr = traceID.reduce( (str, byte) => str + byte.toString(16).padStart(2, "0"), "", ) as unknown as TraceIDStr; const spanIDStr = spanID.reduce( (str, byte) => str + byte.toString(16).padStart(2, "0"), "", ) as unknown as SpanIDStr; return `00-${traceIDStr}-${spanIDStr}-01`; }