Skip to content

Logger Utilities

The AI-Enhanced TypeScript Template includes a comprehensive set of utility functions to make logging more consistent and convenient.

Logger Utility Functions

logValidationSuccess

Logs successful validation operations with consistent formatting.

typescript
loggerUtils.logValidationSuccess(
  logger: ILogger,
  message: string,
  data?: object
): void

Usage:

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

const result = validateUserInput(userData);
if (result.success) {
  loggerUtils.logValidationSuccess(
    logger,
    'User input validated successfully',
    { userId: result.data.name, email: result.data.email }
  );
}

Output:

2024-01-15 10:30:45 [INFO]: ✅ User input validated successfully { userId: "john_doe", email: "john@example.com" }

logValidationFailure

Logs validation failures with error details.

typescript
loggerUtils.logValidationFailure(
  logger: ILogger,
  message: string,
  error: string | Error
): void

Usage:

typescript
const result = validateAppConfig(configData);
if (!result.success) {
  loggerUtils.logValidationFailure(
    logger,
    'Configuration validation failed',
    result.error
  );
}

Output:

2024-01-15 10:30:46 [ERROR]: ❌ Configuration validation failed: app.name: String must contain at least 1 character(s)

logSectionHeader

Creates visually distinct section headers in logs for better organization.

typescript
loggerUtils.logSectionHeader(
  logger: ILogger,
  title: string
): void

Usage:

typescript
loggerUtils.logSectionHeader(logger, 'Database Migration');
logger.info('Starting migration process...');
logger.info('Creating tables...');
logger.info('Seeding initial data...');
loggerUtils.logSectionHeader(logger, 'Server Startup');

Output:

2024-01-15 10:30:47 [INFO]: 
2024-01-15 10:30:47 [INFO]: ═══════════════════════════════════════
2024-01-15 10:30:47 [INFO]: 🔧 Database Migration
2024-01-15 10:30:47 [INFO]: ═══════════════════════════════════════
2024-01-15 10:30:47 [INFO]:

logStartup

Logs application startup information with version details.

typescript
loggerUtils.logStartup(
  logger: ILogger,
  appName: string,
  version?: string
): void

Usage:

typescript
// With version
loggerUtils.logStartup(logger, 'MyApp', '1.2.3');

// Without version
loggerUtils.logStartup(logger, 'MyApp');

Output:

2024-01-15 10:30:48 [INFO]: 🚀 Starting MyApp v1.2.3

logCompletion

Logs completion of operations or processes.

typescript
loggerUtils.logCompletion(
  logger: ILogger,
  message: string
): void

Usage:

typescript
loggerUtils.logCompletion(logger, 'Database migration completed successfully');
loggerUtils.logCompletion(logger, 'Server initialization finished');

Output:

2024-01-15 10:30:49 [INFO]: ✅ Database migration completed successfully

ILogger Interface

The template provides a ILogger interface for dependency injection and testing:

typescript
interface ILogger {
  error(message: string, meta?: object): void;
  warn(message: string, meta?: object): void;
  info(message: string, meta?: object): void;
  debug(message: string, meta?: object): void;
}

Usage in Classes

typescript
class UserService {
  constructor(private logger: ILogger) {}
  
  async createUser(userData: unknown) {
    loggerUtils.logSectionHeader(this.logger, 'User Creation');
    
    const result = validateUserInput(userData);
    if (!result.success) {
      loggerUtils.logValidationFailure(
        this.logger,
        'User creation failed - invalid input',
        result.error
      );
      throw new Error(`Invalid user data: ${result.error}`);
    }
    
    loggerUtils.logValidationSuccess(
      this.logger,
      'User input validated',
      { email: result.data.email }
    );
    
    try {
      const user = await this.database.createUser(result.data);
      this.logger.info('User created successfully', { userId: user.id });
      return user;
    } catch (error) {
      this.logger.error('Database error during user creation', { error: error.message });
      throw error;
    }
  }
}

// Usage
const userService = new UserService(logger);

Testing with Mock Logger

typescript
import { jest } from '@jest/globals';

// Create mock logger
const mockLogger: ILogger = {
  error: jest.fn(),
  warn: jest.fn(),
  info: jest.fn(),
  debug: jest.fn()
};

// Test with mock
describe('UserService', () => {
  it('should log validation success', async () => {
    const userService = new UserService(mockLogger);
    
    await userService.createUser({
      name: 'John Doe',
      email: 'john@example.com'
    });
    
    expect(mockLogger.info).toHaveBeenCalledWith(
      expect.stringContaining('User input validated'),
      expect.objectContaining({ email: 'john@example.com' })
    );
  });
});

Logger Adapter

The createLoggerAdapter function creates an adapter that implements the ILogger interface:

typescript
function createLoggerAdapter(winstonLogger: winston.Logger): ILogger {
  return {
    error: (message: string, meta?: object) => winstonLogger.error(message, meta),
    warn: (message: string, meta?: object) => winstonLogger.warn(message, meta),
    info: (message: string, meta?: object) => winstonLogger.info(message, meta),
    debug: (message: string, meta?: object) => winstonLogger.debug(message, meta)
  };
}

Usage:

typescript
import winston from 'winston';
import { createLoggerAdapter } from './logger.js';

// Create Winston logger
const winstonLogger = winston.createLogger({
  level: 'info',
  format: winston.format.json(),
  transports: [new winston.transports.Console()]
});

// Create adapter
const logger = createLoggerAdapter(winstonLogger);

// Use with utility functions
loggerUtils.logStartup(logger, 'MyApp', '1.0.0');

Custom Utility Functions

Creating Custom Utilities

typescript
// Add to loggerUtils object
const customLoggerUtils = {
  ...loggerUtils,
  
  logApiRequest(logger: ILogger, method: string, path: string, statusCode: number, duration: number) {
    const emoji = statusCode >= 400 ? '❌' : statusCode >= 300 ? '⚠️' : '✅';
    logger.info(`${emoji} ${method} ${path}`, {
      statusCode,
      duration: `${duration}ms`,
      timestamp: new Date().toISOString()
    });
  },
  
  logDatabaseOperation(logger: ILogger, operation: string, table: string, duration: number) {
    logger.debug(`🗄️ Database ${operation}`, {
      table,
      duration: `${duration}ms`,
      operation
    });
  },
  
  logSecurityEvent(logger: ILogger, event: string, details: object) {
    logger.warn(`🔒 Security Event: ${event}`, {
      event,
      timestamp: new Date().toISOString(),
      ...details
    });
  },
  
  logPerformanceMetric(logger: ILogger, metric: string, value: number, unit: string) {
    logger.info(`📊 Performance: ${metric}`, {
      metric,
      value,
      unit,
      timestamp: new Date().toISOString()
    });
  }
};

Usage:

typescript
// API request logging
customLoggerUtils.logApiRequest(logger, 'GET', '/api/users', 200, 45);

// Database operation logging
customLoggerUtils.logDatabaseOperation(logger, 'SELECT', 'users', 12);

// Security event logging
customLoggerUtils.logSecurityEvent(logger, 'Failed Login Attempt', {
  ip: '192.168.1.100',
  username: 'admin',
  attempts: 3
});

// Performance metric logging
customLoggerUtils.logPerformanceMetric(logger, 'Response Time', 150, 'ms');

Middleware Integration

typescript
// Express.js middleware
function createLoggingMiddleware(logger: ILogger) {
  return (req: Request, res: Response, next: NextFunction) => {
    const start = Date.now();
    
    // Log request
    logger.info('Incoming request', {
      method: req.method,
      path: req.path,
      ip: req.ip,
      userAgent: req.get('User-Agent')
    });
    
    // Override res.end to log response
    const originalEnd = res.end;
    res.end = function(...args) {
      const duration = Date.now() - start;
      customLoggerUtils.logApiRequest(logger, req.method, req.path, res.statusCode, duration);
      originalEnd.apply(this, args);
    };
    
    next();
  };
}

// Usage
app.use(createLoggingMiddleware(logger));

Structured Logging Patterns

Request Context Logging

typescript
class RequestLogger {
  constructor(
    private logger: ILogger,
    private requestId: string,
    private userId?: string
  ) {}
  
  info(message: string, meta?: object) {
    this.logger.info(message, {
      requestId: this.requestId,
      userId: this.userId,
      ...meta
    });
  }
  
  error(message: string, meta?: object) {
    this.logger.error(message, {
      requestId: this.requestId,
      userId: this.userId,
      ...meta
    });
  }
  
  // Add other log levels as needed
}

// Usage in request handler
app.get('/api/users/:id', (req, res) => {
  const requestLogger = new RequestLogger(
    logger,
    req.headers['x-request-id'] as string,
    req.user?.id
  );
  
  requestLogger.info('Fetching user', { userId: req.params.id });
  
  // ... rest of handler
});

Error Context Logging

typescript
function logErrorWithContext(
  logger: ILogger,
  error: Error,
  context: {
    operation: string;
    userId?: string;
    requestId?: string;
    additionalData?: object;
  }
) {
  logger.error(`Error in ${context.operation}`, {
    error: {
      name: error.name,
      message: error.message,
      stack: error.stack
    },
    context: {
      operation: context.operation,
      userId: context.userId,
      requestId: context.requestId,
      timestamp: new Date().toISOString(),
      ...context.additionalData
    }
  });
}

// Usage
try {
  await processPayment(paymentData);
} catch (error) {
  logErrorWithContext(logger, error, {
    operation: 'payment_processing',
    userId: user.id,
    requestId: req.id,
    additionalData: { amount: paymentData.amount, currency: paymentData.currency }
  });
  throw error;
}

Performance Considerations

Conditional Logging

typescript
// Only format expensive log data if the level will be logged
function expensiveDebugLog(logger: ILogger, message: string, dataProvider: () => object) {
  if (logger.debug) { // Check if debug logging is enabled
    logger.debug(message, dataProvider());
  }
}

// Usage
expensiveDebugLog(logger, 'Complex operation result', () => ({
  result: JSON.stringify(complexObject),
  metrics: calculateExpensiveMetrics(),
  analysis: performComplexAnalysis()
}));

Async Logging for High-Throughput

typescript
class AsyncLogger {
  private logQueue: Array<{ level: string; message: string; meta?: object }> = [];
  private processing = false;
  
  constructor(private logger: ILogger) {
    this.startProcessing();
  }
  
  info(message: string, meta?: object) {
    this.logQueue.push({ level: 'info', message, meta });
  }
  
  error(message: string, meta?: object) {
    this.logQueue.push({ level: 'error', message, meta });
  }
  
  private async startProcessing() {
    if (this.processing) return;
    this.processing = true;
    
    while (this.logQueue.length > 0) {
      const logEntry = this.logQueue.shift();
      if (logEntry) {
        (this.logger as any)[logEntry.level](logEntry.message, logEntry.meta);
      }
      
      // Small delay to prevent blocking
      await new Promise(resolve => setImmediate(resolve));
    }
    
    this.processing = false;
    
    // Continue processing if more logs were added
    if (this.logQueue.length > 0) {
      setImmediate(() => this.startProcessing());
    }
  }
}

Next Steps

Released under the MIT License.