Skip to main content
Glama
logger.utility.ts13 kB
/** * Logger Utility Module * ===================== * * This module provides standardized logging functionality across the application. * It leverages Winston for structured logging with support for multiple outputs * and configurable formats. */ import fs from "fs"; import path from "path"; import winston from "winston"; import { ErrorSeverityLevel } from '../types/error.definition.types.js'; /** * Supported log levels */ export type LogLevel = "debug" | "info" | "warn" | "error" | "fatal"; /** * Log format types */ export type LogFormat = "json" | "simple" | "detailed"; /** * Logger configuration options */ export interface LoggerConfig { /** Log level (debug, info, warn, error, fatal) */ level?: LogLevel; /** Directory for log files */ logDir?: string; /** Format for log output */ format?: LogFormat; /** Whether to log to console (should be false) */ console?: boolean; /** Whether to log to files */ files?: boolean; /** Custom file names for log files */ fileNames?: { combined?: string; error?: string; warn?: string; info?: string; debug?: string; }; } /** * Default configuration values */ const DEFAULT_CONFIG: LoggerConfig = { level: "info", logDir: "logs", format: "detailed", console: false, // Never log to console by default files: true, fileNames: { combined: "combined.log", error: "error.log", warn: "warn.log", info: "info.log", debug: "debug.log" } }; /** * Maps ErrorSeverityLevel to Winston log levels */ const severityToLogLevel = (severity: ErrorSeverityLevel): string => { switch (severity) { case ErrorSeverityLevel.SEVERITY_INFO: return 'info'; case ErrorSeverityLevel.SEVERITY_WARN: return 'warn'; case ErrorSeverityLevel.SEVERITY_ERROR: return 'error'; case ErrorSeverityLevel.SEVERITY_FATAL: return 'error'; // Winston doesn't have 'fatal', so map to 'error' default: return 'info'; } }; /** * Logger class with configuration options * Implements the Singleton pattern for consistent logging across the application */ export class Logger { private static instance: Logger; // Initialize with a minimal logger to avoid TypeScript errors private logger: winston.Logger = winston.createLogger({ transports: [] // Initialize with no transports to avoid any console logging }); private config: LoggerConfig; /** * Private constructor (use getInstance instead) * @param config Initial logger configuration */ private constructor(config: LoggerConfig = {}) { // Merge provided config with defaults this.config = { ...DEFAULT_CONFIG, ...config, fileNames: { ...DEFAULT_CONFIG.fileNames, ...config.fileNames } }; this.initializeLogger(); } /** * Get or create the singleton logger instance * @param config Optional configuration to override defaults * @returns The logger instance */ public static getInstance(config?: LoggerConfig): Logger { if (!Logger.instance) { Logger.instance = new Logger(config); } else if (config) { // Update configuration if provided Logger.instance.configure(config); } return Logger.instance; } /** * Updates logger configuration * @param config New configuration options */ public configure(config: LoggerConfig): void { // Merge new config with current config this.config = { ...this.config, ...config, fileNames: { ...this.config.fileNames, ...config.fileNames } }; // Reinitialize the logger with new config this.initializeLogger(); } /** * Initialize or reinitialize the Winston logger */ private initializeLogger(): void { // Create logs directory if it doesn't exist and file logging is enabled if (this.config.files && this.config.logDir) { if (!fs.existsSync(this.config.logDir)) { try { fs.mkdirSync(this.config.logDir, { recursive: true }); } catch (error) { // Silently continue - we'll use a silent transport as fallback } } } // Create log formats based on configuration const logFormat = this.createLogFormat(this.config.format); // Initialize transports array with a silent transport to avoid "no transports" warning const transports: winston.transport[] = [ new winston.transports.Console({ silent: true, // Silent transport that doesn't output logs format: logFormat }) ]; // Add file transports if enabled if (this.config.files && this.config.logDir) { try { const fileNames = this.config.fileNames || DEFAULT_CONFIG.fileNames; // Combined log file if (fileNames?.combined) { transports.push(new winston.transports.File({ filename: path.join(this.config.logDir, fileNames.combined), format: logFormat })); } // Level-specific log files if (fileNames?.error) { transports.push(new winston.transports.File({ filename: path.join(this.config.logDir, fileNames.error), level: 'error', format: logFormat })); } if (fileNames?.warn) { transports.push(new winston.transports.File({ filename: path.join(this.config.logDir, fileNames.warn), level: 'warn', format: logFormat })); } if (fileNames?.info) { transports.push(new winston.transports.File({ filename: path.join(this.config.logDir, fileNames.info), level: 'info', format: logFormat })); } if (fileNames?.debug) { transports.push(new winston.transports.File({ filename: path.join(this.config.logDir, fileNames.debug), level: 'debug', format: logFormat })); } } catch (error) { // Silently continue with just the silent transport if file transports fail } } // Create the Winston logger this.logger = winston.createLogger({ level: this.config.level || DEFAULT_CONFIG.level, format: winston.format.json(), defaultMeta: { service: 'github-mcp-server' }, transports }); } /** * Create the appropriate log format based on configuration * @param format Format type string * @returns Winston format */ private createLogFormat(format: LogFormat = "detailed"): winston.Logform.Format { switch (format) { case "json": return winston.format.combine( winston.format.timestamp(), winston.format.json() ); case "simple": return winston.format.combine( winston.format.timestamp(), winston.format.printf((info: winston.Logform.TransformableInfo) => { return `[${info.timestamp}] ${info.level}: ${info.message}`; }) ); case "detailed": default: return winston.format.combine( winston.format.timestamp(), winston.format.errors({ stack: true }), winston.format.printf((info: winston.Logform.TransformableInfo) => { const contextStr = info.context ? `\n Context: ${JSON.stringify(info.context, null, 2)}` : ""; const stackStr = info.stack ? `\n Stack: ${info.stack}` : ""; return `[${info.timestamp}] ${info.level}: ${info.message}${contextStr}${stackStr}`; }) ); } } /** * Log a debug message * @param message The message to log * @param context Optional context object */ public debug(message: string, context?: Record<string, unknown>): void { this.logger.debug(message, { context }); } /** * Log an info message * @param message The message to log * @param context Optional context object */ public info(message: string, context?: Record<string, unknown>): void { this.logger.info(message, { context }); } /** * Log a warning message * @param message The message to log * @param context Optional context object */ public warn(message: string, context?: Record<string, unknown>): void { this.logger.warn(message, { context }); } /** * Log an error message * @param message The message to log * @param context Optional context object */ public error(message: string, context?: Record<string, unknown>): void { this.logger.error(message, { context }); } /** * Log a fatal error message (maps to error in Winston) * @param message The message to log * @param context Optional context object */ public fatal(message: string, context?: Record<string, unknown>): void { this.logger.error(message, { context, level: 'fatal' }); } /** * Log an exception with full stack trace * @param message The error message * @param error The error object * @param context Additional context */ public exception(message: string, error: Error, context?: Record<string, unknown>): void { this.logger.error(message, { context, stack: error.stack, error: { name: error.name, message: error.message } }); } /** * Log based on error severity level * @param severity The error severity level * @param message The message to log * @param context Optional context object */ public logWithSeverity(severity: ErrorSeverityLevel, message: string, context?: Record<string, unknown>): void { const level = severityToLogLevel(severity); this.logger.log(level, message, { context }); } /** * Create a child logger with additional default context * @param defaultContext Default context to include with all log messages * @returns A child logger instance */ public createChildLogger(defaultContext: Record<string, unknown>): ChildLogger { return new ChildLogger(this, defaultContext); } } /** * Child logger that includes default context with all log messages */ export class ChildLogger { /** * Create a new child logger * @param parent Parent logger * @param defaultContext Default context to include with all log messages */ constructor( private parent: Logger, private defaultContext: Record<string, unknown> ) {} /** * Merge provided context with default context * @param context Additional context * @returns Merged context */ private mergeContext(context?: Record<string, unknown>): Record<string, unknown> { return { ...this.defaultContext, ...context }; } /** * Log a debug message * @param message The message to log * @param context Additional context */ public debug(message: string, context?: Record<string, unknown>): void { this.parent.debug(message, this.mergeContext(context)); } /** * Log an info message * @param message The message to log * @param context Additional context */ public info(message: string, context?: Record<string, unknown>): void { this.parent.info(message, this.mergeContext(context)); } /** * Log a warning message * @param message The message to log * @param context Additional context */ public warn(message: string, context?: Record<string, unknown>): void { this.parent.warn(message, this.mergeContext(context)); } /** * Log an error message * @param message The message to log * @param context Additional context */ public error(message: string, context?: Record<string, unknown>): void { this.parent.error(message, this.mergeContext(context)); } /** * Log a fatal error message * @param message The message to log * @param context Additional context */ public fatal(message: string, context?: Record<string, unknown>): void { this.parent.fatal(message, this.mergeContext(context)); } /** * Log an exception with full stack trace * @param message The error message * @param error The error object * @param context Additional context */ public exception(message: string, error: Error, context?: Record<string, unknown>): void { this.parent.exception(message, error, this.mergeContext(context)); } /** * Log based on error severity level * @param severity The error severity level * @param message The message to log * @param context Optional context object */ public logWithSeverity(severity: ErrorSeverityLevel, message: string, context?: Record<string, unknown>): void { this.parent.logWithSeverity(severity, message, this.mergeContext(context)); } } // Create default logger instance with file logging but no console logging export const logger = Logger.getInstance({ console: false, // Ensure no console logging files: true, logDir: "logs", format: "detailed" }); export default logger;

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/cyanheads/github-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server