Skip to content

Logging

Alchemify uses pino for structured JSON logging with automatic request context via pino-http.

Set LOG_LEVEL in your .env file:

Terminal window
# trace | debug | info | warn | error | fatal | silent
LOG_LEVEL=debug

Defaults: debug in development, info in production.

In development, logs are pretty-printed via pino-pretty. In production, logs are plain JSON (one object per line), ready for any log aggregator.

Every HTTP request (except /health) is automatically logged with:

  • Request ID — unique per request
  • HTTP method and URL
  • Response status and duration
  • userId — attached after JWT verification

Additional events at specific log levels:

LevelEvent
debugJWT authentication result, SQL queries (statement only), function calls
infoServer start, auth success (login/verify/invite), magic-link requested
warnAuth failures (invalid credentials, expired tokens)
errorUnhandled errors in request handlers

The following are never logged:

  • Passwords
  • JWT tokens
  • SQL parameter values (only the parameter count is logged)

Magic-link tokens are logged at debug level only when NODE_ENV is not production.

Import the shared logger instance:

import { logger } from "./logger.js";
// Structured fields go in the first argument
logger.info({ port: 3000 }, "server started");

Inside Express request handlers, use req.log instead — it automatically includes the request ID and other context:

app.post("/example", (req, res) => {
req.log.info({ userId: "..." }, "something happened");
});

Tests run with LOG_LEVEL=silent (configured in vitest.config.ts) so log output does not clutter test results.