Skip to main content
Glama
Logger.ts5.03 kB
import { promises as fs } from 'node:fs' import { dirname } from 'node:path' /** * Log level enumeration for structured logging. */ export type LogLevel = 'debug' | 'info' | 'warn' | 'error' /** * Log entry structure for consistent logging format. */ export interface LogEntry { /** Timestamp of the log entry */ timestamp: string /** Log level */ level: LogLevel /** Log message */ message: string /** Optional context data */ context?: Record<string, unknown> /** Optional error object */ error?: Error } /** * Logger utility class for structured logging. * * Provides consistent logging format with timestamps, levels, and context data. * Supports filtering by log level and structured output for debugging. */ export class Logger { private currentLevel: LogLevel private readonly logFilePath?: string private fileWriteErrorShown = false /** * Log level priority for filtering. */ private static readonly levelPriority: Record<LogLevel, number> = { debug: 0, info: 1, warn: 2, error: 3, } constructor(level: LogLevel = 'info') { this.currentLevel = level // Check for optional file logging via environment variable const logFile = process.env['LOG_FILE'] if (logFile?.trim()) { this.logFilePath = logFile.trim() } } /** * Logs a debug message. * * @param message - The message to log * @param context - Optional context data */ debug(message: string, context?: Record<string, unknown>): void { this.log('debug', message, context) } /** * Logs an info message. * * @param message - The message to log * @param context - Optional context data */ info(message: string, context?: Record<string, unknown>): void { this.log('info', message, context) } /** * Logs a warning message. * * @param message - The message to log * @param context - Optional context data */ warn(message: string, context?: Record<string, unknown>): void { this.log('warn', message, context) } /** * Logs an error message. * * @param message - The message to log * @param error - Optional error object * @param context - Optional context data */ error(message: string, error?: Error, context?: Record<string, unknown>): void { const errorContext = error ? { ...context, error: error.message, stack: error.stack } : context this.log('error', message, errorContext) } /** * Internal log method that handles the actual logging. * * @param level - The log level * @param message - The message to log * @param context - Optional context data */ private log(level: LogLevel, message: string, context?: Record<string, unknown>): void { if (!this.shouldLog(level)) { return } const logEntry: LogEntry = { timestamp: new Date().toISOString(), level, message, ...(context && { context }), } this.output(logEntry) } /** * Determines if a message should be logged based on current level. * * @param level - The log level to check * @returns True if the message should be logged */ private shouldLog(level: LogLevel): boolean { return Logger.levelPriority[level] >= Logger.levelPriority[this.currentLevel] } /** * Outputs the log entry to the console and optionally to a file. * * @param entry - The log entry to output */ private output(entry: LogEntry): void { const { timestamp, level, message, context } = entry // Format output for console readability const formattedMessage = `[${timestamp}] ${level.toUpperCase()}: ${message}` if (context && Object.keys(context).length > 0) { console.error(formattedMessage, context) } else { console.error(formattedMessage) } // Optional file output in JSON format if (this.logFilePath) { this.writeToFile(entry) } } /** * Writes log entry to file in JSON format. * Errors are handled internally and do not affect main logging flow. * * @param entry - The log entry to write to file */ private writeToFile(entry: LogEntry): void { // Fire-and-forget async operation to avoid blocking main thread this.performFileWrite(entry).catch((error: unknown) => { // Only show file write error once to avoid spam if (!this.fileWriteErrorShown) { this.fileWriteErrorShown = true console.error('Logger: Failed to write to log file, falling back to console only', error) } }) } /** * Performs the actual file write operation. * * @param entry - The log entry to write */ private async performFileWrite(entry: LogEntry): Promise<void> { if (!this.logFilePath) return // Ensure directory exists const dir = dirname(this.logFilePath) await fs.mkdir(dir, { recursive: true }) // Convert entry to JSON string const jsonLine = `${JSON.stringify(entry)}\n` // Append to file await fs.appendFile(this.logFilePath, jsonLine, 'utf8') } }

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/shinpr/sub-agents-mcp'

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