Skip to content

Commit 2386301

Browse files
committed
add iologging for debugging purposes
1 parent 09366fa commit 2386301

File tree

4 files changed

+129
-3
lines changed

4 files changed

+129
-3
lines changed

cmd/server/main.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ package main
33
import (
44
"context"
55
"fmt"
6+
"io"
67
stdlog "log"
78
"os"
89
"os/signal"
910
"syscall"
1011

1112
"github.com/github/github-mcp-server/pkg/github"
13+
iolog "github.com/github/github-mcp-server/pkg/log"
1214
gogithub "github.com/google/go-github/v69/github"
1315
"github.com/mark3labs/mcp-go/server"
1416
log "github.com/sirupsen/logrus"
@@ -33,7 +35,8 @@ var (
3335
if err != nil {
3436
stdlog.Fatal("Failed to initialize logger:", err)
3537
}
36-
if err := runStdioServer(logger); err != nil {
38+
logCommands := viper.GetBool("log-commands")
39+
if err := runStdioServer(logger, logCommands); err != nil {
3740
stdlog.Fatal("failed to run stdio server:", err)
3841
}
3942
},
@@ -45,9 +48,11 @@ func init() {
4548

4649
// Add global flags that will be shared by all commands
4750
rootCmd.PersistentFlags().String("log-file", "", "Path to log file")
51+
rootCmd.PersistentFlags().Bool("enable-command-logging", false, "When enabled, the server will log all command requests and responses to the log file")
4852

4953
// Bind flag to viper
5054
viper.BindPFlag("log-file", rootCmd.PersistentFlags().Lookup("log-file"))
55+
viper.BindPFlag("enable-command-logging", rootCmd.PersistentFlags().Lookup("enable-command-logging"))
5156

5257
// Add subcommands
5358
rootCmd.AddCommand(stdioCmd)
@@ -70,12 +75,13 @@ func initLogger(outPath string) (*log.Logger, error) {
7075
}
7176

7277
logger := log.New()
78+
logger.SetLevel(log.DebugLevel)
7379
logger.SetOutput(file)
7480

7581
return logger, nil
7682
}
7783

78-
func runStdioServer(logger *log.Logger) error {
84+
func runStdioServer(logger *log.Logger, logCommands bool) error {
7985
// Create app context
8086
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
8187
defer stop()
@@ -97,7 +103,14 @@ func runStdioServer(logger *log.Logger) error {
97103
// Start listening for messages
98104
errC := make(chan error, 1)
99105
go func() {
100-
errC <- stdioServer.Listen(ctx, os.Stdin, os.Stdout)
106+
in, out := io.Reader(os.Stdin), io.Writer(os.Stdout)
107+
108+
if logCommands {
109+
loggedIO := iolog.NewIOLogger(in, out, logger)
110+
in, out = loggedIO, loggedIO
111+
}
112+
113+
errC <- stdioServer.Listen(ctx, in, out)
101114
}()
102115

103116
// Output github-mcp-server string

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@ require (
99
github.com/sirupsen/logrus v1.9.3
1010
github.com/spf13/cobra v1.9.1
1111
github.com/spf13/viper v1.19.0
12+
github.com/stretchr/testify v1.9.0
1213
golang.org/x/exp v0.0.0-20230905200255-921286631fa9
1314
)
1415

1516
require (
17+
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
1618
github.com/fsnotify/fsnotify v1.7.0 // indirect
1719
github.com/google/go-querystring v1.1.0 // indirect
1820
github.com/google/uuid v1.6.0 // indirect
@@ -21,6 +23,7 @@ require (
2123
github.com/magiconair/properties v1.8.7 // indirect
2224
github.com/mitchellh/mapstructure v1.5.0 // indirect
2325
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
26+
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
2427
github.com/sagikazarmark/locafero v0.4.0 // indirect
2528
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
2629
github.com/sourcegraph/conc v0.3.0 // indirect

pkg/log/io.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package log
2+
3+
import (
4+
"io"
5+
6+
log "github.com/sirupsen/logrus"
7+
)
8+
9+
// IOLogger is a wrapper around io.Reader and io.Writer that can be used
10+
// to log the data being read and written from the underlying streams
11+
type IOLogger struct {
12+
reader io.Reader
13+
writer io.Writer
14+
logger *log.Logger
15+
}
16+
17+
// NewIOLogger creates a new IOLogger instance
18+
func NewIOLogger(r io.Reader, w io.Writer, logger *log.Logger) *IOLogger {
19+
return &IOLogger{
20+
reader: r,
21+
writer: w,
22+
logger: logger,
23+
}
24+
}
25+
26+
// Read reads data from the underlying io.Reader and logs it.
27+
func (l *IOLogger) Read(p []byte) (n int, err error) {
28+
if l.reader == nil {
29+
return 0, io.EOF
30+
}
31+
n, err = l.reader.Read(p)
32+
if n > 0 {
33+
l.logger.Infof("[stdin]: received %d bytes: %s", n, string(p[:n]))
34+
}
35+
return n, err
36+
}
37+
38+
// Write writes data to the underlying io.Writer and logs it.
39+
func (l *IOLogger) Write(p []byte) (n int, err error) {
40+
if l.writer == nil {
41+
return 0, io.ErrClosedPipe
42+
}
43+
l.logger.Infof("[stdout]: sending %d bytes: %s", len(p), string(p))
44+
return l.writer.Write(p)
45+
}

pkg/log/io_test.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package log
2+
3+
import (
4+
"bytes"
5+
"strings"
6+
"testing"
7+
8+
log "github.com/sirupsen/logrus"
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
func TestLoggedReadWriter(t *testing.T) {
13+
t.Run("Read method logs and passes data", func(t *testing.T) {
14+
// Setup
15+
inputData := "test input data"
16+
reader := strings.NewReader(inputData)
17+
18+
// Create logger with buffer to capture output
19+
var logBuffer bytes.Buffer
20+
logger := log.New()
21+
logger.SetOutput(&logBuffer)
22+
logger.SetFormatter(&log.TextFormatter{
23+
DisableTimestamp: true,
24+
})
25+
26+
lrw := NewIOLogger(reader, nil, logger)
27+
28+
// Test Read
29+
buf := make([]byte, 100)
30+
n, err := lrw.Read(buf)
31+
32+
// Assertions
33+
assert.NoError(t, err)
34+
assert.Equal(t, len(inputData), n)
35+
assert.Equal(t, inputData, string(buf[:n]))
36+
assert.Contains(t, logBuffer.String(), "[stdin]")
37+
assert.Contains(t, logBuffer.String(), inputData)
38+
})
39+
40+
t.Run("Write method logs and passes data", func(t *testing.T) {
41+
// Setup
42+
outputData := "test output data"
43+
var writeBuffer bytes.Buffer
44+
45+
// Create logger with buffer to capture output
46+
var logBuffer bytes.Buffer
47+
logger := log.New()
48+
logger.SetOutput(&logBuffer)
49+
logger.SetFormatter(&log.TextFormatter{
50+
DisableTimestamp: true,
51+
})
52+
53+
lrw := NewIOLogger(nil, &writeBuffer, logger)
54+
55+
// Test Write
56+
n, err := lrw.Write([]byte(outputData))
57+
58+
// Assertions
59+
assert.NoError(t, err)
60+
assert.Equal(t, len(outputData), n)
61+
assert.Equal(t, outputData, writeBuffer.String())
62+
assert.Contains(t, logBuffer.String(), "[stdout]")
63+
assert.Contains(t, logBuffer.String(), outputData)
64+
})
65+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy