Skip to main content
Glama
FreePeak

Multi Database MCP Server

logger.go8.42 kB
package logger import ( "encoding/json" "fmt" "os" "path/filepath" "runtime/debug" "strings" "sync" "time" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) // Level represents the severity of a log message type Level int const ( // LevelDebug for detailed troubleshooting LevelDebug Level = iota // LevelInfo for general operational entries LevelInfo // LevelWarn for non-critical issues LevelWarn // LevelError for errors that should be addressed LevelError ) var ( // Default logger zapLogger *zap.Logger logLevel Level // Flag to indicate if we're in stdio mode isStdioMode bool // Log file for stdio mode stdioLogFile *os.File // Mutex to protect log file access logMutex sync.Mutex ) // Config represents logger configuration type Config struct { Level string // Log level (debug, info, warn, error) LogDir string // Directory for log files (optional, defaults to ./logs) } // safeStdioWriter is a writer that ensures no output goes to stdout in stdio mode type safeStdioWriter struct { file *os.File } // Write implements io.Writer and filters all output in stdio mode func (w *safeStdioWriter) Write(p []byte) (n int, err error) { // In stdio mode, write to the log file instead of stdout logMutex.Lock() defer logMutex.Unlock() if stdioLogFile != nil { return stdioLogFile.Write(p) } // Last resort: write to stderr, never stdout return os.Stderr.Write(p) } // Sync implements zapcore.WriteSyncer func (w *safeStdioWriter) Sync() error { logMutex.Lock() defer logMutex.Unlock() if stdioLogFile != nil { return stdioLogFile.Sync() } return nil } // Initialize sets up the logger with the specified configuration func Initialize(cfg Config) { setLogLevel(cfg.Level) // Check if we're in stdio mode transportMode := os.Getenv("TRANSPORT_MODE") isStdioMode = transportMode == "stdio" if isStdioMode { // In stdio mode, we need to avoid ANY JSON output to stdout // Use provided log directory or default to "logs" in current directory logsDir := cfg.LogDir if logsDir == "" { logsDir = "logs" } // Create log directory if it doesn't exist if _, err := os.Stat(logsDir); os.IsNotExist(err) { if err := os.MkdirAll(logsDir, 0755); err != nil { fmt.Fprintf(os.Stderr, "Failed to create logs directory: %v\n", err) } } timestamp := time.Now().Format("20060102-150405") logFileName := filepath.Join(logsDir, fmt.Sprintf("mcp-logger-%s.log", timestamp)) // Try to create the log file var err error stdioLogFile, err = os.OpenFile(logFileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) if err != nil { // If we can't create a log file, we'll use a null logger fmt.Fprintf(os.Stderr, "Failed to create log file: %v - all logs will be suppressed\n", err) } else { // Write initial log message to stderr only (as a last message before full redirection) fmt.Fprintf(os.Stderr, "Stdio mode detected - all logs redirected to: %s\n", logFileName) // Create a custom writer that never writes to stdout safeWriter := &safeStdioWriter{file: stdioLogFile} // Create a development encoder for more readable logs encoderConfig := zap.NewDevelopmentEncoderConfig() encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder encoder := zapcore.NewConsoleEncoder(encoderConfig) // Create core that writes to our safe writer core := zapcore.NewCore(encoder, zapcore.AddSync(safeWriter), getZapLevel(logLevel)) // Create the logger with the core zapLogger = zap.New(core) return } } // Standard logger initialization for non-stdio mode or fallback config := zap.NewProductionConfig() config.EncoderConfig.TimeKey = "time" config.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder // In stdio mode with no log file, use a no-op logger to avoid any stdout output if isStdioMode { zapLogger = zap.NewNop() return } else { config.OutputPaths = []string{"stdout"} } config.Level = getZapLevel(logLevel) var err error zapLogger, err = config.Build() if err != nil { // If Zap logger cannot be built, fall back to noop logger zapLogger = zap.NewNop() } } // InitializeWithWriter sets up the logger with the specified level and output writer func InitializeWithWriter(level string, writer *os.File) { setLogLevel(level) config := zap.NewProductionConfig() config.EncoderConfig.TimeKey = "time" config.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder // Create custom core with the provided writer encoderConfig := zap.NewProductionEncoderConfig() encoderConfig.TimeKey = "time" encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder core := zapcore.NewCore( zapcore.NewJSONEncoder(encoderConfig), zapcore.AddSync(writer), getZapLevel(logLevel), ) zapLogger = zap.New(core) } // setLogLevel sets the log level from a string func setLogLevel(level string) { switch strings.ToLower(level) { case "debug": logLevel = LevelDebug case "info": logLevel = LevelInfo case "warn": logLevel = LevelWarn case "error": logLevel = LevelError default: logLevel = LevelInfo } } // getZapLevel converts our level to zap.AtomicLevel func getZapLevel(level Level) zap.AtomicLevel { switch level { case LevelDebug: return zap.NewAtomicLevelAt(zapcore.DebugLevel) case LevelInfo: return zap.NewAtomicLevelAt(zapcore.InfoLevel) case LevelWarn: return zap.NewAtomicLevelAt(zapcore.WarnLevel) case LevelError: return zap.NewAtomicLevelAt(zapcore.ErrorLevel) default: return zap.NewAtomicLevelAt(zapcore.InfoLevel) } } // Debug logs a debug message func Debug(format string, v ...interface{}) { if logLevel > LevelDebug { return } msg := fmt.Sprintf(format, v...) zapLogger.Debug(msg) } // Info logs an info message func Info(format string, v ...interface{}) { if logLevel > LevelInfo { return } msg := fmt.Sprintf(format, v...) zapLogger.Info(msg) } // Warn logs a warning message func Warn(format string, v ...interface{}) { if logLevel > LevelWarn { return } msg := fmt.Sprintf(format, v...) zapLogger.Warn(msg) } // Error logs an error message func Error(format string, v ...interface{}) { if logLevel > LevelError { return } msg := fmt.Sprintf(format, v...) zapLogger.Error(msg) } // ErrorWithStack logs an error with a stack trace func ErrorWithStack(err error) { if err == nil { return } zapLogger.Error( err.Error(), zap.String("stack", string(debug.Stack())), ) } // RequestLog logs details of an HTTP request func RequestLog(method, url, sessionID, body string) { if logLevel > LevelDebug { return } zapLogger.Debug("HTTP Request", zap.String("method", method), zap.String("url", url), zap.String("sessionID", sessionID), zap.String("body", body), ) } // ResponseLog logs details of an HTTP response func ResponseLog(statusCode int, sessionID, body string) { if logLevel > LevelDebug { return } zapLogger.Debug("HTTP Response", zap.Int("statusCode", statusCode), zap.String("sessionID", sessionID), zap.String("body", body), ) } // SSEEventLog logs details of an SSE event func SSEEventLog(eventType, sessionID, data string) { if logLevel > LevelDebug { return } zapLogger.Debug("SSE Event", zap.String("eventType", eventType), zap.String("sessionID", sessionID), zap.String("data", data), ) } // RequestResponseLog logs a combined request and response log entry func RequestResponseLog(method, sessionID string, requestData, responseData string) { if logLevel > LevelDebug { return } // Format for more readable logs formattedRequest := requestData formattedResponse := responseData // Try to format JSON if it's valid if strings.HasPrefix(requestData, "{") || strings.HasPrefix(requestData, "[") { var obj interface{} if err := json.Unmarshal([]byte(requestData), &obj); err == nil { if formatted, err := json.MarshalIndent(obj, "", " "); err == nil { formattedRequest = string(formatted) } } } if strings.HasPrefix(responseData, "{") || strings.HasPrefix(responseData, "[") { var obj interface{} if err := json.Unmarshal([]byte(responseData), &obj); err == nil { if formatted, err := json.MarshalIndent(obj, "", " "); err == nil { formattedResponse = string(formatted) } } } zapLogger.Debug("Request/Response", zap.String("method", method), zap.String("sessionID", sessionID), zap.String("request", formattedRequest), zap.String("response", formattedResponse), ) }

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/FreePeak/db-mcp-server'

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