Add structured backend logging with pino
Wires leveled, structured logging (pretty in dev, JSON in prod) through the server lifecycle, HTTP/WS request handling, run engine, auth, db, and config loading. CLI command output is left untouched since it's user-facing terminal UX, not backend logs. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
import "./src/bootstrap/async-local-storage-polyfill";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { createServer } from "node:http";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import next from "next";
|
||||
import type { Level } from "pino";
|
||||
import { WebSocketServer } from "ws";
|
||||
import { getConfig } from "./src/lib/config/load";
|
||||
import { migrateOnBoot } from "./src/lib/db/client";
|
||||
@@ -10,6 +12,9 @@ import { syncAuthFromConfig } from "./src/lib/auth/sync";
|
||||
import { reconcileOrphanedRuns } from "./src/lib/runner/engine";
|
||||
import { killAllRuns } from "./src/lib/runner/registry";
|
||||
import { attachWsServer, authenticateUpgrade } from "./src/lib/ws/server";
|
||||
import { logger } from "./src/lib/logger";
|
||||
|
||||
const log = logger.child({ mod: "server" });
|
||||
|
||||
const dev = process.env.NODE_ENV !== "production";
|
||||
const { config } = getConfig();
|
||||
@@ -31,11 +36,41 @@ process.env.TRIGGERSHELL_APP_ROOT = dir;
|
||||
const app = next({ dev, dir, hostname, port });
|
||||
const handle = app.getRequestHandler();
|
||||
|
||||
// `/_next/*` asset requests happen dozens of times per page load and carry no operational
|
||||
// signal - logged at debug so they don't drown out page/API requests in the default info level.
|
||||
function accessLogLevel(pathname: string, statusCode: number): Level {
|
||||
if (statusCode >= 500) return "error";
|
||||
if (statusCode >= 400) return "warn";
|
||||
return pathname.startsWith("/_next/") ? "debug" : "info";
|
||||
}
|
||||
|
||||
app.prepare().then(() => {
|
||||
const nextUpgradeHandler = app.getUpgradeHandler();
|
||||
|
||||
const httpServer = createServer((req, res) => {
|
||||
handle(req, res);
|
||||
const reqId = req.headers["x-request-id"]?.toString() ?? randomUUID();
|
||||
req.headers["x-request-id"] = reqId;
|
||||
const startedAt = process.hrtime.bigint();
|
||||
|
||||
res.on("finish", () => {
|
||||
const durationMs = Number(process.hrtime.bigint() - startedAt) / 1e6;
|
||||
const { pathname } = new URL(req.url ?? "/", "http://internal");
|
||||
log[accessLogLevel(pathname, res.statusCode)](
|
||||
{
|
||||
reqId,
|
||||
method: req.method,
|
||||
path: pathname,
|
||||
status: res.statusCode,
|
||||
durationMs: Math.round(durationMs),
|
||||
},
|
||||
"request",
|
||||
);
|
||||
});
|
||||
|
||||
handle(req, res).catch((error: unknown) => {
|
||||
log.error({ reqId, err: error }, "unhandled error handling request");
|
||||
if (!res.headersSent) res.writeHead(500).end();
|
||||
});
|
||||
});
|
||||
|
||||
const wss = new WebSocketServer({ noServer: true });
|
||||
@@ -52,6 +87,7 @@ app.prepare().then(() => {
|
||||
authenticateUpgrade(req)
|
||||
.then((ok) => {
|
||||
if (!ok) {
|
||||
log.warn({ path: pathname }, "rejected unauthenticated WS upgrade");
|
||||
socket.write("HTTP/1.1 401 Unauthorized\r\n\r\n");
|
||||
socket.destroy();
|
||||
return;
|
||||
@@ -60,21 +96,28 @@ app.prepare().then(() => {
|
||||
wss.emit("connection", ws, req);
|
||||
});
|
||||
})
|
||||
.catch(() => socket.destroy());
|
||||
.catch((error: unknown) => {
|
||||
log.error({ err: error }, "error authenticating WS upgrade");
|
||||
socket.destroy();
|
||||
});
|
||||
});
|
||||
|
||||
httpServer.listen(port, hostname, () => {
|
||||
console.log(
|
||||
`> triggershell ready on http://${hostname}:${port} (${dev ? "development" : "production"})`,
|
||||
log.info(
|
||||
{ hostname, port, mode: dev ? "development" : "production" },
|
||||
"triggershell ready",
|
||||
);
|
||||
});
|
||||
|
||||
const shutdown = (signal: string) => {
|
||||
console.log(`> received ${signal}, shutting down...`);
|
||||
log.info({ signal }, "shutting down");
|
||||
killAllRuns();
|
||||
httpServer.close(() => process.exit(0));
|
||||
// Force-exit if graceful shutdown hangs (e.g. a stuck WS connection).
|
||||
setTimeout(() => process.exit(1), 5000).unref();
|
||||
setTimeout(() => {
|
||||
log.warn("graceful shutdown timed out, forcing exit");
|
||||
process.exit(1);
|
||||
}, 5000).unref();
|
||||
};
|
||||
|
||||
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
||||
|
||||
Reference in New Issue
Block a user