Skip to content
82 changes: 65 additions & 17 deletions packages/core/src/rum/DdRum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,16 @@ import type { ErrorEventMapper } from './eventMappers/errorEventMapper';
import { generateErrorEventMapper } from './eventMappers/errorEventMapper';
import type { ResourceEventMapper } from './eventMappers/resourceEventMapper';
import { generateResourceEventMapper } from './eventMappers/resourceEventMapper';
import type {
SpanId,
TraceId
} from './instrumentation/resourceTracking/distributedTracing/TracingIdentifier';
import {
TracingIdFormat,
TracingIdType,
TracingIdentifier
} from './instrumentation/resourceTracking/distributedTracing/TracingIdentifier';
import { PropagatorType } from './types';
import type {
ErrorSource,
DdRumType,
Expand Down Expand Up @@ -230,26 +235,69 @@ class DdRumWrapper implements DdRumType {
);
};

generateUUID = (type: TracingIdType): string => {
switch (type) {
case TracingIdType.trace:
return TracingIdentifier.createTraceId().toString(
TracingIdFormat.paddedHex
);
case TracingIdType.span:
return TracingIdentifier.createSpanId().toString(
TracingIdFormat.decimal
);
default:
console.warn(
`Unsupported tracing ID type '${type}' for generateUUID. Falling back to 64 bit Span ID.`
);
return TracingIdentifier.createSpanId().toString(
TracingIdFormat.decimal
);
generateUUID = (
type: TracingIdType,
propagator: PropagatorType
): {
resource: string;
contextPropagation: string;
} => {
if (!Object.values(TracingIdType).includes(type)) {
console.warn(
`Unsupported tracing ID type '${type}' provided to generateUUID. Defaulting to 'span' type.`
);
}

if (!Object.values(PropagatorType).includes(propagator)) {
console.warn(
`Unsupported propagator '${propagator}' provided to generateUUID. Defaulting to 'Tracecontext' propagator.`
);
}

const uuid = this.createUUID(type);

return {
resource: this.formatUUIDForResource(uuid, type),
contextPropagation: this.formatUUIDForContextPropagation(
uuid,
type,
propagator
)
};
};

private createUUID(type: TracingIdType): TraceId | SpanId {
return type === TracingIdType.trace
? TracingIdentifier.createTraceId()
: TracingIdentifier.createSpanId();
}

private formatUUIDForResource(
id: TraceId | SpanId,
type: TracingIdType
): string {
if (type === TracingIdType.trace) {
return id.toString(TracingIdFormat.paddedHex);
}

return id.toString(TracingIdFormat.decimal);
}

private formatUUIDForContextPropagation(
id: TraceId | SpanId,
type: TracingIdType,
propagator: PropagatorType
): string {
if (propagator === PropagatorType.DATADOG) {
return id.toString(
type === TracingIdType.trace
? TracingIdFormat.lowDecimal
: TracingIdFormat.decimal
);
}
return id.toString(TracingIdFormat.paddedHex);
}

addError = (
message: string,
source: ErrorSource,
Expand Down
235 changes: 204 additions & 31 deletions packages/core/src/rum/__tests__/DdRum.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -451,37 +451,6 @@ describe('DdRum', () => {
});
});

describe('DdRum.generateUUID', () => {
it('generates a valid trace id in paddedHex format', () => {
const uuid = DdRum.generateUUID(TracingIdType.trace);

expect(uuid).toBeDefined(); // Ensure the value is defined
expect(BigInt(uuid, 16).greater(BigInt(0))).toBe(true); // Ensure it's a valid positive number
expect(TracingIdentifierUtils.isWithin128Bits(uuid)).toBe(true); // Ensure the value is within 128 bits
expect(uuid).toMatch(/^[0-9a-f]{32}$/); // Ensure the value is in paddedHex format
});

it('generates a valid span id in decimal format', () => {
const uuid = DdRum.generateUUID(TracingIdType.span);

expect(uuid).toBeDefined(); // Ensure the value is defined
expect(BigInt(uuid).greater(BigInt(0))).toBe(true); // Ensure it's a valid positive number
expect(TracingIdentifierUtils.isWithin64Bits(uuid)).toBe(true); // Ensure the value is within 64 bits
expect(uuid).toMatch(/^[0-9]+$/); // Ensure the value contains only decimal digits
});

it('falls back to 64 bit span id when wrong tracingIdType is passed', () => {
const uuid = DdRum.generateUUID(
('wrong' as unknown) as TracingIdType
);

expect(uuid).toBeDefined(); // Ensure the value is defined
expect(BigInt(uuid).greater(BigInt(0))).toBe(true); // Ensure it's a valid positive number
expect(TracingIdentifierUtils.isWithin64Bits(uuid)).toBe(true); // Ensure the value is within 64 bits
expect(uuid).toMatch(/^[0-9]+$/); // Ensure the value contains only decimal digits
});
});

describe('DdRum.addAction', () => {
test('uses given context when context is valid', async () => {
const context = {
Expand Down Expand Up @@ -1032,4 +1001,208 @@ describe('DdRum', () => {
expect(PropagatorType.TRACECONTEXT).toBe('tracecontext');
});
});
describe('DdRum.generateUUID', () => {
describe('Types and Enums', () => {
it('exposes PropagatorType enum', () => {
expect(PropagatorType).toBeDefined();
});

it('exposes TracingIdType enum', () => {
expect(TracingIdType).toBeDefined();
});
});

describe('Default behavior with invalid input', () => {
it('generates 64-bit span id when passed invalid tracing id type', () => {
const randomPropagatorType = Object.values(PropagatorType);

for (let i = 0; i < 100; i++) {
const tempContextPropagator =
randomPropagatorType[
Math.floor(
Math.random() * randomPropagatorType.length
)
];

const uuid = DdRum.generateUUID(
('wrong' as unknown) as TracingIdType,
tempContextPropagator
);

const uuidPropagator = uuid.contextPropagation;

expect(uuidPropagator).toBeDefined();

if (tempContextPropagator === PropagatorType.DATADOG) {
expect(
TracingIdentifierUtils.isWithin64Bits(
uuidPropagator
)
).toBe(true);
} else {
expect(uuidPropagator).toMatch(/^[0-9a-f]{16}$/);
expect(
TracingIdentifierUtils.isWithin64Bits(
uuidPropagator,
16
)
).toBe(true);
}

const uuidDecimal = uuid.resource;
expect(uuidDecimal).toBeDefined();
expect(
TracingIdentifierUtils.isWithin64Bits(uuidDecimal)
).toBe(true);
}
});
});

describe('Trace ID generation', () => {
it('generates valid 128-bit trace ID in decimal format for Datadog propagator', () => {
let iterations = 100;
while (iterations-- > 0) {
const uuidObject = DdRum.generateUUID(
TracingIdType.trace,
PropagatorType.DATADOG
);

const uuidStrLow64Propagator =
uuidObject.contextPropagation;
expect(uuidStrLow64Propagator).toBeDefined();
expect(
TracingIdentifierUtils.isWithin64Bits(
uuidStrLow64Propagator
)
).toBe(true);

const uuidPaddedHexResource = uuidObject.resource;
expect(uuidPaddedHexResource).toBeDefined();
expect(uuidPaddedHexResource).toMatch(
/^[0-9a-f]{8}[0]{8}[0-9a-f]{16}$/
);
expect(
TracingIdentifierUtils.isWithin128Bits(
uuidPaddedHexResource,
16
)
).toBe(true);
}
});

it('generates valid 128-bit trace ID in hex format for B3, B3multi and TraceContext', () => {
let iterations = 100;

const nonDatadogPropagators = Object.values(
PropagatorType
).filter(type => type !== PropagatorType.DATADOG);

while (iterations-- > 0) {
const tempPropagatorType =
nonDatadogPropagators[
Math.floor(
Math.random() * nonDatadogPropagators.length
)
];

const uuidObject = DdRum.generateUUID(
TracingIdType.trace,
tempPropagatorType
);

const uuidPaddedHexPropagation =
uuidObject.contextPropagation;
expect(uuidPaddedHexPropagation).toBeDefined();
expect(uuidPaddedHexPropagation).toMatch(
/^[0-9a-f]{8}[0]{8}[0-9a-f]{16}$/
);
expect(
TracingIdentifierUtils.isWithin128Bits(
uuidPaddedHexPropagation,
16
)
).toBe(true);

const uuidPaddedHexResource = uuidObject.resource;
expect(uuidPaddedHexResource).toBeDefined();
expect(uuidPaddedHexResource).toMatch(
/^[0-9a-f]{8}[0]{8}[0-9a-f]{16}$/
);
expect(
TracingIdentifierUtils.isWithin128Bits(
uuidPaddedHexResource,
16
)
).toBe(true);
}
});
});

describe('Span ID generation', () => {
it('generates valid 64-bit span ID in decimal format for Datadog propagator', () => {
let iterations = 100;
while (iterations-- > 0) {
const uuidObject = DdRum.generateUUID(
TracingIdType.span,
PropagatorType.DATADOG
);

const uuid6StrDecimal64Propagator = uuidObject.resource;
expect(uuid6StrDecimal64Propagator).toBeDefined();
expect(
TracingIdentifierUtils.isWithin64Bits(
uuid6StrDecimal64Propagator
)
).toBe(true);

const uuid6StrDecimal64Resource = uuidObject.resource;
expect(uuid6StrDecimal64Resource).toBeDefined();
expect(
TracingIdentifierUtils.isWithin64Bits(
uuid6StrDecimal64Resource
)
).toBe(true);
}
});

it('generates valid 64-bit span ID in hex format for B3, B3multi and TraceContext', () => {
let iterations = 100;
const nonDatadogPropagators = Object.values(
PropagatorType
).filter(type => type !== PropagatorType.DATADOG);

while (iterations-- > 0) {
const tempPropagatorType =
nonDatadogPropagators[
Math.floor(
Math.random() * nonDatadogPropagators.length
)
];

const uuidObject = DdRum.generateUUID(
TracingIdType.span,
tempPropagatorType
);

const uuidPaddedHexPropagation =
uuidObject.contextPropagation;
expect(uuidPaddedHexPropagation).toMatch(/^[0-9a-f]{16}$/);
expect(
TracingIdentifierUtils.isWithin64Bits(
uuidPaddedHexPropagation,
16
)
).toBe(true);

const uuidDecimalResource = uuidObject.resource;
expect(uuidDecimalResource).toBeDefined();
expect(
TracingIdentifierUtils.isWithin64Bits(
uuidDecimalResource
)
).toBe(true);
}
});
});
});
});
7 changes: 6 additions & 1 deletion packages/core/src/rum/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,13 @@ export type DdRumType = {
/**
* Generate a new unique tracing ID.
* @param type - The type of the tracing ID to generate. Trace (128-bit) or Span (64-bit).
* @param propragator - The propagator to use for the generated tracing ID. Datadog, TraceContext, B3 or B3Multi.
* @returns The generated tracing ID with the resource and context propagation values.
*/
generateUUID(type: TracingIdType): string;
generateUUID(
type: TracingIdType,
propagator: PropagatorType
): { resource: string; contextPropagation: string };

/**
* Add a RUM Error.
Expand Down
Loading