UNPKG

@logtail/next

Version:

Better Stack Telemetry Next.js client

167 lines 8.48 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.withBetterStack = exports.withBetterStackRouteHandler = exports.withBetterStackNextConfig = void 0; const config_1 = require("./config"); const logger_1 = require("./logger"); const shared_1 = require("./shared"); function withBetterStackNextConfig(nextConfig) { return Object.assign(Object.assign({}, nextConfig), { rewrites: () => __awaiter(this, void 0, void 0, function* () { var _a; const rewrites = yield ((_a = nextConfig.rewrites) === null || _a === void 0 ? void 0 : _a.call(nextConfig)); 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('Envvars not detected. If this is production please see https://betterstack.com/docs/logs/javascript/nextjs/ for help'); log.warn('Sending Web Vitals to /dev/null'); log.warn('Sending logs to console'); return rewrites || []; // nothing to do } const betterStackRewrites = [ { source: `${config_1.config.proxyPath}/web-vitals`, destination: webVitalsEndpoint, basePath: false, }, { source: `${config_1.config.proxyPath}/logs`, destination: logsEndpoint, basePath: false, }, ]; if (!rewrites) { return betterStackRewrites; } else if (Array.isArray(rewrites)) { return rewrites.concat(betterStackRewrites); } else { rewrites.afterFiles = (rewrites.afterFiles || []).concat(betterStackRewrites); return rewrites; } }) }); } exports.withBetterStackNextConfig = withBetterStackNextConfig; function withBetterStackRouteHandler(handler, config) { return (req, arg) => __awaiter(this, void 0, void 0, function* () { var _a, _b, _c, _d; let region = ''; if ('geo' in req) { // @ts-ignore NextRequest.ip was removed in Next 15, works with undefined region = (_b = (_a = req.geo) === null || _a === void 0 ? void 0 : _a.region) !== null && _b !== void 0 ? _b : ''; } 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 === null || config === void 0 ? void 0 : config.logRequestDetails) || (config === null || config === void 0 ? void 0 : config.logRequestDetails) === true ? yield (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 === null || config === void 0 ? void 0 : config.logRequestDetails) ? Object.fromEntries(Object.entries(requestDetails).filter(([key]) => (config === null || config === void 0 ? void 0 : 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'}-log`; const betterStackContext = req; const args = arg; betterStackContext.log = log; try { const result = yield handler(betterStackContext, 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.isVercel) { 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 yield 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 = (_c = config === null || config === void 0 ? void 0 : config.notFoundLogLevel) !== null && _c !== void 0 ? _c : logger_1.LogLevel.warn; statusCode = 404; } else if (error.message === 'NEXT_REDIRECT') { logLevel = (_d = config === null || config === void 0 ? void 0 : config.redirectLogLevel) !== null && _d !== void 0 ? _d : 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.isVercel) { 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); yield logger.flush(); throw error; } }); } exports.withBetterStackRouteHandler = withBetterStackRouteHandler; function isNextConfig(param) { return typeof param == 'object'; } function withBetterStack(param, config) { if (typeof param == 'function') { return withBetterStackRouteHandler(param, config); } else if (isNextConfig(param)) { return withBetterStackNextConfig(param); } return withBetterStackRouteHandler(param, config); } exports.withBetterStack = withBetterStack; //# sourceMappingURL=withBetterStack.js.map