Skip to main content

Logging Best Practices

This document describes the logging standards and best practices for the Substrate project.

Overview

Substrate uses structured logging to provide consistent, searchable, and contextual log output across all services. Logs include:

  • Timestamps - ISO 8601 format with milliseconds
  • Severity levels - TRACE, DEBUG, INFO, WARN, ERROR, FATAL
  • Context - Component names, request paths, user IDs, and other metadata
  • Error details - Error messages, stack traces, and relevant context

Server-Side Logging

For server-side code (API routes, GraphQL resolvers, middleware, etc.), use the logger from shared-utils:

  • Use createLogger() for general components
  • Use createRouteLogger() for API routes (automatically extracts path from request)
  • Use createResolverLogger() for GraphQL resolvers

Client-Side Logging

For client-side React components, use createClientLogger() from shared-utils. Client logs are formatted for browser console output with timestamps and severity levels.

Log Levels

Choose the appropriate log level:

  • TRACE - Very detailed diagnostic information (development only)
  • DEBUG - Detailed information useful for debugging (development only)
  • INFO - General informational messages about application flow
  • WARN - Warning messages for potentially harmful situations
  • ERROR - Error messages for failures that don't stop the application
  • FATAL - Critical errors that may cause the application to terminate

Context Best Practices

Always include relevant context in your logs:

For API Routes

Create logger inside handler to capture the route path automatically. Include method, userId, and operation details as context.

For GraphQL Resolvers

Create logger without resolver name and add operation details in the context when logging.

For Background Jobs

Use createLogger() with a component name and include job-specific context like queueSize and batchId.

What to Log

DO Log:

  • ✅ Request/response lifecycle events
  • ✅ Business logic decisions
  • ✅ External service calls and responses
  • ✅ Performance metrics
  • ✅ Security events (auth failures, access denials)
  • ✅ Errors with full context and stack traces
  • ✅ State changes in critical operations

DON'T Log:

  • ❌ Sensitive data (passwords, tokens, API keys, PII)
  • ❌ Full request/response payloads in production
  • ❌ Noisy information in tight loops
  • ❌ Stack traces for expected errors (e.g., validation failures)

Environment-Specific Behavior

The logging system automatically adjusts based on environment:

Development

  • Pretty-printed, colorized output
  • All log levels enabled (including DEBUG and TRACE)
  • Full stack traces included

Production

  • JSON structured output
  • INFO level and above
  • Optimized for log aggregation systems

Configuration

Configure logging via environment variables:

# Set log level (default: 'info' in production, 'debug' in development)
LOG_LEVEL=debug

# Node environment
NODE_ENV=production

Docker Container Logs

When running in Docker containers, logs are automatically output to stdout in JSON format with ISO 8601 timestamps, uppercase severity levels, component context, and structured fields for easy parsing. The logger uses synchronous writes to ensure logs are immediately flushed to stdout, which is essential for containerized and serverless environments where async logging may not flush before the process exits.

To view debug-level logs in Docker, set the LOG_LEVEL environment variable:

docker run -e LOG_LEVEL=debug substrate-web

These logs can be easily consumed by log aggregation systems like CloudWatch Logs, Datadog, Splunk, ELK Stack, or Grafana Loki.

Troubleshooting

Missing Timestamps

If logs don't show timestamps, ensure you're using the logger from shared-utils instead of console.log.

Logs Not Appearing

Check the LOG_LEVEL environment variable. In production, only INFO and above are logged by default.

Too Verbose Logs

Set LOG_LEVEL=warn or LOG_LEVEL=error to reduce noise.

Migration from console.*

Replace all console.log, console.error, etc. with the appropriate logger. Instead of using console methods, use the logger's methods (info, error, warn, etc.) with proper context objects.

Further Reading