hectoday
DocsCoursesChangelog GitHub
DocsCoursesChangelog GitHub

Access Required

Enter your access code to view courses.

Invalid code

← All courses Logging and Observability with @hectoday/http

Why Logging

  • console.log Is Not Enough
  • What to Log
  • Project Setup

Structured Logging

  • JSON Logs
  • Log Levels
  • Building a Logger

Request Logging

  • Request IDs
  • Request-Response Logging
  • Error Logging

Context and Correlation

  • Log Context
  • Child Loggers
  • Correlating Across Services

What to Observe

  • Business Event Logging
  • Performance Logging
  • Health and Metrics

Production

  • Log Output and Transport
  • Sensitive Data
  • Checklist and Capstone

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 to error — 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

EventLevelWhy
Request completed (200)infoNormal operation
Request failed (400)infoClient error, not server problem
Request failed (500)errorServer bug
Deprecated endpoint calledwarnWorking but needs attention
Slow query (>100ms)warnWorking but needs optimization
External service downerrorSystem degraded
Database connection lostfatalSystem cannot function
Cache hitdebugOnly useful for debugging
SQL query executeddebugOnly 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'?

← JSON Logs Building a Logger →

© 2026 hectoday. All rights reserved.