UNPKG

@vtex/api

Version:
160 lines (159 loc) • 7.88 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.metricsMiddleware = void 0; const constants_1 = require("../../constants"); const RequestCancelledError_1 = require("../../errors/RequestCancelledError"); const requestStats_1 = require("../../service/worker/runtime/http/middlewares/requestStats"); const utils_1 = require("../../utils"); const retry_1 = require("../../utils/retry"); const status_1 = require("../../utils/status"); const DEFAULT_ACCOUNT = 'unknown'; const metricsMiddleware = ({ metrics, serverTiming, name }) => { const serverTimingStart = process.hrtime(); const serverTimingLabel = (0, utils_1.shrinkTimings)((0, utils_1.formatTimingName)({ hopNumber: 0, source: process.env.VTEX_APP_NAME, target: name || 'unknown', })); return async (ctx, next) => { var _a, _b; const start = process.hrtime(); let status = 'unknown'; let errorCode; let errorStatus; try { if (ctx.config.verbose && ctx.config.label) { console.log(`VERBOSE: ${name}.${ctx.config.label}`, `start`); } await next(); if (ctx.config.metric && ctx.response && ctx.response.status) { status = (0, status_1.statusLabel)(ctx.response.status); } } catch (err) { const isCancelled = (err.message === requestStats_1.cancelMessage); if (ctx.config.metric) { errorCode = err.code; errorStatus = err.response && err.response.status; if (err.code === 'ECONNABORTED') { status = 'aborted'; } else if (err.response && err.response.data && err.response.data.code === retry_1.TIMEOUT_CODE) { status = 'timeout'; } else if (err.response && err.response.status) { status = (0, status_1.statusLabel)(err.response.status); } else if (isCancelled) { status = 'cancelled'; } else { status = 'error'; } } throw isCancelled ? new RequestCancelledError_1.RequestCancelledError(err.message) : err; } finally { if (ctx.config.metric && metrics) { const label = `http-client-${ctx.config.metric}`; const extensions = {}; Object.assign(extensions, { [status]: 1 }); // Determine cache state for diagnostics metrics let cacheState = 'none'; if (ctx.cacheHit) { Object.assign(extensions, ctx.cacheHit, { [`${status}-hit`]: 1 }); cacheState = 'hit'; } else if (!ctx.inflightHit && !ctx.memoizedHit) { // Lets us know how many calls passed through to origin Object.assign(extensions, { [`${status}-miss`]: 1 }); cacheState = 'miss'; } if (ctx.inflightHit) { Object.assign(extensions, { [`${status}-inflight`]: 1 }); cacheState = 'inflight'; } if (ctx.memoizedHit) { Object.assign(extensions, { [`${status}-memoized`]: 1 }); cacheState = 'memoized'; } const retryCount = ctx.config.retryCount || 0; if (retryCount > 0) { extensions[`retry-${status}-${retryCount}`] = 1; } const end = status === 'success' && !ctx.cacheHit && !ctx.inflightHit && !ctx.memoizedHit ? process.hrtime(start) : undefined; // Legacy metrics (backward compatibility) metrics.batch(label, end, extensions); // New diagnostics metrics with stable names and attributes if (global.diagnosticsMetrics) { const elapsed = process.hrtime(start); const rawStatusCode = ((_a = ctx.response) === null || _a === void 0 ? void 0 : _a.status) || errorStatus; // Extract account from request headers, fallback to default const account = ((_b = ctx.config.headers) === null || _b === void 0 ? void 0 : _b[constants_1.HeaderKeys.ACCOUNT]) || DEFAULT_ACCOUNT; const baseAttributes = { [constants_1.AttributeKeys.VTEX_ACCOUNT_NAME]: account, component: 'http-client', client_metric: ctx.config.metric, status_code: rawStatusCode, status, }; // Record latency histogram with all context global.diagnosticsMetrics.recordLatency(elapsed, { ...baseAttributes, cache_state: cacheState, }); // Increment counters for different event types (replaces extensions behavior) // Main request counter with status as attribute global.diagnosticsMetrics.incrementCounter('http_client_requests_total', 1, baseAttributes); // Cache counter with cache_state as attribute (replaces extensions like 'success-hit', 'error-miss') if (cacheState !== 'none') { global.diagnosticsMetrics.incrementCounter('http_client_cache_total', 1, { ...baseAttributes, cache_state: cacheState, }); } // Retry counter (replaces 'retry-{status}-{count}' extensions) if (retryCount > 0) { global.diagnosticsMetrics.incrementCounter('http_client_requests_retried_total', 1, { ...baseAttributes, retry_count: retryCount, }); } } else { console.warn('DiagnosticsMetrics not available. HTTP client metrics not reported.'); } if (ctx.config.verbose) { console.log(`VERBOSE: ${name}.${ctx.config.label}`, { ...extensions, ...errorCode || errorStatus ? { errorCode, errorStatus } : null, millis: end ? (0, utils_1.hrToMillis)(end) : extensions.revalidated || extensions.router || status !== 'success' ? (0, utils_1.hrToMillis)(process.hrtime(start)) : '(from cache)', status: ctx.response && ctx.response.status, headers: ctx.response && ctx.response.headers, }); } } else { if (ctx.config.verbose) { console.warn(`PROTIP: Please add a metric property to ${name} client request to get metrics in Splunk`, { baseURL: ctx.config.baseURL, url: ctx.config.url }); } } if (serverTiming) { // Timings in the client's perspective const dur = (0, utils_1.hrToMillis)(process.hrtime(serverTimingStart)); if (!serverTiming[serverTimingLabel] || Number(serverTiming[serverTimingLabel]) < dur) { serverTiming[serverTimingLabel] = `${dur}`; } } } }; }; exports.metricsMiddleware = metricsMiddleware;