UNPKG

openoracle-sdk-js

Version:

OpenOracle Node.js SDK - Intelligent Oracle Routing with Multiple LLM Providers

327 lines (278 loc) 9.02 kB
/** * Logging utilities for OpenOracle SDK */ import { createLogger, format, transports, Logger } from 'winston' import { OracleConfig } from '../core/config' export interface LogContext { provider?: string requestId?: string userId?: string operation?: string duration?: number error?: Error metadata?: Record<string, any> } export class OracleLogger { private readonly logger: Logger private readonly config: OracleConfig constructor(config: OracleConfig) { this.config = config this.logger = this.createWinstonLogger() } private createWinstonLogger(): Logger { const logFormat = this.config.logging.format === 'json' ? format.combine( format.timestamp(), format.errors({ stack: true }), format.json() ) : format.combine( format.timestamp(), format.errors({ stack: true }), format.printf(({ timestamp, level, message, ...meta }) => { const metaString = Object.keys(meta).length > 0 ? ` ${JSON.stringify(meta)}` : '' return `${timestamp} [${level.toUpperCase()}] ${message}${metaString}` }) ) const logTransports: any[] = [] // Console transport if (this.config.logging.destination === 'console' || this.config.logging.destination === 'both') { logTransports.push(new transports.Console({ level: this.config.logging.level, format: logFormat })) } // File transport if (this.config.logging.destination === 'file' || this.config.logging.destination === 'both') { logTransports.push(new transports.File({ filename: this.config.logging.filePath || './logs/openoracle.log', level: this.config.logging.level, format: logFormat, maxsize: this.parseSize(this.config.logging.maxFileSize || '10m'), maxFiles: this.config.logging.maxFiles || 5 })) } return createLogger({ level: this.config.logging.level, format: logFormat, transports: logTransports, exitOnError: false }) } private parseSize(sizeStr: string): number { const match = sizeStr.match(/^(\d+)([kmg]?)$/i) if (!match) return 10 * 1024 * 1024 // Default 10MB const size = parseInt(match[1]) const unit = match[2]?.toLowerCase() || '' switch (unit) { case 'k': return size * 1024 case 'm': return size * 1024 * 1024 case 'g': return size * 1024 * 1024 * 1024 default: return size } } debug(message: string, context?: LogContext): void { this.logger.debug(message, this.formatContext(context)) } info(message: string, context?: LogContext): void { this.logger.info(message, this.formatContext(context)) } warn(message: string, context?: LogContext): void { this.logger.warn(message, this.formatContext(context)) } error(message: string, context?: LogContext): void { this.logger.error(message, this.formatContext(context)) } private formatContext(context?: LogContext): Record<string, any> { if (!context) return {} const formatted: Record<string, any> = {} if (context.provider) formatted.provider = context.provider if (context.requestId) formatted.requestId = context.requestId if (context.userId) formatted.userId = context.userId if (context.operation) formatted.operation = context.operation if (context.duration !== undefined) formatted.duration = `${context.duration}ms` if (context.error) { formatted.error = { name: context.error.name, message: context.error.message, stack: context.error.stack } } if (context.metadata) formatted.metadata = context.metadata return formatted } // Performance logging logPerformance(operation: string, duration: number, context?: LogContext): void { const level = duration > 5000 ? 'warn' : duration > 2000 ? 'info' : 'debug' const message = `Performance: ${operation} completed in ${duration}ms` this.logger.log(level, message, this.formatContext({ ...context, operation, duration })) } // Provider-specific logging logProviderQuery( provider: string, query: string, success: boolean, duration: number, error?: Error ): void { const message = `Provider query: ${provider} - ${success ? 'SUCCESS' : 'FAILED'}` const level = success ? 'info' : 'error' this.logger.log(level, message, this.formatContext({ provider, operation: 'query', duration, error, metadata: { query, success } })) } // API request logging logApiRequest( method: string, url: string, statusCode: number, duration: number, requestId?: string ): void { const level = statusCode >= 400 ? 'error' : statusCode >= 300 ? 'warn' : 'info' const message = `API Request: ${method} ${url} - ${statusCode}` this.logger.log(level, message, this.formatContext({ requestId, operation: 'api_request', duration, metadata: { method, url, statusCode } })) } // User activity logging logUserActivity( userId: string, action: string, details?: Record<string, any> ): void { this.logger.info(`User activity: ${action}`, this.formatContext({ userId, operation: action, metadata: details })) } // Security event logging logSecurityEvent( event: string, severity: 'low' | 'medium' | 'high' | 'critical', details?: Record<string, any> ): void { const level = severity === 'critical' ? 'error' : severity === 'high' ? 'warn' : 'info' this.logger.log(level, `Security event: ${event}`, this.formatContext({ operation: 'security_event', metadata: { event, severity, ...details } })) } // Business logic logging logBusinessEvent( event: string, value?: number, context?: LogContext ): void { this.logger.info(`Business event: ${event}`, this.formatContext({ ...context, operation: 'business_event', metadata: { event, value, ...context?.metadata } })) } // Create child logger with default context child(defaultContext: LogContext): OracleLogger { const childLogger = new OracleLogger(this.config) // Override logging methods to include default context const originalMethods = { debug: childLogger.debug.bind(childLogger), info: childLogger.info.bind(childLogger), warn: childLogger.warn.bind(childLogger), error: childLogger.error.bind(childLogger) } childLogger.debug = (message: string, context?: LogContext) => { originalMethods.debug(message, { ...defaultContext, ...context }) } childLogger.info = (message: string, context?: LogContext) => { originalMethods.info(message, { ...defaultContext, ...context }) } childLogger.warn = (message: string, context?: LogContext) => { originalMethods.warn(message, { ...defaultContext, ...context }) } childLogger.error = (message: string, context?: LogContext) => { originalMethods.error(message, { ...defaultContext, ...context }) } return childLogger } // Flush logs (useful for graceful shutdown) async flush(): Promise<void> { return new Promise((resolve) => { this.logger.on('finish', resolve) this.logger.end() }) } } // Global logger instance let globalLogger: OracleLogger | null = null /** * Get the global logger instance */ export function getLogger(config?: OracleConfig): OracleLogger { if (!globalLogger && config) { globalLogger = new OracleLogger(config) } if (!globalLogger) { throw new Error('Logger not initialized. Call getLogger with config first.') } return globalLogger } /** * Set the global logger instance */ export function setLogger(logger: OracleLogger): void { globalLogger = logger } /** * Create a performance timer */ export function createTimer(operation: string, logger?: OracleLogger): () => void { const startTime = Date.now() const log = logger || globalLogger return () => { const duration = Date.now() - startTime log?.logPerformance(operation, duration) } } /** * Decorator for automatic performance logging */ export function logPerformance(operation?: string) { return function (target: any, propertyName: string, descriptor: PropertyDescriptor) { const method = descriptor.value const opName = operation || `${target.constructor.name}.${propertyName}` descriptor.value = async function (...args: any[]) { const timer = createTimer(opName) try { const result = await method.apply(this, args) timer() return result } catch (error) { timer() throw error } } return descriptor } } /** * Structured logging helpers */ export const LogLevel = { DEBUG: 'debug', INFO: 'info', WARN: 'warn', ERROR: 'error' } as const export type LogLevelType = typeof LogLevel[keyof typeof LogLevel]