Files
flyer-crawler.projectium.com/docs/adr/0004-standardized-application-wide-structured-logging.md
Torben Sorensen d356d9dfb6
Some checks failed
Deploy to Test Environment / deploy-to-test (push) Failing after 43s
claude 1
2026-01-08 07:47:29 -08:00

4.9 KiB

ADR-004: Standardized Application-Wide Structured Logging

Date: 2025-12-12

Status: Accepted

Implemented: 2026-01-07

Context

Our application currently uses a logger service, but the implementation of logging varies significantly across different modules. The errorHandler middleware produces high-quality, structured JSON logs for errors, but logging within route handlers and service layers is often ad-hoc, using plain strings or inconsistent object structures.

This inconsistency leads to several problems: Difficult Debugging: It is hard to trace a single user request through the system or correlate events related to a specific operation. Ineffective Log Analysis: Inconsistent log formats make it difficult to effectively query, filter, and create dashboards in a log management system (like Datadog, Splunk, or the ELK stack). Security Risks: There is no enforced standard for redacting sensitive information (like passwords or tokens) in logs outside of the errorHandler, increasing the risk of accidental data exposure. Missing Context: Logs often lack crucial context, such as a unique request ID, the authenticated user's ID, or the source IP address, making them less useful for diagnosing issues.

Decision

We will adopt a standardized, application-wide structured logging policy. All log entries MUST be in JSON format and adhere to a consistent schema.

Request-Scoped Logger with Context: We will create a middleware that runs at the beginning of the request lifecycle. This middleware will:

  • Generate a unique request_id for each incoming request.
  • Create a request-scoped logger instance (a "child logger") that automatically includes the request_id, user_id (if authenticated), and ip_address in every log message it generates.
  • Attach this child logger to the req object (e.g., req.log).

Mandatory Use of Request-Scoped Logger: All route handlers and any service functions called by them MUST use the request-scoped logger (req.log) instead of the global logger instance. This ensures all logs for a given request are automatically correlated.

Standardized Log Schema: All log messages should follow a base schema. The logger configuration will be updated to enforce this. Base Fields: level, timestamp, message, request_id, user_id, ip_address. Error Fields: When logging an error, the log entry MUST include an error object with name, message, and stack.

Standardized Logging Practices: INFO: Log key business events, such as User logged in or Flyer processed. WARN: Log recoverable errors or unusual situations that do not break the request, such as Client Error: 404 on GET /api/non-existent-route or Retrying failed database connection. ERROR: Log only unhandled or server-side errors that cause a request to fail (typically handled by the errorHandler). Avoid logging expected client errors (like 4xx) at this level. DEBUG: Log detailed diagnostic information useful during development, such as function entry/exit points or variable states.

Example Usage

// In a new middleware file: logger.middleware.ts
import { logger } from '../services/logger.server';
import { randomUUID } from 'crypto';

export const requestLogger = (req, res, next) => {
  const requestId = randomUUID();
  // Create a child logger with context for this request
  req.log = logger.child({
    request_id: requestId,
    user_id: req.user?.user_id, // Assumes user is attached by auth middleware
    ip_address: req.ip,
  });
  next();
};

// In server/app setup:
// app.use(requestLogger); // Add this early in the middleware chain

// In a route handler:
router.get('/:id', async (req, res, next) => {
  // Use the request-scoped logger
  req.log.info({ flyerId: req.params.id }, 'Fetching flyer by ID');
  try {
    // ... business logic ...
    res.json(flyer);
  } catch (error) {
    // The error itself will be logged with full context by the errorHandler
    next(error);
  }
});

Consequences

Positive

Enhanced Observability: Every log line from a single request can be instantly grouped and analyzed, dramatically speeding up debugging. Improved Security: Centralizing the addition of context (like user_id) reduces the chance of developers manually logging sensitive data. Scalable Log Management: Consistent JSON logs are easily ingested and indexed by any modern log aggregation tool. Clearer Code: Removes the need to manually pass contextual information (like user ID) down to service functions just for logging purposes.

Negative

Refactoring Effort: Requires adding the requestLogger middleware and refactoring all routes and services to use req.log instead of the global logger. Slight Performance Overhead: Creating a child logger for every request adds a minor performance cost, though this is negligible for most modern logging libraries.