Skip to main content
Glama
logger.go7.17 kB
package logging import ( "fmt" "io" "log" "os" "strings" "sync" ) // LogLevel represents the severity of a log message type LogLevel int const ( // Debug level for verbose development logs LevelDebug LogLevel = iota // Info level for general operational information LevelInfo // Warn level for warning conditions LevelWarn // Error level for error conditions LevelError // Fatal level for critical errors LevelFatal ) // String returns the string representation of a log level func (l LogLevel) String() string { switch l { case LevelDebug: return "DEBUG" case LevelInfo: return "INFO" case LevelWarn: return "WARN" case LevelError: return "ERROR" case LevelFatal: return "FATAL" default: return fmt.Sprintf("LEVEL(%d)", l) } } // Component represents a specific part of the application for which logs can be filtered type Component string const ( // Core component for the main application Core Component = "core" // LSP component for high-level Language Server Protocol operations LSP Component = "lsp" // LSPWire component for raw LSP wire protocol messages LSPWire Component = "wire" // LSPProcess component for logs from the LSP server process itself LSPProcess Component = "lsp-process" // Watcher component for file system watching Watcher Component = "watcher" // Tools component for LSP tools Tools Component = "tools" ) // DefaultMinLevel is the default minimum log level var DefaultMinLevel = LevelInfo // ComponentLevels tracks the minimum log level for each component var ComponentLevels = map[Component]LogLevel{} // Writer is the destination for logs var Writer io.Writer = os.Stderr // TestOutput can be set during tests to capture log output var TestOutput io.Writer // logMu protects concurrent modifications to logging config var logMu sync.Mutex // Initialize from environment variables func init() { // Set default levels for each component ComponentLevels[Core] = DefaultMinLevel ComponentLevels[LSP] = DefaultMinLevel ComponentLevels[Watcher] = DefaultMinLevel ComponentLevels[Tools] = DefaultMinLevel ComponentLevels[LSPProcess] = DefaultMinLevel ComponentLevels[LSPWire] = DefaultMinLevel // Parse log level from environment variable if level := os.Getenv("LOG_LEVEL"); level != "" { switch strings.ToUpper(level) { case "DEBUG": DefaultMinLevel = LevelDebug case "INFO": DefaultMinLevel = LevelInfo case "WARN": DefaultMinLevel = LevelWarn case "ERROR": DefaultMinLevel = LevelError case "FATAL": DefaultMinLevel = LevelFatal } // Set all components to this level by default for comp := range ComponentLevels { ComponentLevels[comp] = DefaultMinLevel } } // Allow overriding levels for specific components if compLevels := os.Getenv("LOG_COMPONENT_LEVELS"); compLevels != "" { for _, part := range strings.SplitN(compLevels, ",", -1) { compAndLevel := strings.Split(part, ":") if len(compAndLevel) != 2 { continue } comp := Component(strings.TrimSpace(compAndLevel[0])) levelStr := strings.ToUpper(strings.TrimSpace(compAndLevel[1])) var level LogLevel switch levelStr { case "DEBUG": level = LevelDebug case "INFO": level = LevelInfo case "WARN": level = LevelWarn case "ERROR": level = LevelError case "FATAL": level = LevelFatal default: continue } ComponentLevels[comp] = level } } // Use custom log file if specified if logFile := os.Getenv("LOG_FILE"); logFile != "" { file, err := os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err == nil { Writer = io.MultiWriter(os.Stderr, file) } } // Configure the standard logger log.SetOutput(Writer) log.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds) } // Logger is the interface for component-specific logging type Logger interface { Debug(format string, v ...any) Info(format string, v ...any) Warn(format string, v ...any) Error(format string, v ...any) Fatal(format string, v ...any) IsLevelEnabled(level LogLevel) bool } // ComponentLogger is a logger for a specific component type ComponentLogger struct { component Component } // NewLogger creates a new logger for the specified component func NewLogger(component Component) Logger { return &ComponentLogger{ component: component, } } // IsLevelEnabled returns true if the given log level is enabled for this component func (l *ComponentLogger) IsLevelEnabled(level LogLevel) bool { logMu.Lock() defer logMu.Unlock() minLevel, ok := ComponentLevels[l.component] if !ok { minLevel = DefaultMinLevel } return level >= minLevel } // log logs a message at the specified level if it meets the threshold func (l *ComponentLogger) log(level LogLevel, format string, v ...any) { if !l.IsLevelEnabled(level) { return } message := fmt.Sprintf(format, v...) logMessage := fmt.Sprintf("[%s][%s] %s", level, l.component, message) if err := log.Output(3, logMessage); err != nil { fmt.Fprintf(os.Stderr, "Failed to output log: %v\n", err) } // Write to test output if set if TestOutput != nil { if _, err := fmt.Fprintln(TestOutput, logMessage); err != nil { fmt.Fprintf(os.Stderr, "Failed to output log to test output: %v\n", err) } } } // Debug logs a debug message func (l *ComponentLogger) Debug(format string, v ...any) { l.log(LevelDebug, format, v...) } // Info logs an info message func (l *ComponentLogger) Info(format string, v ...any) { l.log(LevelInfo, format, v...) } // Warn logs a warning message func (l *ComponentLogger) Warn(format string, v ...any) { l.log(LevelWarn, format, v...) } // Error logs an error message func (l *ComponentLogger) Error(format string, v ...any) { l.log(LevelError, format, v...) } // Fatal logs a fatal message and exits func (l *ComponentLogger) Fatal(format string, v ...any) { l.log(LevelFatal, format, v...) os.Exit(1) } // SetLevel sets the minimum log level for a component func SetLevel(component Component, level LogLevel) { logMu.Lock() defer logMu.Unlock() ComponentLevels[component] = level } // SetGlobalLevel sets the log level for all components func SetGlobalLevel(level LogLevel) { logMu.Lock() defer logMu.Unlock() DefaultMinLevel = level for comp := range ComponentLevels { ComponentLevels[comp] = level } } // SetWriter sets the writer for log output func SetWriter(w io.Writer) { logMu.Lock() defer logMu.Unlock() Writer = w log.SetOutput(Writer) } // SetupFileLogging configures logging to a file in addition to stderr func SetupFileLogging(filePath string) error { file, err := os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { return fmt.Errorf("failed to open log file: %w", err) } logMu.Lock() defer logMu.Unlock() Writer = io.MultiWriter(os.Stderr, file) log.SetOutput(Writer) return nil } // SetupTestLogging configures logging for tests func SetupTestLogging(captureOutput io.Writer) { logMu.Lock() defer logMu.Unlock() // Set test output for capturing logs TestOutput = captureOutput } // ResetTestLogging resets logging after tests func ResetTestLogging() { logMu.Lock() defer logMu.Unlock() TestOutput = nil }

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/isaacphi/mcp-language-server'

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