UNPKG

@0xobelisk/graphql-server

Version:

Tookit for interacting with dubhe graphql server

276 lines (245 loc) 8.27 kB
import pino from 'pino'; import path from 'path'; import fs from 'fs'; export interface LoggerConfig { level?: string; service?: string; component?: string; enableFileLogging?: boolean; logsDir?: string; } export interface ComponentLoggerMethods { debug: (message: string, meta?: any) => void; info: (message: string, meta?: any) => void; warn: (message: string, meta?: any) => void; error: (message: string, error?: any, meta?: any) => void; } /** * High-performance logging system based on Pino */ export class Logger { private pinoInstance: pino.Logger; private config: Required<LoggerConfig>; constructor(config: LoggerConfig = {}) { this.config = { level: config.level || process.env.LOG_LEVEL || 'info', service: config.service || 'dubhe-graphql-server', component: config.component || 'default', enableFileLogging: config.enableFileLogging !== false, logsDir: config.logsDir || path.join(process.cwd(), 'logs') }; this.ensureLogsDirectory(); this.pinoInstance = this.createPinoInstance(); this.setupExceptionHandlers(); } /** * Ensure logs directory exists */ private ensureLogsDirectory(): void { if (this.config.enableFileLogging && !fs.existsSync(this.config.logsDir)) { fs.mkdirSync(this.config.logsDir, { recursive: true }); } } /** * Create Pino instance */ private createPinoInstance(): pino.Logger { const pinoOptions: pino.LoggerOptions = { level: this.config.level, base: { service: this.config.service, pid: process.pid }, timestamp: pino.stdTimeFunctions.isoTime, formatters: { level(label: string) { return { level: label }; } }, serializers: { error: pino.stdSerializers.err } }; // If file logging is enabled, use multistream if (this.config.enableFileLogging) { const streams = [ // Pretty print to console { stream: pino.transport({ target: 'pino-pretty', options: { colorize: true, translateTime: 'yyyy-mm-dd HH:MM:ss.l', ignore: 'pid,hostname,service,component', messageFormat: '[{component}]: {msg}', singleLine: true, hideObject: false } }) }, // JSON format to file { stream: pino.destination({ dest: path.join(this.config.logsDir, 'combined.log'), sync: false }) } ]; return pino(pinoOptions, pino.multistream(streams)); } // Only output to console in pretty format return pino({ ...pinoOptions, transport: { target: 'pino-pretty', options: { colorize: true, translateTime: 'yyyy-mm-dd HH:MM:ss.l', ignore: 'pid,hostname,service', messageFormat: '[{component}]: {msg}', singleLine: true, hideObject: false } } }); } /** * Setup exception handlers */ private setupExceptionHandlers(): void { process.on('uncaughtException', (error) => { this.pinoInstance.fatal({ error }, 'Uncaught Exception'); process.exit(1); }); process.on('unhandledRejection', (reason, promise) => { this.pinoInstance.fatal({ reason, promise }, 'Unhandled Promise Rejection'); process.exit(1); }); } /** * Create component logger with context */ public createComponentLogger(component: string): ComponentLoggerMethods { const componentLogger = this.pinoInstance.child({ component }); return { debug: (message: string, meta?: any) => componentLogger.debug(meta || {}, message), info: (message: string, meta?: any) => componentLogger.info(meta || {}, message), warn: (message: string, meta?: any) => componentLogger.warn(meta || {}, message), error: (message: string, error?: any, meta?: any) => { const errorData = error instanceof Error ? { error: { message: error.message, stack: error.stack, name: error.name }, ...meta } : { error, ...meta }; componentLogger.error(errorData, message); } }; } /** * Get raw Pino instance */ public getPinoInstance(): pino.Logger { return this.pinoInstance; } /** * Log performance metrics */ public logPerformance(operation: string, startTime: number, meta?: any): void { const duration = Date.now() - startTime; const perfLogger = this.createComponentLogger('performance'); perfLogger.info(operation, { duration: `${duration}ms`, ...meta }); } /** * Log Express HTTP requests */ public logExpress( method: string, path: string, statusCode: number, startTime: number, meta?: any ): void { const duration = Date.now() - startTime; const httpLogger = this.createComponentLogger('express'); const message = `${method} ${path} - ${statusCode} (${duration}ms)`; // Choose log level based on status code if (statusCode >= 500) { httpLogger.error(message, meta); } else if (statusCode >= 400) { httpLogger.warn(message, meta); } else { httpLogger.info(message, meta); } } /** * Log database operations */ public logDatabaseOperation(operation: string, table?: string, meta?: any): void { const dbLogger = this.createComponentLogger('database'); dbLogger.info(`Database operation: ${operation}`, { table, ...meta }); } /** * Log WebSocket events */ public logWebSocketEvent(event: string, clientCount?: number, meta?: any): void { const wsLogger = this.createComponentLogger('websocket'); wsLogger.info(`WebSocket event: ${event}`, { clientCount, ...meta }); } /** * Log GraphQL queries */ public logGraphQLQuery(operation: string, query?: string, variables?: any): void { const gqlLogger = this.createComponentLogger('graphql'); gqlLogger.info(`GraphQL ${operation}`, { query: query?.substring(0, 200) + (query && query.length > 200 ? '...' : ''), variableCount: variables ? Object.keys(variables).length : 0 }); } } // Create default logger instance const defaultLogger = new Logger(); // Export predefined component loggers (maintain backward compatibility) export const dbLogger = defaultLogger.createComponentLogger('database'); export const serverLogger = defaultLogger.createComponentLogger('server'); export const httpLogger = defaultLogger.createComponentLogger('express'); export const wsLogger = defaultLogger.createComponentLogger('websocket'); export const gqlLogger = defaultLogger.createComponentLogger('graphql'); export const subscriptionLogger = defaultLogger.createComponentLogger('subscription'); export const systemLogger = defaultLogger.createComponentLogger('system'); export const authLogger = defaultLogger.createComponentLogger('auth'); export const perfLogger = defaultLogger.createComponentLogger('performance'); // Export utility functions (maintain backward compatibility) export const createComponentLogger = (component: string) => defaultLogger.createComponentLogger(component); export const logPerformance = (operation: string, startTime: number, meta?: any) => defaultLogger.logPerformance(operation, startTime, meta); export const logExpress = ( method: string, path: string, statusCode: number, startTime: number, meta?: any ) => defaultLogger.logExpress(method, path, statusCode, startTime, meta); export const logDatabaseOperation = (operation: string, table?: string, meta?: any) => defaultLogger.logDatabaseOperation(operation, table, meta); export const logWebSocketEvent = (event: string, clientCount?: number, meta?: any) => defaultLogger.logWebSocketEvent(event, clientCount, meta); export const logGraphQLQuery = (operation: string, query?: string, variables?: any) => defaultLogger.logGraphQLQuery(operation, query, variables); // Default export (maintain backward compatibility) export default defaultLogger.getPinoInstance();