Files
supervisor-ui/lib/utils/logger.ts

137 lines
3.4 KiB
TypeScript
Raw Normal View History

feat: implement comprehensive logging infrastructure (Phases 1-3) Added production-ready logging using Pino with structured JSON output, pretty printing in development, and automatic sensitive data redaction. ## Phase 1: Core Logger Setup - Installed pino, pino-http, and pino-pretty dependencies - Created logger utility (lib/utils/logger.ts): - Environment-based log levels (debug in dev, info in prod) - Pretty printing with colors in development - JSON structured logs in production - Sensitive data redaction (passwords, tokens, auth headers) - Custom serializers for errors and requests - Helper functions for child loggers and timing - Added LOG_LEVEL environment variable to .env.example - Configured Next.js for Turbopack with external pino packages ## Phase 2: API Request Logging - Created API logger wrapper (lib/utils/api-logger.ts): - withLogging() HOF for wrapping API route handlers - Automatic request/response logging with timing - Correlation ID generation (X-Request-ID header) - Error catching and structured error responses - logPerformance() helper for timing operations - createApiLogger() for manual logging in routes ## Phase 3: Supervisor Client Logging - Updated lib/supervisor/client.ts: - Added logger instance to SupervisorClient class - Comprehensive XML-RPC call logging (method, params, duration) - Error logging with full context and stack traces - Success logging with result size tracking - DEBUG level logs for all XML-RPC operations - Constructor logging for client initialization ## Configuration Changes - Updated next.config.ts for Turbopack compatibility - Added serverComponentsExternalPackages for pino - Removed null-loader workaround (not needed) ## Features Implemented ✅ Request correlation IDs for tracing ✅ Performance timing for all operations ✅ Sensitive data redaction (passwords, auth) ✅ Environment-based log formatting ✅ Structured JSON logs for production ✅ Pretty colored logs for development ✅ Error serialization with stack traces ✅ Ready for log aggregation (stdout/JSON) ## Next Steps (Phases 4-7) - Update ~30 API routes with logging - Add React Query/hooks error logging - Implement client-side error boundary - Add documentation and testing 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-23 20:44:46 +01:00
import pino from 'pino';
// Determine environment
const isDevelopment = process.env.NODE_ENV === 'development';
const logLevel = (process.env.LOG_LEVEL || (isDevelopment ? 'debug' : 'info')) as pino.Level;
// Custom serializers for better log output
const serializers = {
error: pino.stdSerializers.err,
req: (req: any) => ({
id: req.id,
method: req.method,
url: req.url,
query: req.query,
params: req.params,
remoteAddress: req.headers?.['x-forwarded-for'] || req.socket?.remoteAddress,
userAgent: req.headers?.['user-agent'],
}),
res: (res: any) => ({
statusCode: res.statusCode,
}),
};
// Sensitive fields to redact from logs
const redactPaths = [
'password',
'token',
'secret',
'apiKey',
'authorization',
'*.password',
'*.token',
'*.secret',
'*.apiKey',
'*.authorization',
'req.headers.authorization',
'req.headers.cookie',
'SUPERVISOR_PASSWORD',
'SUPERVISOR_USERNAME',
];
// Create base logger
export const logger = pino({
level: logLevel,
// Pretty printing in development, JSON in production
transport: isDevelopment
? {
target: 'pino-pretty',
options: {
colorize: true,
translateTime: 'HH:MM:ss',
ignore: 'pid,hostname',
singleLine: false,
},
}
: undefined,
// Redact sensitive information
redact: {
paths: redactPaths,
censor: '[REDACTED]',
},
// Custom serializers
serializers,
// Add timestamp in production
timestamp: isDevelopment ? false : pino.stdTimeFunctions.isoTime,
// Base context
base: isDevelopment
? undefined
: {
pid: process.pid,
hostname: process.env.HOSTNAME || 'unknown',
},
});
/**
* Create a child logger with additional context
* @param context - Additional context to add to all log messages
* @returns Child logger instance
*/
export function createLogger(context: Record<string, any>) {
return logger.child(context);
}
/**
* Generate a unique request ID
* @returns Unique request identifier
*/
export function generateRequestId(): string {
return `req-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}
/**
* Create a logger for API operations
* @param operation - The operation name (e.g., 'startProcess', 'stopProcess')
* @param metadata - Additional metadata
* @returns Child logger with operation context
*/
export function createOperationLogger(operation: string, metadata: Record<string, any> = {}) {
return createLogger({
operation,
...metadata,
});
}
/**
* Log timing information for an operation
* @param logger - Logger instance
* @param operation - Operation name
* @param startTime - Start time from performance.now() or Date.now()
*/
export function logTiming(logger: pino.Logger, operation: string, startTime: number) {
const duration = Date.now() - startTime;
logger.info({ operation, duration }, `${operation} completed in ${duration}ms`);
}
/**
* Format error for logging
* @param error - Error object or message
* @returns Formatted error object
*/
export function formatError(error: unknown): { message: string; stack?: string; code?: string } {
if (error instanceof Error) {
return {
message: error.message,
stack: error.stack,
code: (error as any).code,
};
}
if (typeof error === 'string') {
return { message: error };
}
return { message: 'Unknown error', stack: JSON.stringify(error) };
}
// Export default logger
export default logger;