Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: use Uint8Array for ids #786

Closed
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/opentelemetry-api/src/trace/NoopSpan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ import { SpanContext } from './span_context';
import { Status } from './status';
import { TraceFlags } from './trace_flags';

export const INVALID_TRACE_ID = '0';
export const INVALID_SPAN_ID = '0';
export const INVALID_TRACE_ID = new Uint8Array(16);
export const INVALID_SPAN_ID = new Uint8Array(8);
const INVALID_SPAN_CONTEXT: SpanContext = {
traceId: INVALID_TRACE_ID,
spanId: INVALID_SPAN_ID,
Expand Down
10 changes: 4 additions & 6 deletions packages/opentelemetry-api/src/trace/span_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,14 @@ export interface SpanContext {
/**
* The ID of the trace that this span belongs to. It is worldwide unique
* with practically sufficient probability by being made as 16 randomly
* generated bytes, encoded as a 32 lowercase hex characters corresponding to
* 128 bits.
* generated bytes.
*/
traceId: string;
traceId: Uint8Array;
/**
* The ID of the Span. It is globally unique with practically sufficient
* probability by being made as 8 randomly generated bytes, encoded as a 16
* lowercase hex characters corresponding to 64 bits.
* probability by being made as 8 randomly generated bytes.
*/
spanId: string;
spanId: Uint8Array;
/**
* Only true if the SpanContext was propagated from a remote parent.
*/
Expand Down
4 changes: 2 additions & 2 deletions packages/opentelemetry-api/test/api/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ describe('API', () => {

describe('GlobalTracerProvider', () => {
const spanContext = {
traceId: 'd4cda95b652f4a1592b449d5929fda1b',
spanId: '6e0c63257de34c92',
traceId: new Uint8Array([0xd4, 0xcd, 0xa9, 0x5b, 0x65, 0x2f, 0x4a, 0x15, 0x92, 0xb4, 0x49, 0xd5, 0x92, 0x9f, 0xda, 0x1b]),
spanId: new Uint8Array([0x6e, 0x0c, 0x63, 0x25, 0x7d, 0xe3, 0x4c, 0x92]),
traceFlags: TraceFlags.UNSAMPLED,
};
const dummySpan = new NoopSpan(spanContext);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ describe('NoopMeter', () => {
1,
{ key: { value: 'value' } },
{
traceId: 'a3cda95b652f4a1592b449d5929fda1b',
spanId: '5e0c63257de34c92',
traceId: new Uint8Array([0xa3, 0xcd, 0xa9, 0x5b, 0x65, 0x2f, 0x4a, 0x15, 0x92, 0xb4, 0x49, 0xd5, 0x92, 0x9f, 0xda, 0x1b]),
spanId: new Uint8Array([0x5e, 0x0c, 0x63, 0x25, 0x7d, 0xe3, 0x4c, 0x92]),
}
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ describe('NoopSpan', () => {
span.addEvent('sent', { id: '42', key: 'value' });

span.addLink({
traceId: 'd4cda95b652f4a1592b449d5929fda1b',
spanId: '6e0c63257de34c92',
traceId: new Uint8Array([0xd4, 0xcd, 0xa9, 0x5b, 0x65, 0x2f, 0x4a, 0x15, 0x92, 0xb4, 0x49, 0xd5, 0x92, 0x9f, 0xda, 0x1b]),
spanId: new Uint8Array([0x6e, 0x0c, 0x63, 0x25, 0x7d, 0xe3, 0x4c, 0x92]),
});
span.addLink(
{
traceId: 'd4cda95b652f4a1592b449d5929fda1b',
spanId: '6e0c63257de34c92',
traceId: new Uint8Array([0xd4, 0xcd, 0xa9, 0x5b, 0x65, 0x2f, 0x4a, 0x15, 0x92, 0xb4, 0x49, 0xd5, 0x92, 0x9f, 0xda, 0x1b]),
spanId: new Uint8Array([0x6e, 0x0c, 0x63, 0x25, 0x7d, 0xe3, 0x4c, 0x92]),
},
{ id: '42', key: 'value' }
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@
*/

import * as assert from 'assert';
import { NoopTracer, NOOP_SPAN, SpanKind } from '../../src';
import { NoopTracer, NOOP_SPAN, SpanKind, INVALID_TRACE_ID, INVALID_SPAN_ID } from '../../src';

describe('NoopTracer', () => {
it('should not crash', () => {
const spanContext = { traceId: '', spanId: '' };
const spanContext = { traceId: INVALID_TRACE_ID, spanId: INVALID_SPAN_ID };
const tracer = new NoopTracer();

assert.deepStrictEqual(tracer.startSpan('span-name'), NOOP_SPAN);
Expand Down
33 changes: 13 additions & 20 deletions packages/opentelemetry-core/src/context/propagation/B3Format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,12 @@ import {
SpanContext,
TraceFlags,
} from '@opentelemetry/api';
import { spanIdIsValid, traceIdIsValid } from '../../trace/spancontext-utils';
import { hexToId, idToHex } from '../../platform';

export const X_B3_TRACE_ID = 'x-b3-traceid';
export const X_B3_SPAN_ID = 'x-b3-spanid';
export const X_B3_SAMPLED = 'x-b3-sampled';
const VALID_TRACEID_REGEX = /^[0-9a-f]{32}$/i;
const VALID_SPANID_REGEX = /^[0-9a-f]{16}$/i;
const INVALID_ID_REGEX = /^0+$/i;

function isValidTraceId(traceId: string): boolean {
return VALID_TRACEID_REGEX.test(traceId) && !INVALID_ID_REGEX.test(traceId);
}

function isValidSpanId(spanId: string): boolean {
return VALID_SPANID_REGEX.test(spanId) && !INVALID_ID_REGEX.test(spanId);
}

/**
* Propagator for the B3 HTTP header format.
Expand All @@ -43,11 +34,11 @@ function isValidSpanId(spanId: string): boolean {
export class B3Format implements HttpTextFormat {
inject(spanContext: SpanContext, format: string, carrier: Carrier): void {
if (
isValidTraceId(spanContext.traceId) &&
isValidSpanId(spanContext.spanId)
traceIdIsValid(spanContext.traceId) &&
spanIdIsValid(spanContext.spanId)
) {
carrier[X_B3_TRACE_ID] = spanContext.traceId;
carrier[X_B3_SPAN_ID] = spanContext.spanId;
carrier[X_B3_TRACE_ID] = idToHex(spanContext.traceId);
carrier[X_B3_SPAN_ID] = idToHex(spanContext.spanId);

// We set the header only if there is an existing sampling decision.
// Otherwise we will omit it => Absent.
Expand All @@ -62,15 +53,17 @@ export class B3Format implements HttpTextFormat {
const spanIdHeader = carrier[X_B3_SPAN_ID];
const sampledHeader = carrier[X_B3_SAMPLED];
if (!traceIdHeader || !spanIdHeader) return null;
const traceId = Array.isArray(traceIdHeader)
? traceIdHeader[0]
: traceIdHeader;
const spanId = Array.isArray(spanIdHeader) ? spanIdHeader[0] : spanIdHeader;
const traceId = hexToId(
Array.isArray(traceIdHeader) ? traceIdHeader[0] : traceIdHeader
);
const spanId = hexToId(
Array.isArray(spanIdHeader) ? spanIdHeader[0] : spanIdHeader
);
const options = Array.isArray(sampledHeader)
? sampledHeader[0]
: sampledHeader;

if (isValidTraceId(traceId) && isValidSpanId(spanId)) {
if (traceIdIsValid(traceId) && spanIdIsValid(spanId)) {
return {
traceId,
spanId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,26 +55,19 @@ export class BinaryTraceContext implements BinaryFormat {
* | `---------------------------------- traceID field ID (0)
* `------------------------------------ version (0)
*/
const traceId = spanContext.traceId;
const spanId = spanContext.spanId;
const traceId = new Uint8Array(spanContext.traceId);
const spanId = new Uint8Array(spanContext.spanId);
const buf = new Uint8Array(FORMAT_LENGTH);
let j = TRACE_ID_OFFSET;
for (let i = TRACE_ID_OFFSET; i < SPAN_ID_FIELD_ID_OFFSET; i++) {
// tslint:disable-next-line:ban Needed to parse hexadecimal.
buf[j++] = parseInt(traceId.substr((i - TRACE_ID_OFFSET) * 2, 2), 16);
}
buf[j++] = SPAN_ID_FIELD_ID;
for (let i = SPAN_ID_OFFSET; i < TRACE_OPTION_FIELD_ID_OFFSET; i++) {
// tslint:disable-next-line:ban Needed to parse hexadecimal.
buf[j++] = parseInt(spanId.substr((i - SPAN_ID_OFFSET) * 2, 2), 16);
}
buf[j++] = TRACE_OPTION_FIELD_ID;
buf[j++] = Number(spanContext.traceFlags) || TraceFlags.UNSAMPLED;
buf.set(traceId, TRACE_ID_OFFSET);
buf[SPAN_ID_FIELD_ID_OFFSET] = SPAN_ID_FIELD_ID;
buf.set(spanId, SPAN_ID_OFFSET);
buf[TRACE_OPTION_FIELD_ID_OFFSET] = TRACE_OPTION_FIELD_ID;
buf[TRACE_OPTIONS_OFFSET] =
Number(spanContext.traceFlags) || TraceFlags.UNSAMPLED;
return buf;
}

fromBytes(buf: Uint8Array): SpanContext | null {
const result: SpanContext = { traceId: '', spanId: '' };
// Length must be 29.
if (buf.length !== FORMAT_LENGTH) return null;
// Check version and field numbers.
Expand All @@ -87,27 +80,16 @@ export class BinaryTraceContext implements BinaryFormat {
return null;
}

const result: SpanContext = {
traceId: new Uint8Array(TRACE_ID_SIZE),
spanId: new Uint8Array(SPAN_ID_SIZE),
};
result.isRemote = true;

// See serializeSpanContext for byte offsets.
result.traceId = toHex(buf.slice(TRACE_ID_OFFSET, SPAN_ID_FIELD_ID_OFFSET));
result.spanId = toHex(
buf.slice(SPAN_ID_OFFSET, TRACE_OPTION_FIELD_ID_OFFSET)
);
result.traceId = buf.slice(TRACE_ID_OFFSET, SPAN_ID_FIELD_ID_OFFSET);
result.spanId = buf.slice(SPAN_ID_OFFSET, TRACE_OPTION_FIELD_ID_OFFSET);
result.traceFlags = buf[TRACE_OPTIONS_OFFSET];
return result;
}
}

function toHex(buff: Uint8Array) {
let out = '';
for (let i = 0; i < buff.length; ++i) {
const n = buff[i];
if (n < 16) {
out += '0' + n.toString(16);
} else {
out += n.toString(16);
}
}
return out;
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
TraceFlags,
} from '@opentelemetry/api';
import { TraceState } from '../../trace/TraceState';
import { hexToId, idToHex } from '../../platform';

export const TRACE_PARENT_HEADER = 'traceparent';
export const TRACE_STATE_HEADER = 'tracestate';
Expand Down Expand Up @@ -48,8 +49,8 @@ export function parseTraceParent(traceParent: string): SpanContext | null {
}

return {
traceId: match[1],
spanId: match[2],
traceId: hexToId(match[1]),
spanId: hexToId(match[2]),
traceFlags: parseInt(match[3], 16),
};
}
Expand All @@ -62,9 +63,9 @@ export function parseTraceParent(traceParent: string): SpanContext | null {
*/
export class HttpTraceContext implements HttpTextFormat {
inject(spanContext: SpanContext, format: string, carrier: Carrier) {
const traceParent = `${VERSION}-${spanContext.traceId}-${
const traceParent = `${VERSION}-${idToHex(spanContext.traceId)}-${idToHex(
spanContext.spanId
}-0${Number(spanContext.traceFlags || TraceFlags.UNSAMPLED).toString(16)}`;
)}-0${Number(spanContext.traceFlags || TraceFlags.UNSAMPLED).toString(16)}`;

carrier[TRACE_PARENT_HEADER] = traceParent;
if (spanContext.traceState) {
Expand Down
68 changes: 55 additions & 13 deletions packages/opentelemetry-core/src/platform/browser/id.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,66 @@ declare type WindowWithMsCrypto = Window & {
const cryptoLib = window.crypto || (window as WindowWithMsCrypto).msCrypto;

const SPAN_ID_BYTES = 8;
const spanBytesArray = new Uint8Array(SPAN_ID_BYTES);
const TRACE_ID_BYTES = 16;

/** Returns a random 16-byte trace ID formatted as a 32-char hex string. */
export function randomTraceId(): string {
return randomSpanId() + randomSpanId();
const byteToHex: string[] = [];
for (let n = 0; n <= 0xff; ++n) {
const hexOctet = n.toString(16).padStart(2, '0');
byteToHex.push(hexOctet);
}

/** Returns a random 8-byte span ID formatted as a 16-char hex string. */
export function randomSpanId(): string {
let spanId = '';
cryptoLib.getRandomValues(spanBytesArray);
for (let i = 0; i < SPAN_ID_BYTES; i++) {
const hexStr = spanBytesArray[i].toString(16);
/**
* Encodes an Uint8Array into a hex string.
* @param buf the array buffer to encode
*/
export function idToHex(buf: Uint8Array): string {
const hex = new Array(buf.length);
for (let i = 0; i < buf.length; ++i) {
hex.push(byteToHex[buf[i]]);
}
return hex.join('');
}

// Zero pad bytes whose hex values are single digit.
if (hexStr.length === 1) spanId += '0';
/**
* Converts hex encoded string into an Uint8Array
* @param s the string to convert
*/
export function hexToId(s: string): Uint8Array {
const cnt = s.length / 2;
const buf = new Uint8Array(cnt);
for (let i = 0; i < cnt; i++) {
buf[i] = parseInt(s.substring(2 * i, 2 * i + 2), 16);
}
return buf;
}

spanId += hexStr;
/*
* Compare if two id are equal.
* @param id1 first id to compare
* @param id2 second id to compare
*/
export function idsEquals(id1: Uint8Array, id2: Uint8Array): boolean {
if (id1.byteLength !== id2.byteLength) {
return false;
}
for (let i = 0; i < id1.byteLength; i++) {
if (id1[i] !== id2[i]) {
return false;
}
}
return true;
}

/** Returns a random 16-byte trace ID. */
export function randomTraceId(): Uint8Array {
const traceId = new Uint8Array(TRACE_ID_BYTES);
cryptoLib.getRandomValues(traceId);
return traceId;
}

/** Returns a random 8-byte span ID. */
export function randomSpanId(): Uint8Array {
let spanId = new Uint8Array(SPAN_ID_BYTES);
cryptoLib.getRandomValues(spanId);
return spanId;
}
43 changes: 35 additions & 8 deletions packages/opentelemetry-core/src/platform/node/id.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,46 @@
import * as crypto from 'crypto';

const SPAN_ID_BYTES = 8;
const TRACE_ID_BYTES = 16;

/**
* Returns a random 16-byte trace ID formatted/encoded as a 32 lowercase hex
* characters corresponding to 128 bits.
* Encodes an Uint8Array into a hex string.
* @param buf the array buffer to encode
*/
export function randomTraceId(): string {
return randomSpanId() + randomSpanId();
export function idToHex(buf: Uint8Array): string {
return Buffer.from(buf).toString('hex');
}

/**
* Returns a random 8-byte span ID formatted/encoded as a 16 lowercase hex
* characters corresponding to 64 bits.
* Converts hex encoded string into an Uint8Array
* @param s the string to convert
*/
export function randomSpanId(): string {
return crypto.randomBytes(SPAN_ID_BYTES).toString('hex');
export function hexToId(s: string): Uint8Array {
const buf = Buffer.from(s, 'hex');
return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
}

/**
* Compare if two id are equal.
* @param id1 first id to compare
* @param id2 second id to compare
*/
export function idsEquals(id1: Uint8Array, id2: Uint8Array): boolean {
return Buffer.compare(id1, id2) === 0;
}

/**
* Returns a random 16-byte trace ID.
*/
export function randomTraceId(): Uint8Array {
const traceId = crypto.randomBytes(TRACE_ID_BYTES);
return new Uint8Array(traceId.buffer, traceId.byteOffset, traceId.byteLength);
}

/**
* Returns a random 8-byte span ID.
*/
export function randomSpanId(): Uint8Array {
const spanId = crypto.randomBytes(SPAN_ID_BYTES);
return new Uint8Array(spanId.buffer, spanId.byteOffset, spanId.byteLength);
}
Loading