Improve logging
This commit is contained in:
parent
06a6b5c7aa
commit
888ca48712
@ -1,3 +1,5 @@
|
||||
import { inspect } from 'util';
|
||||
|
||||
interface LogContext {
|
||||
requestId?: string;
|
||||
userId?: string;
|
||||
@ -9,36 +11,194 @@ interface LogContext {
|
||||
}
|
||||
|
||||
export class Logger {
|
||||
private static getLogLevel(): string {
|
||||
return process.env.LOG_LEVEL || (process.env.NODE_ENV === 'development' ? 'debug' : 'info');
|
||||
}
|
||||
|
||||
private static shouldLog(level: string): boolean {
|
||||
const currentLevel = this.getLogLevel().toLowerCase();
|
||||
const levels = ['error', 'warn', 'info', 'debug'];
|
||||
const currentIndex = levels.indexOf(currentLevel);
|
||||
const messageIndex = levels.indexOf(level.toLowerCase());
|
||||
|
||||
return messageIndex <= currentIndex;
|
||||
}
|
||||
|
||||
private static formatMessage(level: string, message: string, context?: LogContext): string {
|
||||
const timestamp = new Date().toISOString();
|
||||
const baseLog = {
|
||||
timestamp,
|
||||
level,
|
||||
message,
|
||||
...context
|
||||
};
|
||||
const isDevelopment = process.env.NODE_ENV === 'development';
|
||||
const supportsColors = this.supportsColors();
|
||||
|
||||
// Create a more readable header with level, timestamp, and message
|
||||
const levelSymbol = this.getLevelSymbol(level);
|
||||
const levelColor = this.getLevelColor(level, isDevelopment && supportsColors);
|
||||
const timestampFormatted = (isDevelopment && supportsColors) ?
|
||||
`\x1b[90m${timestamp}\x1b[0m` : // Gray timestamp in dev
|
||||
timestamp;
|
||||
|
||||
const header = (isDevelopment && supportsColors) ?
|
||||
`${levelColor}${levelSymbol} [${level}]\x1b[0m ${timestampFormatted} \x1b[1m${message}\x1b[0m` :
|
||||
`${levelSymbol} [${level}] ${timestamp} ${message}`;
|
||||
|
||||
// Format context data if present
|
||||
if (context && Object.keys(context).length > 0) {
|
||||
const contextFormatted = inspect(context, {
|
||||
depth: isDevelopment ? 10 : 6, // Much deeper inspection
|
||||
colors: isDevelopment && supportsColors,
|
||||
compact: false,
|
||||
breakLength: 100,
|
||||
maxArrayLength: isDevelopment ? 20 : 10, // Show more array elements
|
||||
maxStringLength: isDevelopment ? 500 : 200, // Longer strings
|
||||
showHidden: false,
|
||||
sorted: true,
|
||||
getters: false,
|
||||
showProxy: false, // Don't show proxy details
|
||||
customInspect: true // Use custom inspect methods
|
||||
});
|
||||
|
||||
// Add visual separation
|
||||
const separator = (isDevelopment && supportsColors) ?
|
||||
`\x1b[90m${'─'.repeat(80)}\x1b[0m` :
|
||||
'─'.repeat(80);
|
||||
|
||||
return `${header}\n${separator}\n${contextFormatted}`;
|
||||
}
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
return JSON.stringify(baseLog, null, process.env.NODE_ENV === 'development' ? 2 : 0);
|
||||
private static supportsColors(): boolean {
|
||||
// Check if colors are explicitly disabled
|
||||
if (process.env.FORCE_COLOR === '0') return false;
|
||||
if (process.env.NO_COLOR) return false;
|
||||
if (process.env.TERM === 'dumb') return false;
|
||||
|
||||
// Check if colors are explicitly enabled
|
||||
if (process.env.FORCE_COLOR === '1' || process.env.FORCE_COLOR === '2' || process.env.FORCE_COLOR === '3') return true;
|
||||
|
||||
// Check if we're in a TTY
|
||||
if (process.stdout && !process.stdout.isTTY) return false;
|
||||
|
||||
// Check terminal capabilities - be more permissive
|
||||
if (process.env.TERM && (
|
||||
process.env.TERM.includes('256color') ||
|
||||
process.env.TERM.includes('truecolor') ||
|
||||
process.env.TERM.includes('xterm') ||
|
||||
process.env.TERM.includes('screen') ||
|
||||
process.env.TERM.includes('tmux') ||
|
||||
process.env.TERM.includes('color')
|
||||
)) return true;
|
||||
|
||||
// Check COLORTERM
|
||||
if (process.env.COLORTERM) return true;
|
||||
|
||||
// Default to true for development mode if we have a TTY
|
||||
return process.env.NODE_ENV === 'development' && process.stdout.isTTY;
|
||||
}
|
||||
|
||||
private static getLevelSymbol(level: string): string {
|
||||
const symbols = {
|
||||
'INFO': 'ℹ️ ',
|
||||
'DEBUG': '🐛',
|
||||
'WARN': '⚠️ ',
|
||||
'ERROR': '❌'
|
||||
};
|
||||
return symbols[level as keyof typeof symbols] || '📝';
|
||||
}
|
||||
|
||||
private static getLevelPrefix(level: string, supportsColors: boolean): string {
|
||||
if (supportsColors) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Provide visual distinction without colors
|
||||
const prefixes = {
|
||||
'ERROR': '>>> ',
|
||||
'WARN': '>>> ',
|
||||
'INFO': '>>> ',
|
||||
'DEBUG': '>>> '
|
||||
};
|
||||
return prefixes[level as keyof typeof prefixes] || '>>> ';
|
||||
}
|
||||
|
||||
private static getLevelColor(level: string, isDevelopment: boolean): string {
|
||||
if (!isDevelopment) return '';
|
||||
|
||||
const colors = {
|
||||
'INFO': '\x1b[36m', // Cyan
|
||||
'DEBUG': '\x1b[35m', // Magenta
|
||||
'WARN': '\x1b[33m', // Yellow
|
||||
'ERROR': '\x1b[31m' // Red
|
||||
};
|
||||
return colors[level as keyof typeof colors] || '\x1b[37m'; // White default
|
||||
}
|
||||
|
||||
static info(message: string, context?: LogContext): void {
|
||||
console.log(this.formatMessage('INFO', message, context));
|
||||
if (this.shouldLog('info')) {
|
||||
console.log(this.formatMessage('INFO', message, context));
|
||||
}
|
||||
}
|
||||
|
||||
static warn(message: string, context?: LogContext): void {
|
||||
console.warn(this.formatMessage('WARN', message, context));
|
||||
if (this.shouldLog('warn')) {
|
||||
console.warn(this.formatMessage('WARN', message, context));
|
||||
}
|
||||
}
|
||||
|
||||
static error(message: string, context?: LogContext): void {
|
||||
console.error(this.formatMessage('ERROR', message, context));
|
||||
if (this.shouldLog('error')) {
|
||||
console.error(this.formatMessage('ERROR', message, context));
|
||||
}
|
||||
}
|
||||
|
||||
static debug(message: string, context?: LogContext): void {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
if (this.shouldLog('debug')) {
|
||||
console.debug(this.formatMessage('DEBUG', message, context));
|
||||
}
|
||||
}
|
||||
|
||||
// Enhanced logging methods that automatically detect and format objects
|
||||
static logObject(message: string, obj: any, level: 'info' | 'debug' | 'warn' | 'error' = 'info'): void {
|
||||
const isDevelopment = process.env.NODE_ENV === 'development';
|
||||
const supportsColors = this.supportsColors();
|
||||
|
||||
// Create a more readable object representation
|
||||
const formattedObj = inspect(obj, {
|
||||
depth: isDevelopment ? 10 : 6, // Much deeper inspection
|
||||
colors: isDevelopment && supportsColors,
|
||||
compact: false, // Always use expanded format for objects
|
||||
breakLength: 100,
|
||||
maxArrayLength: isDevelopment ? 20 : 10,
|
||||
maxStringLength: isDevelopment ? 500 : 200,
|
||||
showHidden: false,
|
||||
sorted: true,
|
||||
getters: false,
|
||||
showProxy: false,
|
||||
customInspect: true
|
||||
});
|
||||
|
||||
const context = {
|
||||
objectType: obj?.constructor?.name || typeof obj,
|
||||
objectSize: JSON.stringify(obj).length,
|
||||
formattedObject: formattedObj
|
||||
};
|
||||
|
||||
switch (level) {
|
||||
case 'info':
|
||||
this.info(message, context);
|
||||
break;
|
||||
case 'debug':
|
||||
this.debug(message, context);
|
||||
break;
|
||||
case 'warn':
|
||||
this.warn(message, context);
|
||||
break;
|
||||
case 'error':
|
||||
this.error(message, context);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Helper for HTTP request logging
|
||||
static logRequest(req: any, res: any, duration: number): void {
|
||||
const context = {
|
||||
@ -78,4 +238,37 @@ export class Logger {
|
||||
this.error(message, logContext);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Helper for logging API responses with better formatting
|
||||
static logApiResponse(operation: string, response: any, level: 'info' | 'debug' | 'warn' | 'error' = 'info'): void {
|
||||
const context = {
|
||||
operation,
|
||||
responseType: response?.constructor?.name || typeof response,
|
||||
statusCode: response?.status || response?.statusCode,
|
||||
hasData: !!response?.data,
|
||||
dataKeys: response?.data ? Object.keys(response.data) : [],
|
||||
responseSize: JSON.stringify(response).length
|
||||
};
|
||||
|
||||
// Add the actual response data for detailed inspection
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
context['responseData'] = response;
|
||||
}
|
||||
|
||||
switch (level) {
|
||||
case 'info':
|
||||
this.info(`API Response: ${operation}`, context);
|
||||
break;
|
||||
case 'debug':
|
||||
this.debug(`API Response: ${operation}`, context);
|
||||
break;
|
||||
case 'warn':
|
||||
this.warn(`API Response: ${operation}`, context);
|
||||
break;
|
||||
case 'error':
|
||||
this.error(`API Response: ${operation}`, context);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user