UNPKG

next-axiom

Version:

Send WebVitals from your Next.js project to Axiom.

168 lines 7.26 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.withAxiomNextConfig = withAxiomNextConfig; exports.withAxiomRouteHandler = withAxiomRouteHandler; exports.withAxiom = withAxiom; const functions_1 = require("@vercel/functions"); const config_1 = require("./config"); const logger_1 = require("./logger"); const shared_1 = require("./shared"); function withAxiomNextConfig(nextConfig) { return { ...nextConfig, rewrites: async () => { const rewrites = await nextConfig.rewrites?.(); const webVitalsEndpoint = config_1.config.getIngestURL(shared_1.EndpointType.webVitals); const logsEndpoint = config_1.config.getIngestURL(shared_1.EndpointType.logs); if (!webVitalsEndpoint && !logsEndpoint) { const log = new logger_1.Logger(); log.warn('axiom: Envvars not detected. If this is production please see https://github.com/axiomhq/next-axiom for help'); log.warn('axiom: Sending Web Vitals to /dev/null'); log.warn('axiom: Sending logs to console'); return rewrites || []; // nothing to do } const axiomRewrites = [ { source: `${config_1.config.proxyPath}/web-vitals`, destination: webVitalsEndpoint, basePath: false, }, { source: `${config_1.config.proxyPath}/logs`, destination: logsEndpoint, basePath: false, }, ]; if (!rewrites) { return axiomRewrites; } else if (Array.isArray(rewrites)) { return rewrites.concat(axiomRewrites); } else { rewrites.afterFiles = (rewrites.afterFiles || []).concat(axiomRewrites); return rewrites; } }, }; } function withAxiomRouteHandler(handler, config) { return async (req, arg) => { let region = ''; if ('geo' in req) { region = req.geo?.region ?? ''; } let pathname = ''; if ('nextUrl' in req) { pathname = req.nextUrl.pathname; } else if (req instanceof Request) { // pathname = req.url.substring(req.headers.get('host')?.length || 0) pathname = new URL(req.url).pathname; } const requestDetails = Array.isArray(config?.logRequestDetails) || config?.logRequestDetails === true ? await (0, shared_1.requestToJSON)(req) : undefined; const report = { startTime: new Date().getTime(), endTime: new Date().getTime(), path: pathname, method: req.method, host: req.headers.get('host'), userAgent: req.headers.get('user-agent'), scheme: req.url.split('://')[0], ip: req.headers.get('x-forwarded-for'), region, details: Array.isArray(config?.logRequestDetails) ? Object.fromEntries(Object.entries(requestDetails).filter(([key]) => (config?.logRequestDetails).includes(key))) : requestDetails, }; // main logger, mainly used to log reporting on the incoming HTTP request const logger = new logger_1.Logger({ req: report, source: config_1.isEdgeRuntime ? 'edge' : 'lambda' }); // child logger to be used by the users within the handler const log = logger.with({}); log.config.source = `${config_1.isEdgeRuntime ? 'edge' : 'lambda'}${!config_1.isVercelIntegration ? '-log' : ''}`; const axiomContext = req; const args = arg; axiomContext.log = log; try { const result = await handler(axiomContext, args); report.endTime = new Date().getTime(); // report log record report.statusCode = result.status; report.durationMs = report.endTime - report.startTime; // record the request if (!config_1.isVercelIntegration) { logger.logHttpRequest(logger_1.LogLevel.info, `${req.method} ${report.path} ${report.statusCode} in ${report.endTime - report.startTime}ms`, report, {}); } // attach the response status to all children logs log.attachResponseStatus(result.status); // flush the logger along with the child logger if (config_1.isVercel) { (0, functions_1.waitUntil)(logger.flush()); } else { await logger.flush(); } return result; } catch (error) { // capture request endTime first for more accurate reporting report.endTime = new Date().getTime(); // set default values for statusCode and logLevel let statusCode = 500; let logLevel = logger_1.LogLevel.error; // handle navigation errors like notFound and redirect if (error instanceof Error) { if (error.message === 'NEXT_NOT_FOUND') { logLevel = config?.notFoundLogLevel ?? logger_1.LogLevel.warn; statusCode = 404; } else if (error.message === 'NEXT_REDIRECT') { logLevel = config?.redirectLogLevel ?? logger_1.LogLevel.info; // according to Next.js docs, values are: 307 (Temporary) or 308 (Permanent) // see: https://nextjs.org/docs/app/api-reference/functions/redirect#why-does-redirect-use-307-and-308 // extract status code from digest, if exists const e = error; if (e.digest) { const d = e.digest.split(';'); statusCode = parseInt(d[3]); } else { statusCode = 307; } } } // report log record report.statusCode = statusCode; report.durationMs = report.endTime - report.startTime; // record the request if (!config_1.isVercelIntegration) { logger.logHttpRequest(logLevel, `${req.method} ${report.path} ${report.statusCode} in ${report.endTime - report.startTime}ms`, report, {}); } // forward the error message as a log event log.log(logLevel, error.message, { error }); log.attachResponseStatus(statusCode); if (config_1.isVercel) { (0, functions_1.waitUntil)(logger.flush()); } else { await logger.flush(); } throw error; } }; } function isNextConfig(param) { return typeof param == 'object'; } function withAxiom(param, config) { if (typeof param == 'function') { return withAxiomRouteHandler(param, config); } else if (isNextConfig(param)) { return withAxiomNextConfig(param); } return withAxiomRouteHandler(param, config); } //# sourceMappingURL=withAxiom.js.map