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