137 lines
3.4 KiB
TypeScript
137 lines
3.4 KiB
TypeScript
|
|
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;
|