Hay uses Pino for structured, PII-redacting logging across the server.
import { createLogger } from "@server/lib/logger";
const logger = createLogger("my-module");
logger.info("Operation completed");
logger.info({ userId: "123", duration: 45 }, "Operation completed with context");
logger.error({ err: error }, "Operation failed");
logger.warn("Deprecation notice");
logger.debug("Detailed trace info");
Pino calling convention — object first, message second:
// Structured data + message
logger.info({ orderId, status }, "Order processed");
// Error logging (use `err` key for proper serialization)
logger.error({ err: error, orderId }, "Failed to process order");
// Simple message only
logger.info("Server started");
| Level | Use for |
|---|---|
error |
Failures requiring attention |
warn |
Degraded behavior, recoverable issues |
info |
Important state changes, startup, success |
debug |
Diagnostic detail, request tracing, dev output |
Configure via LOG_LEVEL environment variable. Defaults to info in production and debug otherwise.
All logs are automatically redacted at two layers:
Sensitive fields are replaced with [REDACTED] before serialization. The list below is a representative subset; see server/lib/logger/redaction.ts for the full source of truth.
password, token, accessToken, refreshToken, apiKey, secret, clientSecretemail, phone, phoneNumber, ssn, creditCard, bankAccountheaders.authorization, headers.cookiefield, *.field, *.*.field)Sensitive patterns embedded in freeform text are automatically caught, including email addresses, phone numbers, JWTs, Bearer/Basic auth tokens, IPv4 addresses, credit card numbers, SSNs, and common API key prefixes (e.g., sk-, pk_live_):
logger.info("User john@example.com signed up");
// Output: "User [EMAIL_REDACTED] signed up"
logger.info("Call +1-555-123-4567 for support");
// Output: "Call [PHONE_REDACTED] for support"
pino-pretty| Variable | Default | Description |
|---|---|---|
LOG_LEVEL |
debug/info |
Log level (info in production, debug otherwise) |
DEBUG_MODULES |
* |
Module filter for debug output |
console.* is banned in server code (no-console: "error"). Exceptions:
*.test.ts, *.spec.ts, tests/setup.ts)database/migrations/)scripts/, run-migration.ts)Production logs must be retained for no more than 30 days per GDPR requirements.
This is configured at the infrastructure level:
max-size/max-file or use a log aggregation service with 30-day retentionlogrotate with 30-day maximumserver/lib/logger/index.ts — Root logger and createLogger() factoryserver/lib/logger/redaction.ts — PII redaction patterns and string sanitizerserver/tests/lib/logger.test.ts — Redaction unit tests