next-axiom
Version:
Send WebVitals from your Next.js project to Axiom.
168 lines • 7.26 kB
JavaScript
;
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