Files
flyer-crawler.projectium.com/docs/adr/ADR-031-granular-debug-logging-strategy.md
Torben Sorensen 4d323a51ca
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 49m39s
fix tour / whats new collision
2026-02-12 04:29:43 -08:00

7.4 KiB

ADR-031: Granular Debug Logging Strategy

Date: 2026-02-10

Status: Accepted

Source: Imported from flyer-crawler project (ADR-052)

Related: ADR-027, ADR-017

Context

Global log levels (INFO vs DEBUG) are too coarse. Developers need to inspect detailed debug information for specific subsystems (e.g., ai-service, db-pool, auth-service) without being flooded by logs from the entire application.

When debugging a specific feature:

  • Setting LOG_LEVEL=debug globally produces too much noise
  • Manually adding/removing debug statements is error-prone
  • No standard way to enable targeted debugging in production

Decision

We will adopt a namespace-based debug filter pattern, similar to the debug npm package, but integrated into our Pino logger.

  1. Logger Namespaces: Every service/module logger must be initialized with a module property (e.g., logger.child({ module: 'ai-service' })).
  2. Environment Filter: We will support a DEBUG_MODULES environment variable that overrides the log level for matching modules.

Implementation

Core Implementation

Implemented in src/services/logger.server.ts:

import pino from 'pino';

// Parse DEBUG_MODULES from environment
const debugModules = (process.env.DEBUG_MODULES || '').split(',').map((s) => s.trim());

// Base logger configuration
export const logger = pino({
  level: process.env.LOG_LEVEL || (process.env.NODE_ENV === 'production' ? 'info' : 'debug'),
  // ... other configuration
});

/**
 * Creates a scoped logger for a specific module.
 * If DEBUG_MODULES includes this module or '*', debug level is enabled.
 */
export const createScopedLogger = (moduleName: string) => {
  // If DEBUG_MODULES contains the module name or "*", force level to 'debug'
  const isDebugEnabled = debugModules.includes('*') || debugModules.includes(moduleName);

  return logger.child({
    module: moduleName,
    level: isDebugEnabled ? 'debug' : logger.level,
  });
};

Service Usage Examples

// src/services/aiService.server.ts
import { createScopedLogger } from './logger.server';

const logger = createScopedLogger('ai-service');

export async function processWithAI(data: unknown) {
  logger.debug({ data }, 'Starting AI processing');
  // ... implementation
  logger.info({ result }, 'AI processing completed');
}
// src/services/authService.server.ts
import { createScopedLogger } from './logger.server';

const logger = createScopedLogger('auth-service');

export async function validateToken(token: string) {
  logger.debug({ tokenLength: token.length }, 'Validating token');
  // ... implementation
}

Module Naming Convention

Use kebab-case suffixed with -service or -worker:

Module Name Purpose File
ai-service AI/external API interactions src/services/aiService.server.ts
auth-service Authentication and authorization src/services/authService.server.ts
db-pool Database connection pooling src/services/database.server.ts
cache-service Redis/caching operations src/services/cacheService.server.ts
queue-worker Background job processing src/workers/queueWorker.ts
email-service Email sending src/services/emailService.server.ts

Usage

Enable Debug Logging for Specific Modules

To debug only AI and authentication:

DEBUG_MODULES=ai-service,auth-service npm run dev

Enable All Debug Logging

Use wildcard to enable debug logging for all modules:

DEBUG_MODULES=* npm run dev

Development Environment

In .env.development:

# Enable debug logging for specific modules during development
DEBUG_MODULES=ai-service

Production Troubleshooting

Temporarily enable debug logging for a specific subsystem:

# SSH into production server
ssh root@example.com

# Set environment variable and restart
DEBUG_MODULES=ai-service pm2 restart app-api

# View logs
pm2 logs app-api --lines 100

# Disable debug logging
pm2 unset DEBUG_MODULES app-api
pm2 restart app-api

With PM2 Configuration

In ecosystem.config.js:

module.exports = {
  apps: [
    {
      name: 'app-api',
      script: 'dist/server.js',
      env: {
        NODE_ENV: 'production',
        // DEBUG_MODULES is unset by default
      },
      env_debug: {
        NODE_ENV: 'production',
        DEBUG_MODULES: 'ai-service,auth-service',
      },
    },
  ],
};

Start with debug logging:

pm2 start ecosystem.config.js --env debug

Best Practices

1. Use Scoped Loggers for Long-Running Services

Services with complex workflows or external API calls should use createScopedLogger to allow targeted debugging:

const logger = createScopedLogger('payment-service');

export async function processPayment(payment: Payment) {
  logger.debug({ paymentId: payment.id }, 'Starting payment processing');

  try {
    const result = await externalPaymentAPI.process(payment);
    logger.debug({ result }, 'External API response');
    return result;
  } catch (error) {
    logger.error({ error, paymentId: payment.id }, 'Payment processing failed');
    throw error;
  }
}

2. Use Child Loggers for Contextual Data

Even within scoped loggers, create child loggers with job/request-specific context:

const logger = createScopedLogger('queue-worker');

async function processJob(job: Job) {
  const jobLogger = logger.child({ jobId: job.id, jobName: job.name });

  jobLogger.debug('Starting job processing');
  // ... processing
  jobLogger.info('Job completed successfully');
}

3. Consistent Debug Message Patterns

Use consistent patterns for debug messages:

// Function entry
logger.debug({ params: sanitizedParams }, 'Function entry: processOrder');

// External API calls
logger.debug({ url, method }, 'External API request');
logger.debug({ statusCode, duration }, 'External API response');

// State changes
logger.debug({ before, after }, 'State transition');

// Decision points
logger.debug({ condition, result }, 'Branch decision');

4. Production Usage Guidelines

  • DEBUG_MODULES can be set in production for temporary debugging
  • Should not be used continuously due to increased log volume
  • Always unset after troubleshooting is complete
  • Monitor log storage when debug logging is enabled

Consequences

Positive

  • Developers can inspect detailed logs for specific subsystems without log flooding
  • Production debugging becomes more targeted and efficient
  • No performance impact when debug logging is disabled
  • Compatible with existing Pino logging infrastructure
  • Follows familiar pattern from debug npm package

Negative

  • Requires developers to know module names (mitigated by documentation)
  • Not all services have adopted scoped loggers yet (gradual migration)
  • Additional configuration complexity

References