7.4 KiB
ADR-031: Granular Debug Logging Strategy
Date: 2026-02-10
Status: Accepted
Source: Imported from flyer-crawler project (ADR-052)
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=debugglobally 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.
- Logger Namespaces: Every service/module logger must be initialized with a
moduleproperty (e.g.,logger.child({ module: 'ai-service' })). - Environment Filter: We will support a
DEBUG_MODULESenvironment 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_MODULEScan 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
debugnpm 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
- ADR-027: Application-Wide Structured Logging
- ADR-017: Structured Logging with Pino
- debug npm package - Inspiration for namespace pattern
- Pino Child Loggers