@logtail/next
Version:
Better Stack Telemetry Next.js client
167 lines • 8.48 kB
JavaScript
;
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