Improve logging
This commit is contained in:
parent
06a6b5c7aa
commit
888ca48712
@ -1,3 +1,5 @@
|
|||||||
|
import { inspect } from 'util';
|
||||||
|
|
||||||
interface LogContext {
|
interface LogContext {
|
||||||
requestId?: string;
|
requestId?: string;
|
||||||
userId?: string;
|
userId?: string;
|
||||||
@ -9,36 +11,194 @@ interface LogContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class Logger {
|
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 {
|
private static formatMessage(level: string, message: string, context?: LogContext): string {
|
||||||
const timestamp = new Date().toISOString();
|
const timestamp = new Date().toISOString();
|
||||||
const baseLog = {
|
const isDevelopment = process.env.NODE_ENV === 'development';
|
||||||
timestamp,
|
const supportsColors = this.supportsColors();
|
||||||
level,
|
|
||||||
message,
|
// Create a more readable header with level, timestamp, and message
|
||||||
...context
|
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 {
|
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 {
|
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 {
|
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 {
|
static debug(message: string, context?: LogContext): void {
|
||||||
if (process.env.NODE_ENV === 'development') {
|
if (this.shouldLog('debug')) {
|
||||||
console.debug(this.formatMessage('DEBUG', message, context));
|
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
|
// Helper for HTTP request logging
|
||||||
static logRequest(req: any, res: any, duration: number): void {
|
static logRequest(req: any, res: any, duration: number): void {
|
||||||
const context = {
|
const context = {
|
||||||
@ -78,4 +238,37 @@ export class Logger {
|
|||||||
this.error(message, logContext);
|
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