@logtail/next
Version:
Better Stack Telemetry Next.js client
155 lines • 8.33 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 (request, context) => __awaiter(this, void 0, void 0, function* () {
var _a, _b;
const pathname = request.nextUrl.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)(request)
: undefined;
const report = {
startTime: new Date().getTime(),
endTime: new Date().getTime(),
path: pathname,
method: request.method,
host: request.headers.get('host'),
userAgent: request.headers.get('user-agent'),
scheme: request.url.split('://')[0],
ip: request.headers.get('x-forwarded-for'),
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 = request;
betterStackContext.log = log;
// Handle params Promise for Next.js 15+
const resolvedParams = context && typeof context === 'object' && 'params' in context && context.params instanceof Promise
? Object.assign(Object.assign({}, context), { params: yield context.params }) : context;
try {
const result = yield handler(betterStackContext, resolvedParams);
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, `${request.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 = (_a = config === null || config === void 0 ? void 0 : config.notFoundLogLevel) !== null && _a !== void 0 ? _a : logger_1.LogLevel.warn;
statusCode = 404;
}
else if (error.message === 'NEXT_REDIRECT') {
logLevel = (_b = config === null || config === void 0 ? void 0 : config.redirectLogLevel) !== null && _b !== void 0 ? _b : 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, `${request.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;
// withBetterStack can be called either with NextConfig, which will add proxy rewrites
// to improve deliverability of Web-Vitals and logs, or with a NextHandler function.
function withBetterStack(param, config) {
if (typeof param === 'function') {
return withBetterStackRouteHandler(param, config);
}
else {
// Assume it's a NextConfig object if it's not a function
return withBetterStackNextConfig(param);
}
}
exports.withBetterStack = withBetterStack;
//# sourceMappingURL=withBetterStack.js.map