Log Levels
Not all logs are equal
A debug trace (“entering function X”) and a fatal error (“database connection lost”) are both log entries — but they have very different importance. Log levels classify entries by severity so you can filter: show errors in production, show everything in development.
The five levels
debug — Detailed information for developers. Function entry/exit, variable values, SQL queries. Only useful during development or when debugging a specific issue.
{
"level": "debug",
"message": "executing query",
"sql": "SELECT * FROM books WHERE genre = ?",
"params": ["fiction"]
} info — Normal operation. Requests completed, resources created, configuration loaded. The default level in production.
{"level":"info","message":"request completed","method":"GET","path":"/books","status":200,"duration":12}
{"level":"info","message":"server started","port":3000} warn — Something unexpected happened but the system handled it. Deprecated API called, cache miss on expected hit, approaching rate limit.
{"level":"warn","message":"deprecated endpoint called","path":"/v1/books","sunset":"2025-06-01"}
{"level":"warn","message":"slow query","duration":250,"threshold":100} [!NOTE] The Error Handling course classified errors as operational (expected) and programmer (unexpected). Operational errors map to
warn— the system handled them. Programmer errors map toerror— something is broken.
error — Something failed and needs attention. Unhandled exceptions, failed external service calls, data corruption.
{
"level": "error",
"message": "payment processing failed",
"error": "Card declined",
"userId": "user-42",
"orderId": "order-99"
} fatal — The process is about to crash. Database connection permanently lost, out of memory, configuration missing at startup.
{
"level": "fatal",
"message": "database connection failed",
"error": "SQLITE_CANTOPEN",
"path": "catalog.db"
} Level hierarchy
Each level includes all levels above it:
fatal → only fatal
error → error + fatal
warn → warn + error + fatal
info → info + warn + error + fatal
debug → debug + info + warn + error + fatal (everything) Setting the log level to info means: show info, warn, error, and fatal. Hide debug.
Setting the log level to error means: show only error and fatal. Hide debug, info, and warn.
Level filtering
const LEVELS = { debug: 0, info: 1, warn: 2, error: 3, fatal: 4 } as const;
type Level = keyof typeof LEVELS;
const currentLevel: Level = (process.env.LOG_LEVEL as Level) ?? "info";
function shouldLog(level: Level): boolean {
return LEVELS[level] >= LEVELS[currentLevel];
}
function log(level: Level, message: string, context: Record<string, unknown> = {}): void {
if (!shouldLog(level)) return; // Skip if below current level
console.log(
JSON.stringify({
timestamp: new Date().toISOString(),
level,
message,
...context,
}),
);
} In production: LOG_LEVEL=info. Debug traces are hidden. Info, warnings, and errors are visible.
In development: LOG_LEVEL=debug. Everything is visible, including SQL queries and function traces.
When debugging a production issue: temporarily set LOG_LEVEL=debug to see detailed traces, then set it back.
Choosing the right level
| Event | Level | Why |
|---|---|---|
| Request completed (200) | info | Normal operation |
| Request failed (400) | info | Client error, not server problem |
| Request failed (500) | error | Server bug |
| Deprecated endpoint called | warn | Working but needs attention |
| Slow query (>100ms) | warn | Working but needs optimization |
| External service down | error | System degraded |
| Database connection lost | fatal | System cannot function |
| Cache hit | debug | Only useful for debugging |
| SQL query executed | debug | Only useful for debugging |
Exercises
Exercise 1: Add level filtering to the log function. Set LOG_LEVEL=warn. Verify debug and info entries are hidden.
Exercise 2: Log a request at info level, a slow query at warn, and an unhandled error at error. Verify the hierarchy.
Exercise 3: Set LOG_LEVEL=debug in development and LOG_LEVEL=info in production (via environment variable). Compare the output.
Why use 'info' as the default log level in production instead of 'debug'?