UNPKG

@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
"use strict"; 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