@zendesk/retrace
Version:
define and capture Product Operation Traces along with computed metrics with an optional friendly React beacon API
131 lines • 5.7 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.TraceManager = void 0;
const rxjs_1 = require("rxjs");
const ensureMatcherFn_1 = require("./ensureMatcherFn");
const Tracer_1 = require("./Tracer");
/**
* Class representing the centralized trace manager.
* Usually you'll have a single instance of this class in your app.
*/
class TraceManager {
performanceEntryDeduplicationStrategy;
currentTrace = undefined;
// Event subjects for all traces
eventSubjects = {
'trace-start': new rxjs_1.Subject(),
'state-transition': new rxjs_1.Subject(),
'required-span-seen': new rxjs_1.Subject(),
'add-span-to-recording': new rxjs_1.Subject(),
'definition-modified': new rxjs_1.Subject(),
};
get currentTracerContext() {
if (!this.currentTrace)
return undefined;
return this.currentTrace;
}
constructor(configInput) {
this.utilities = {
// by default noop for warnings
reportWarningFn: () => { },
...configInput,
replaceCurrentTrace: (newTrace, reason) => {
if (this.currentTrace) {
this.currentTrace.interrupt(reason);
}
this.currentTrace = newTrace;
// Subscribe to the new trace's events and forward them to our subjects
this.subscribeToTraceEvents(newTrace);
// Emit trace-start event
this.eventSubjects['trace-start'].next({
traceContext: newTrace,
});
},
onEndTrace: (traceToCleanUp) => {
if (traceToCleanUp === this.currentTrace) {
this.currentTrace = undefined;
}
// warn on miss?
},
getCurrentTrace: () => this.currentTrace,
};
}
/**
* Subscribe to events from a trace and forward them to the TraceManager subjects
*/
subscribeToTraceEvents(trace) {
// Forward state transition events
trace.when('state-transition').subscribe((event) => {
this.eventSubjects['state-transition'].next(event);
});
// Forward required span seen events
trace.when('required-span-seen').subscribe((event) => {
this.eventSubjects['required-span-seen'].next(event);
});
// Forward add-span-to-recording events
if ('when' in trace) {
trace.when('add-span-to-recording').subscribe((event) => {
this.eventSubjects['add-span-to-recording'].next(event);
});
trace.when('definition-modified').subscribe((event) => {
this.eventSubjects['definition-modified'].next(event);
});
}
}
when(event) {
return this.eventSubjects[event].asObservable();
}
utilities;
createTracer(traceDefinition) {
const requiredSpans = (0, ensureMatcherFn_1.convertMatchersToFns)(traceDefinition.requiredSpans);
const labelMatching = traceDefinition.labelMatching
? (0, ensureMatcherFn_1.convertLabelMatchersToFns)(traceDefinition.labelMatching)
: undefined;
const debounceOnSpans = (0, ensureMatcherFn_1.convertMatchersToFns)(traceDefinition.debounceOnSpans);
const interruptOnSpans = (0, ensureMatcherFn_1.convertMatchersToFns)(traceDefinition.interruptOnSpans);
const suppressErrorStatusPropagationOnSpans = (0, ensureMatcherFn_1.convertMatchersToFns)(traceDefinition.suppressErrorStatusPropagationOnSpans);
const computedSpanDefinitions = Object.fromEntries(Object.entries(traceDefinition.computedSpanDefinitions ?? {}).map(([name, def]) => [
name,
{
startSpan: typeof def.startSpan === 'string'
? def.startSpan
: (0, ensureMatcherFn_1.ensureMatcherFn)(def.startSpan),
endSpan: typeof def.endSpan === 'string'
? def.endSpan
: (0, ensureMatcherFn_1.ensureMatcherFn)(def.endSpan),
},
]));
const computedValueDefinitionsInputEntries = Object.entries(traceDefinition.computedValueDefinitions ?? {});
const computedValueDefinitions = Object.fromEntries(computedValueDefinitionsInputEntries.map(([name, def]) => [
name,
{
...def,
matches: def.matches.map((m) => (0, ensureMatcherFn_1.ensureMatcherFn)(m)),
computeValueFromMatches: def.computeValueFromMatches,
},
]));
const completeTraceDefinition = {
...traceDefinition,
requiredSpans: requiredSpans ??
[
// lack of requiredSpan is invalid, but we warn about it below
],
debounceOnSpans,
interruptOnSpans,
suppressErrorStatusPropagationOnSpans,
computedSpanDefinitions,
computedValueDefinitions,
labelMatching,
relationSchema: this.utilities.relationSchemas[traceDefinition.relationSchemaName],
};
if (!requiredSpans) {
this.utilities.reportErrorFn(new Error('requiredSpans must be defined along with the trace, as a trace can only end in an interrupted state otherwise'), { definition: completeTraceDefinition });
}
return new Tracer_1.Tracer(completeTraceDefinition, this.utilities);
}
processSpan(span) {
return this.currentTrace?.processSpan(span);
}
}
exports.TraceManager = TraceManager;
//# sourceMappingURL=TraceManager.js.map