diff --git a/AGENTS.md b/AGENTS.md index 305c8fd..2552c97 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -131,11 +131,11 @@ All server-side logging goes through `src/server/logger.ts`, a thin wrapper over import { getLogger } from '@/server/logger' const logger = getLogger(['feature', 'subsystem']) logger.info('Created todo {id}', { id }) -logger.error('DB write failed: {err}', { err }) +logger.error('DB write failed', { error }) ``` - Categories are hierarchical arrays — they show up as dot-paths in JSON output (`"logger":"feature.subsystem"`) and let you filter by prefix when shipping logs. -- The `{name}` placeholders in the message string are filled from the second-arg properties object. Don't string-concatenate or template-literal — that defeats structured logging. +- The `{name}` placeholders are for **primitive** values you want rendered inline (numbers, short strings, IDs). For objects, errors, and anything multi-field, omit the placeholder and just pass the value in properties — `logger.error('Auth failed', { error, userId })` keeps the message clean while properties stay structured. Never string-concatenate or template-literal — that defeats structured logging. - Format is `pretty` (icons + ANSI) on TTY, `json` (one-line JSON) when piped — perfect for Loki/Datadog/CloudWatch ingestion. Override with `LOG_FORMAT`. - Drizzle SQL queries are logged at `info` under category `['db']` when `LOG_DB=true`, via `@logtape/drizzle-orm`'s `DrizzleLogger` adapter (constructed in `src/server/db/index.ts`). The `info` level is intentional: flipping `LOG_DB=true` alone is enough — no need to also lower `LOG_LEVEL`. - `src/server/api/interceptors.ts` calls `getLogger(['api']).error(...)` from `logError`. CLI subcommands lazy-import the logger inside `run()` — they are still required to be side-effect-free at module top (citty eager-loads for `--help`). diff --git a/src/server/api/interceptors.ts b/src/server/api/interceptors.ts index f88d7a6..cfbbd8c 100644 --- a/src/server/api/interceptors.ts +++ b/src/server/api/interceptors.ts @@ -5,7 +5,7 @@ import { getLogger } from '@/server/logger' const logger = getLogger(['api']) export const logError = (error: unknown) => { - logger.error('Unhandled error in ORPC handler: {error}', { error }) + logger.error('Unhandled error in ORPC handler', { error }) } export const handleValidationError = (error: unknown) => { diff --git a/src/server/plugins/shutdown.ts b/src/server/plugins/shutdown.ts index 1f54272..097ed79 100644 --- a/src/server/plugins/shutdown.ts +++ b/src/server/plugins/shutdown.ts @@ -1,21 +1,28 @@ import { db } from '@/server/db' +import { getLogger } from '@/server/logger' export default () => { if (import.meta.dev) return + const logger = getLogger(['shutdown']) let exiting = false - const shutdown = () => { + const shutdown = (signal: NodeJS.Signals) => { if (exiting) { + logger.warn('Forcing exit on repeated signal', { signal }) process.exit(0) } exiting = true + logger.info('Draining for shutdown', { signal, graceMs: 500 }) setTimeout(() => { - db.$client.end().finally(() => process.exit(0)) + db.$client.end().finally(() => { + logger.info('DB pool closed, exiting') + process.exit(0) + }) }, 500) } - process.on('SIGINT', shutdown) - process.on('SIGTERM', shutdown) + process.on('SIGINT', () => shutdown('SIGINT')) + process.on('SIGTERM', () => shutdown('SIGTERM')) }