Skip to content

Commit 6fa0fde

Browse files
committed
fix: disable unnessary logs in stdio mode
1 parent 1f082fc commit 6fa0fde

File tree

11 files changed

+135
-21
lines changed

11 files changed

+135
-21
lines changed

bin/input.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}

bin/multi-protocol-server

16.6 KB
Binary file not shown.

bin/sse-server

16.6 KB
Binary file not shown.

bin/stdio-server

17 KB
Binary file not shown.

examples/stdio-server/logs/cortex-20250401-003240.log

Whitespace-only changes.

examples/stdio-server/logs/cortex-20250401-003528.log

Whitespace-only changes.
7.89 MB
Binary file not shown.

internal/infrastructure/logging/logger.go

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ package logging
33

44
import (
55
"context"
6+
"fmt"
7+
"os"
8+
"path/filepath"
9+
"time"
610

711
"go.uber.org/zap"
812
"go.uber.org/zap/zapcore"
@@ -40,33 +44,92 @@ type Config struct {
4044

4145
// DefaultConfig returns a default configuration for the logger
4246
func DefaultConfig() Config {
47+
// If in stdio mode, default to stderr instead of stdout
48+
outputs := []string{"stdout"}
49+
if isStdioMode() {
50+
outputs = getStdioSafeOutputs()
51+
}
52+
4353
return Config{
4454
Level: InfoLevel,
4555
Development: false,
46-
OutputPaths: []string{"stdout"},
56+
OutputPaths: outputs,
4757
}
4858
}
4959

5060
// DevelopmentConfig returns a development configuration for the logger
5161
func DevelopmentConfig() Config {
62+
// If in stdio mode, default to stderr instead of stdout
63+
outputs := []string{"stdout"}
64+
if isStdioMode() {
65+
outputs = getStdioSafeOutputs()
66+
}
67+
5268
return Config{
5369
Level: DebugLevel,
5470
Development: true,
55-
OutputPaths: []string{"stdout"},
71+
OutputPaths: outputs,
5672
}
5773
}
5874

5975
// ProductionConfig returns a production configuration for the logger
6076
func ProductionConfig() Config {
77+
// If in stdio mode, default to stderr instead of stdout
78+
outputs := []string{"stdout"}
79+
if isStdioMode() {
80+
outputs = getStdioSafeOutputs()
81+
}
82+
6183
return Config{
6284
Level: InfoLevel,
6385
Development: false,
64-
OutputPaths: []string{"stdout"},
86+
OutputPaths: outputs,
87+
}
88+
}
89+
90+
// isStdioMode checks if we're running in stdio mode
91+
func isStdioMode() bool {
92+
mode := os.Getenv("TRANSPORT_MODE")
93+
return mode == "stdio"
94+
}
95+
96+
// getStdioSafeOutputs returns outputs that are safe for stdio mode
97+
func getStdioSafeOutputs() []string {
98+
// In stdio mode, never use stdout as it will interfere with JSON-RPC
99+
// Try to use a log file, fall back to stderr
100+
if os.Getenv("MCP_DISABLE_LOGGING") == "true" {
101+
// If logging is disabled, use /dev/null or nul for Windows
102+
return []string{"stderr"}
65103
}
104+
105+
// Create a log file in the logs directory
106+
logsDir := "logs"
107+
if _, err := os.Stat(logsDir); os.IsNotExist(err) {
108+
os.MkdirAll(logsDir, 0755)
109+
}
110+
111+
// Create a timestamped log file
112+
timestamp := time.Now().Format("20060102-150405")
113+
logFile := filepath.Join(logsDir, fmt.Sprintf("cortex-%s.log", timestamp))
114+
115+
return []string{logFile, "stderr"}
66116
}
67117

68118
// New creates a new logger with the given configuration
69119
func New(config Config) (*Logger, error) {
120+
// If in stdio mode, force logs to NOT go to stdout regardless of config
121+
if isStdioMode() && len(config.OutputPaths) > 0 {
122+
// Replace any stdout with safe outputs
123+
safeOutputs := getStdioSafeOutputs()
124+
for _, path := range config.OutputPaths {
125+
if path == "stdout" {
126+
// In stdio mode, replace stdout with safe outputs
127+
config.OutputPaths = safeOutputs
128+
break
129+
}
130+
}
131+
}
132+
70133
// Convert log level to zapcore level
71134
var level zapcore.Level
72135
switch config.Level {

internal/interfaces/stdio/server.go

Lines changed: 54 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -88,15 +88,36 @@ func WithErrorLogger(stdLogger *log.Logger) StdioOption {
8888
// NewStdioServer creates a new stdio server wrapper around an MCPServer.
8989
// It initializes the server with a default logger that logs to stderr.
9090
func NewStdioServer(server *rest.MCPServer, opts ...StdioOption) *StdioServer {
91-
// Create default logger - always use stderr for STDIO servers
92-
defaultLogger, err := logging.New(logging.Config{
93-
Level: logging.InfoLevel,
94-
Development: true,
95-
OutputPaths: []string{"stderr"}, // Force stderr for all logging output
96-
InitialFields: logging.Fields{
97-
"component": "stdio-server",
98-
},
99-
})
91+
// Check for environment variables that indicate logging should be disabled
92+
var defaultLogger *logging.Logger
93+
var err error
94+
95+
disableLogging := os.Getenv("MCP_DISABLE_LOGGING") == "true" ||
96+
os.Getenv("DISABLE_LOGGING") == "true"
97+
98+
if disableLogging {
99+
// Create a logger that sends nothing to stdout
100+
// We'll still log to stderr for critical errors
101+
defaultLogger, err = logging.New(logging.Config{
102+
Level: logging.ErrorLevel, // Only log errors
103+
Development: false,
104+
OutputPaths: []string{"stderr"}, // Only stderr, never stdout
105+
InitialFields: logging.Fields{
106+
"component": "stdio-server",
107+
},
108+
})
109+
} else {
110+
// Create default logger - always use stderr for STDIO servers
111+
defaultLogger, err = logging.New(logging.Config{
112+
Level: logging.InfoLevel,
113+
Development: true,
114+
OutputPaths: []string{"stderr"}, // Force stderr for all logging output
115+
InitialFields: logging.Fields{
116+
"component": "stdio-server",
117+
},
118+
})
119+
}
120+
100121
if err != nil {
101122
// Fallback to a simple default logger if we can't create the structured one
102123
defaultLogger = logging.Default()
@@ -222,19 +243,30 @@ func ServeStdio(server *rest.MCPServer, opts ...StdioOption) error {
222243

223244
go func() {
224245
sig := <-sigChan
225-
s.logger.Info("Received shutdown signal, stopping server...", logging.Fields{"signal": sig.String()})
246+
// Only log if logging is enabled
247+
if os.Getenv("MCP_DISABLE_LOGGING") != "true" && os.Getenv("DISABLE_LOGGING") != "true" {
248+
s.logger.Info("Received shutdown signal, stopping server...", logging.Fields{"signal": sig.String()})
249+
}
226250
cancel()
227251
}()
228252

229-
s.logger.Info("Starting MCP server in stdio mode")
253+
// Only log if logging is enabled
254+
if os.Getenv("MCP_DISABLE_LOGGING") != "true" && os.Getenv("DISABLE_LOGGING") != "true" {
255+
s.logger.Info("Starting MCP server in stdio mode")
256+
}
230257

231258
err := s.Listen(ctx, os.Stdin, os.Stdout)
232259
if err != nil && err != context.Canceled {
260+
// Always log errors, but to stderr
233261
s.logger.Error("Server exited with error", logging.Fields{"error": err})
234262
return err
235263
}
236264

237-
s.logger.Info("Server shutdown complete")
265+
// Only log if logging is enabled
266+
if os.Getenv("MCP_DISABLE_LOGGING") != "true" && os.Getenv("DISABLE_LOGGING") != "true" {
267+
s.logger.Info("Server shutdown complete")
268+
}
269+
238270
return nil
239271
}
240272

@@ -307,7 +339,11 @@ func (p *MessageProcessor) Process(ctx context.Context, message string) (interfa
307339
// Check if this is a notification (no ID field)
308340
// Notifications don't require responses
309341
if baseMessage.ID == nil && strings.HasPrefix(baseMessage.Method, "notifications/") {
310-
p.logger.Info("Received notification", logging.Fields{"method": baseMessage.Method})
342+
// In stdio mode we must be very careful about logging
343+
// Use a custom field to avoid JSON output format
344+
if os.Getenv("MCP_DISABLE_LOGGING") != "true" && os.Getenv("DISABLE_LOGGING") != "true" {
345+
p.logger.Info("Received notification", logging.Fields{"method": baseMessage.Method})
346+
}
311347
// Process notification but don't return a response
312348
return nil, nil
313349
}
@@ -317,7 +353,11 @@ func (p *MessageProcessor) Process(ctx context.Context, message string) (interfa
317353

318354
// Handle notifications with a prefix
319355
if !exists && strings.HasPrefix(baseMessage.Method, "notifications/") {
320-
p.logger.Info("Processed notification", logging.Fields{"method": baseMessage.Method})
356+
// In stdio mode we must be very careful about logging
357+
// Use a custom field to avoid JSON output format
358+
if os.Getenv("MCP_DISABLE_LOGGING") != "true" && os.Getenv("DISABLE_LOGGING") != "true" {
359+
p.logger.Info("Processed notification", logging.Fields{"method": baseMessage.Method})
360+
}
321361
return nil, nil
322362
}
323363

logs/cortex-20250401-003412.log

Whitespace-only changes.

0 commit comments

Comments
 (0)