UNPKG

@bugsnag/core-performance

Version:
146 lines (143 loc) 4.99 kB
import { millisecondsToNanoseconds } from './clock.js'; import { SpanEvents } from './events.js'; import traceIdToSamplingRate from './trace-id-to-sampling-rate.js'; import { isBoolean, isParentContext, isTime } from './validation.js'; const HOUR_IN_MILLISECONDS = 60 * 60 * 1000; function spanToJson(span, clock) { return { name: span.name, kind: span.kind, spanId: span.id, traceId: span.traceId, parentSpanId: span.parentSpanId, ...(span.attributes.droppedAttributesCount > 0 ? { droppedAttributesCount: span.attributes.droppedAttributesCount } : {}), startTimeUnixNano: clock.toUnixTimestampNanoseconds(span.startTime), endTimeUnixNano: clock.toUnixTimestampNanoseconds(span.endTime), attributes: span.attributes.toJson(), events: span.events.toJson(clock) }; } function spanEndedToSpan(span) { return { get id() { return span.id; }, get traceId() { return span.traceId; }, get samplingRate() { return span.samplingRate; }, get samplingProbability() { return span.samplingProbability.raw; }, get name() { return span.name; }, isValid: () => false, end: () => { }, // no-op setAttribute: (name, value) => { span.attributes.setCustom(name, value); } }; } async function runSpanEndCallbacks(spanEnded, logger, callbacks) { if (!callbacks || callbacks.length === 0) return true; const span = spanEndedToSpan(spanEnded); const callbackStartTime = performance.now(); let shouldSample = true; for (const callback of callbacks) { try { let result = callback(span); // @ts-expect-error result may or may not be a promise if (typeof result.then === 'function') { result = await result; } if (result === false) { shouldSample = false; break; } } catch (err) { logger.error('Error in onSpanEnd callback: ' + err); } } if (shouldSample) { const duration = millisecondsToNanoseconds(performance.now() - callbackStartTime); span.setAttribute('bugsnag.span.callbacks_duration', duration); } return shouldSample; } class SpanInternal { constructor(id, traceId, name, startTime, attributes, clock, samplingProbability, parentSpanId) { this.kind = 3 /* Kind.Client */; // TODO: How do we define the initial Kind? this.events = new SpanEvents(); this.id = id; this.traceId = traceId; this.parentSpanId = parentSpanId; this.name = name; this.startTime = startTime; this.attributes = attributes; this.samplingRate = traceIdToSamplingRate(this.traceId); this.samplingProbability = samplingProbability; this.clock = clock; } addEvent(name, time) { this.events.add(name, time); } setAttribute(name, value) { this.attributes.set(name, value); } setCustomAttribute(name, value) { this.attributes.setCustom(name, value); } end(endTime, samplingProbability) { this.endTime = endTime; let _samplingProbability = samplingProbability; this.attributes.set('bugsnag.sampling.p', _samplingProbability.raw); return { id: this.id, name: this.name, kind: this.kind, traceId: this.traceId, startTime: this.startTime, attributes: this.attributes, events: this.events, samplingRate: this.samplingRate, endTime, get samplingProbability() { return _samplingProbability; }, set samplingProbability(samplingProbability) { _samplingProbability = samplingProbability; this.attributes.set('bugsnag.sampling.p', _samplingProbability.raw); }, parentSpanId: this.parentSpanId }; } isValid() { return this.endTime === undefined && this.startTime > (this.clock.now() - HOUR_IN_MILLISECONDS); } } const coreSpanOptionSchema = { startTime: { message: 'should be a number or Date', getDefaultValue: () => undefined, validate: isTime }, parentContext: { message: 'should be a ParentContext', getDefaultValue: () => undefined, validate: (value) => value === null || isParentContext(value) }, makeCurrentContext: { message: 'should be true|false', getDefaultValue: () => undefined, validate: isBoolean }, isFirstClass: { message: 'should be true|false', getDefaultValue: () => undefined, validate: isBoolean } }; export { SpanInternal, coreSpanOptionSchema, runSpanEndCallbacks, spanEndedToSpan, spanToJson };