UNPKG

@zendesk/retrace

Version:

define and capture Product Operation Traces along with computed metrics with an optional friendly React beacon API

198 lines 7.54 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.extractTimingOffsets = void 0; exports.isSuppressedError = isSuppressedError; exports.formatMs = formatMs; exports.getConfigSummary = getConfigSummary; exports.getComputedResults = getComputedResults; exports.getMatcherLabelFromCombinator = getMatcherLabelFromCombinator; exports.formatMatcher = formatMatcher; const constants_1 = require("./constants"); const recordingComputeUtils_1 = require("./recordingComputeUtils"); // Helper to check if error is suppressed function isSuppressedError(trace, spanAndAnnotation) { return !!trace.definition.suppressErrorStatusPropagationOnSpans?.some((fn) => fn(spanAndAnnotation, trace)); } // Helper to format ms function formatMs(ms) { if (ms == null) return 'n/a'; if (ms < 1_000) return `${ms.toFixed(0)}ms`; return `${(ms / 1_000).toFixed(2)}s`; } // Helper to get config summary from traceContext or definition function getConfigSummary(traceContext) { const def = traceContext.definition; const variant = def.variants[traceContext.input.variant]; const timeout = variant?.timeout; const debounce = (def.debounceOnSpans ?? []).length > 0 ? def.debounceWindow ?? constants_1.DEFAULT_DEBOUNCE_DURATION : undefined; const interactive = typeof def.captureInteractive === 'object' ? def.captureInteractive.timeout : def.captureInteractive ? constants_1.DEFAULT_INTERACTIVE_TIMEOUT_DURATION : undefined; return { timeout, debounce, interactive }; } // Helper to get computed values/spans for completed/interrupted traces function getComputedResults( // eslint-disable-next-line @typescript-eslint/no-explicit-any traceContext, finalTransition) { try { const recording = (0, recordingComputeUtils_1.createTraceRecording)(traceContext, finalTransition); return recording; } catch { return undefined; } } /** * Extract timing offsets from a transition object */ const extractTimingOffsets = (transition) => { let lastRequiredSpanOffset; let completeSpanOffset; let cpuIdleSpanOffset; if ('lastRequiredSpanAndAnnotation' in transition && transition.lastRequiredSpanAndAnnotation) { lastRequiredSpanOffset = transition.lastRequiredSpanAndAnnotation.annotation .operationRelativeEndTime; } if ('completeSpanAndAnnotation' in transition && transition.completeSpanAndAnnotation) { completeSpanOffset = transition.completeSpanAndAnnotation.annotation.operationRelativeEndTime; } if ('cpuIdleSpanAndAnnotation' in transition && transition.cpuIdleSpanAndAnnotation) { cpuIdleSpanOffset = transition.cpuIdleSpanAndAnnotation.annotation.operationRelativeEndTime; } return { lastRequiredSpanOffset, completeSpanOffset, cpuIdleSpanOffset }; }; exports.extractTimingOffsets = extractTimingOffsets; /** * Attempt to create a more descriptive name from the definition * This part needs customization based on how 'fromDefinition' is structured. * Example: Check for specific properties like 'name', 'type', 'label' etc. */ function getMatcherLabelFromCombinator(def, index) { if ('fromDefinition' in def && def.fromDefinition) { return getMatcherLabelFromCombinator(def.fromDefinition, index); } const parts = []; // Example: Prioritize 'label' if it exists if ('label' in def && typeof def.label === 'string') { return `label="${def.label}"`; } // Example: Use 'name' if it exists if ('name' in def) { if (typeof def.name === 'string') { parts.push(`name="${def.name}"`); } else if (def.name instanceof RegExp) { parts.push(`name=/${def.name.source}/${def.name.flags}`); } } // Example: Add type if present if ('type' in def && typeof def.type === 'string') { parts.push(`type="${def.type}"`); } // Add other relevant properties from your definition structure if ('oneOf' in def && Array.isArray(def.oneOf)) { parts.push(`( ${def.oneOf .map((item, i) => getMatcherLabelFromCombinator(item, i)) .join(' OR ')} )`); } // performanceEntryName if ('performanceEntryName' in def && def.performanceEntryName !== undefined) { if (typeof def.performanceEntryName === 'string') { parts.push(`performanceEntryName="${def.performanceEntryName}"`); } else if (def.performanceEntryName instanceof RegExp) { parts.push(`performanceEntryName=/${def.performanceEntryName.source}/${def.performanceEntryName.flags}`); } } // status if ('status' in def && typeof def.status === 'string') { parts.push(`status="${def.status}"`); } // attributes if ('attributes' in def && typeof def.attributes === 'object' && def.attributes !== null) { const attrStr = Object.entries(def.attributes) .map(([k, v]) => `${k}:${JSON.stringify(v)}`) .join(', '); if (attrStr) parts.push(`attributes={${attrStr}}`); } // matchingRelations if ('matchingRelations' in def && def.matchingRelations !== undefined) { if (Array.isArray(def.matchingRelations)) { parts.push(`matchingRelations=[${def.matchingRelations.join(', ')}]`); } else if (typeof def.matchingRelations === 'boolean' && def.matchingRelations) { parts.push(`matchingRelations`); } } // occurrence if ('occurrence' in def && def.occurrence !== undefined) { if (typeof def.occurrence === 'number') { parts.push(`occurrence=${def.occurrence}`); } else if (typeof def.occurrence === 'function') { parts.push('occurrence=<fn>'); } } // isIdle if ('isIdle' in def && typeof def.isIdle === 'boolean' && def.isIdle) { parts.push(`isIdle`); } // fn if ('fn' in def && typeof def.fn === 'function') { parts.push('<customMatcherFn>'); } // nthMatch if ('nthMatch' in def && typeof def.nthMatch === 'number') { parts.push(`nthMatch=${def.nthMatch}`); } if (parts.length > 0) { return parts.join(' AND '); } // Fallback: Stringify the definition (can be verbose) try { const defString = JSON.stringify(def); // Limit length to avoid overly long strings return defString.length > 100 ? `${defString.slice(0, 97)}...` : defString; } catch { // Fallback if stringify fails return `<matcher#${index ?? '?'}>`; } } /** * Formats a SpanMatcherFn into a more readable string representation. * This is a basic implementation and can be significantly improved * based on the actual structure of `matcher.fromDefinition`. * * @param matcher The matcher function to format. * @param index Optional index for generic naming. * @returns A string representation of the matcher. */ function formatMatcher(matcher, index) { // Check if the matcher has attached definition info if (matcher.fromDefinition) { const def = matcher.fromDefinition; if (def && typeof def === 'object') { return getMatcherLabelFromCombinator(def, index); } } // Fallback if no definition info is available return `<matcher#${index ?? '?'}>`; } //# sourceMappingURL=debugUtils.js.map