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
- 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);
}
}
- 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';
}
- 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;
}
}