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 (default: debug in development, info in production).
All logs are automatically redacted at two layers:
Sensitive fields are replaced with [REDACTED] before serialization:
password, token, accessToken, refreshToken, apiKey, secret, clientSecretemail, phone, phoneNumber, ssn, creditCard, bankAccountheaders.authorization, headers.cookie*.email, *.token, etc.)Email addresses and phone numbers embedded in freeform text are automatically caught:
logger.info("User [email protected] 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 (debug in dev, info in prod) |
DEBUG_MODULES |
* |
Module filter for legacy debugLog |
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 maximumdebugLog from @server/lib/debug-logger is deprecated. It now delegates to the Pino logger internally, so existing callers get PII redaction automatically. For new code, use createLogger() directly:
// Before (deprecated):
import { debugLog } from "@server/lib/debug-logger";
debugLog("perception", "Analyzing intent", { messageId: "123" });
// After:
import { createLogger } from "@server/lib/logger";
const logger = createLogger("perception");
logger.info({ messageId: "123" }, "Analyzing intent");
server/lib/logger/index.ts — Root logger and createLogger() factoryserver/lib/logger/redaction.ts — PII redaction patterns and string sanitizerserver/lib/debug-logger.ts — Legacy bridge (deprecated)server/tests/lib/logger.test.ts — Redaction unit tests