@bugsnag/core-performance
Version:
Core performance client
146 lines (143 loc) • 4.99 kB
JavaScript
import { millisecondsToNanoseconds } from './clock.js';
import { SpanEvents } from './events.js';
import traceIdToSamplingRate from './trace-id-to-sampling-rate.js';
import { isTime, isParentContext, isBoolean } 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 };