Skip to content

Logger Configuration

Comprehensive guide to configuring Winston logging in the AI-Enhanced TypeScript Template.

Configuration Interface

The logger configuration is defined by the LoggerConfig interface:

typescript
interface LoggerConfig {
  level: 'error' | 'warn' | 'info' | 'debug';
  outputFile: string | null;
  includeTimestamp: boolean;
  useColors: boolean;
}

Configuration Methods

1. Application Config File

Configure logging through your main application configuration:

json
{
  "app": {
    "name": "my-app",
    "version": "1.0.0",
    "environment": "development"
  },
  "logging": {
    "level": "debug",
    "outputFile": "logs/app.log",
    "includeTimestamp": true,
    "useColors": true
  }
}
typescript
import { ConfigManager } from './config.js';
import { createLoggerFromConfig } from './logger.js';

const configManager = new ConfigManager();
const configResult = configManager.loadConfig();

if (configResult.success) {
  const logger = createLoggerFromConfig(configResult.data.logging);
}

2. Environment Variables

Override configuration using environment variables:

bash
# Set log level
export LOG_LEVEL=info

# Set output file
export LOG_FILE=production.log

# Enable/disable colors
export LOG_COLORS=false

# Enable/disable timestamps
export LOG_TIMESTAMP=true
typescript
import { createLogger } from './logger.js';

const logger = createLogger({
  level: (process.env.LOG_LEVEL as any) || 'info',
  outputFile: process.env.LOG_FILE || null,
  useColors: process.env.LOG_COLORS === 'true',
  includeTimestamp: process.env.LOG_TIMESTAMP !== 'false'
});

3. Direct Configuration

Create loggers with specific configurations:

typescript
import { createLogger } from './logger.js';

// Development configuration
const devLogger = createLogger({
  level: 'debug',
  outputFile: null, // Console only
  includeTimestamp: true,
  useColors: true
});

// Production configuration
const prodLogger = createLogger({
  level: 'info',
  outputFile: 'logs/production.log',
  includeTimestamp: true,
  useColors: false
});

// Test configuration
const testLogger = createLogger({
  level: 'error',
  outputFile: null,
  includeTimestamp: false,
  useColors: false
});

Environment-Specific Configurations

Development Environment

typescript
const developmentConfig: LoggerConfig = {
  level: 'debug',           // Show all log messages
  outputFile: null,         // Console output only
  includeTimestamp: true,   // Include timestamps for debugging
  useColors: true          // Colorized output for readability
};

Features:

  • Debug level logging - See all log messages including debug info
  • Console output only - No file logging needed in development
  • Colored output - Easy to distinguish log levels visually
  • Timestamps - Help track timing of operations

Production Environment

typescript
const productionConfig: LoggerConfig = {
  level: 'info',                    // Info and above (no debug logs)
  outputFile: 'logs/app.log',      // File logging for persistence
  includeTimestamp: true,          // Essential for production logs
  useColors: false                 // No colors in log files
};

Features:

  • Info level logging - Reduces log volume while keeping important messages
  • File output - Persistent logs for analysis and debugging
  • No colors - Clean log files without ANSI color codes
  • Timestamps - Critical for production log analysis

Test Environment

typescript
const testConfig: LoggerConfig = {
  level: 'error',          // Only log errors during tests
  outputFile: null,        // Console output for test runners
  includeTimestamp: false, // Cleaner test output
  useColors: false        // Consistent output across environments
};

Features:

  • Error level only - Minimize test output noise
  • No timestamps - Cleaner test output
  • No colors - Consistent across different test environments
  • Console only - No file I/O during tests

Advanced Configuration

Multiple Loggers

Create specialized loggers for different components:

typescript
// Database logger
const dbLogger = createLogger({
  level: 'debug',
  outputFile: 'logs/database.log',
  includeTimestamp: true,
  useColors: false
});

// API logger
const apiLogger = createLogger({
  level: 'info',
  outputFile: 'logs/api.log',
  includeTimestamp: true,
  useColors: false
});

// Security logger
const securityLogger = createLogger({
  level: 'warn',
  outputFile: 'logs/security.log',
  includeTimestamp: true,
  useColors: false
});

Custom Transport Configuration

For advanced use cases, configure Winston transports directly:

typescript
import winston from 'winston';

const customLogger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.errors({ stack: true }),
    winston.format.json()
  ),
  transports: [
    // Console transport with colors
    new winston.transports.Console({
      format: winston.format.combine(
        winston.format.colorize(),
        winston.format.simple()
      )
    }),
    
    // File transport for all logs
    new winston.transports.File({
      filename: 'logs/combined.log'
    }),
    
    // Separate file for errors
    new winston.transports.File({
      filename: 'logs/error.log',
      level: 'error'
    })
  ]
});

Configuration Validation

The logger configuration is validated using Zod schemas:

typescript
import { z } from 'zod';

const LoggerConfigSchema = z.object({
  level: z.enum(['error', 'warn', 'info', 'debug']),
  outputFile: z.string().nullable(),
  includeTimestamp: z.boolean(),
  useColors: z.boolean()
});

// Validate configuration before creating logger
function createValidatedLogger(config: unknown) {
  const result = LoggerConfigSchema.safeParse(config);
  
  if (!result.success) {
    throw new Error(`Invalid logger configuration: ${result.error.message}`);
  }
  
  return createLogger(result.data);
}

Dynamic Configuration

Runtime Configuration Changes

typescript
class ConfigurableLogger {
  private logger: winston.Logger;
  private config: LoggerConfig;
  
  constructor(initialConfig: LoggerConfig) {
    this.config = initialConfig;
    this.logger = createLogger(initialConfig);
  }
  
  updateConfig(newConfig: Partial<LoggerConfig>) {
    this.config = { ...this.config, ...newConfig };
    
    // Recreate logger with new configuration
    this.logger = createLogger(this.config);
  }
  
  setLevel(level: LoggerConfig['level']) {
    this.updateConfig({ level });
  }
  
  enableFileLogging(filename: string) {
    this.updateConfig({ outputFile: filename });
  }
  
  disableFileLogging() {
    this.updateConfig({ outputFile: null });
  }
}

// Usage
const configurableLogger = new ConfigurableLogger({
  level: 'info',
  outputFile: null,
  includeTimestamp: true,
  useColors: true
});

// Change log level at runtime
configurableLogger.setLevel('debug');

// Enable file logging
configurableLogger.enableFileLogging('runtime.log');

Configuration Hot Reloading

typescript
import { watch } from 'fs';

class HotReloadLogger {
  private logger: winston.Logger;
  private configPath: string;
  
  constructor(configPath: string) {
    this.configPath = configPath;
    this.loadConfig();
    this.watchConfig();
  }
  
  private loadConfig() {
    try {
      const configData = JSON.parse(fs.readFileSync(this.configPath, 'utf-8'));
      const result = LoggerConfigSchema.safeParse(configData.logging);
      
      if (result.success) {
        this.logger = createLogger(result.data);
        console.log('Logger configuration loaded successfully');
      } else {
        console.error('Invalid logger configuration:', result.error.message);
      }
    } catch (error) {
      console.error('Failed to load logger configuration:', error.message);
    }
  }
  
  private watchConfig() {
    watch(this.configPath, (eventType) => {
      if (eventType === 'change') {
        console.log('Configuration file changed, reloading...');
        this.loadConfig();
      }
    });
  }
  
  getLogger() {
    return this.logger;
  }
}

Configuration Best Practices

Environment Detection

typescript
function getEnvironmentConfig(): LoggerConfig {
  const env = process.env.NODE_ENV || 'development';
  
  switch (env) {
    case 'production':
      return {
        level: 'info',
        outputFile: 'logs/production.log',
        includeTimestamp: true,
        useColors: false
      };
      
    case 'test':
      return {
        level: 'error',
        outputFile: null,
        includeTimestamp: false,
        useColors: false
      };
      
    case 'development':
    default:
      return {
        level: 'debug',
        outputFile: null,
        includeTimestamp: true,
        useColors: true
      };
  }
}

const logger = createLogger(getEnvironmentConfig());

Configuration Hierarchy

typescript
function createConfiguredLogger(): winston.Logger {
  // 1. Start with defaults
  let config: LoggerConfig = {
    level: 'info',
    outputFile: null,
    includeTimestamp: true,
    useColors: true
  };
  
  // 2. Override with environment-specific defaults
  const envConfig = getEnvironmentConfig();
  config = { ...config, ...envConfig };
  
  // 3. Override with application config file
  const appConfig = loadApplicationConfig();
  if (appConfig?.logging) {
    config = { ...config, ...appConfig.logging };
  }
  
  // 4. Override with environment variables
  if (process.env.LOG_LEVEL) {
    config.level = process.env.LOG_LEVEL as LoggerConfig['level'];
  }
  if (process.env.LOG_FILE) {
    config.outputFile = process.env.LOG_FILE;
  }
  if (process.env.LOG_COLORS) {
    config.useColors = process.env.LOG_COLORS === 'true';
  }
  
  return createLogger(config);
}

Configuration Validation and Fallbacks

typescript
function createRobustLogger(config: unknown): winston.Logger {
  // Validate configuration
  const validationResult = LoggerConfigSchema.safeParse(config);
  
  if (validationResult.success) {
    return createLogger(validationResult.data);
  }
  
  // Log validation error and use fallback
  console.warn('Invalid logger configuration, using fallback:', validationResult.error.message);
  
  const fallbackConfig: LoggerConfig = {
    level: 'info',
    outputFile: null,
    includeTimestamp: true,
    useColors: false
  };
  
  return createLogger(fallbackConfig);
}

Troubleshooting Configuration

Common Issues

  1. File Permission Errors
typescript
// Ensure log directory exists and is writable
import { mkdir } from 'fs/promises';
import { dirname } from 'path';

async function ensureLogDirectory(logFile: string) {
  try {
    await mkdir(dirname(logFile), { recursive: true });
  } catch (error) {
    console.error('Failed to create log directory:', error.message);
  }
}
  1. Invalid Log Levels
typescript
function validateLogLevel(level: string): LoggerConfig['level'] {
  const validLevels = ['error', 'warn', 'info', 'debug'];
  if (validLevels.includes(level)) {
    return level as LoggerConfig['level'];
  }
  console.warn(`Invalid log level '${level}', using 'info'`);
  return 'info';
}
  1. Configuration Loading Errors
typescript
function safeLoadConfig(configPath: string): LoggerConfig | null {
  try {
    const configData = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
    const result = LoggerConfigSchema.safeParse(configData.logging);
    
    if (result.success) {
      return result.data;
    } else {
      console.error('Configuration validation failed:', result.error.message);
      return null;
    }
  } catch (error) {
    console.error('Failed to load configuration:', error.message);
    return null;
  }
}

Next Steps

Released under the MIT License.