Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Add logging stack with slog adapter#1574

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Draft
omgitsads wants to merge9 commits intomain
base:main
Choose a base branch
Loading
fromadd-logging-stack
Draft

Conversation

@omgitsads
Copy link
Member

This allows us to have a deeper observability stack in the MCP server, particularly for surfacing logging in the remote MCP server.

SamMorrowDrums reacted with heart emoji
@mattdhollowaymattdholloway marked this pull request as ready for reviewDecember 12, 2025 15:47
@mattdhollowaymattdholloway requested a review froma team as acode ownerDecember 12, 2025 15:47
CopilotAI review requested due to automatic review settingsDecember 12, 2025 15:47
@mattdhollowaymattdholloway marked this pull request as draftDecember 12, 2025 15:47
Copy link
Contributor

CopilotAI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Pull request overview

This PR introduces a comprehensive logging infrastructure for the MCP server to enable deeper observability, particularly for the remote MCP server deployment. The implementation adds a newpkg/observability package with an slog-based logging abstraction layer.

Key changes:

  • Introduces a new observability package with Logger interface and two implementations (SlogLogger and NoopLogger)
  • Modifies exported API signatures to thread observability through the system (DefaultToolsetGroup, IssueRead)
  • Integrates logging into the server initialization and tool handlers

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 8 comments.

Show a summary per file
FileDescription
pkg/observability/observability.goIntroduces Exporters struct to hold observability components (currently just Logger)
pkg/observability/log/log.goDefines Logger interface with standard logging methods and level support
pkg/observability/log/level.goDefines custom log Level type with constants for debug through fatal
pkg/observability/log/slog_adapter.goImplements Logger interface as adapter for Go's standard slog.Logger
pkg/observability/log/noop_adapter.goImplements no-op Logger for testing and scenarios where logging is disabled
pkg/github/tools.goUpdates DefaultToolsetGroup to accept observability.Exporters parameter
pkg/github/issues.goUpdates IssueRead to accept observability.Exporters and adds logging statement
pkg/github/issues_test.goUpdates test setup to create and pass NoopLogger through observability.Exporters
internal/ghmcp/server.goIntegrates slog adapter into server initialization, wrapping existing slog.Logger
cmd/github-mcp-server/generate_docs.goUpdates documentation generator to pass NoopLogger for observability parameter

bretthowell714-source reacted with laugh emoji
Comment on lines +45 to +48
func (l *SlogLogger) Fatal(msg string, fields ...slog.Attr) {
l.Log(context.Background(), FatalLevel, msg, fields...)
panic("fatal log called")
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

The Fatal method panics unconditionally after logging, which means the log message may not be flushed to its destination before the panic occurs. Since slog buffers writes, the fatal log entry could be lost. Consider calling Sync() on the underlying logger before panicking, or use os.Exit(1) instead of panic to allow deferred functions and cleanup to run.

Copilot uses AI. Check for mistakes.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Thank you

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Thank you

Comment on lines +40 to +41
func (l *NoopLogger) Fatal(_ string, _ ...slog.Attr) {
// No-op

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

The NoopLogger's Fatal method should maintain consistent behavior with the SlogLogger implementation. Since SlogLogger panics after logging a fatal message, NoopLogger should also panic to ensure consistent Fatal semantics across implementations. Otherwise, code that relies on Fatal terminating execution could continue running unexpectedly when using NoopLogger.

Suggested change
func (l*NoopLogger)Fatal(_string,_...slog.Attr) {
// No-op
func (l*NoopLogger)Fatal(msgstring,_...slog.Attr) {
panic("NoopLogger.Fatal called: "+msg)

Copilot uses AI. Check for mistakes.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Your a badass ai

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Your a badass ai

Comment on lines +13 to +103
func NewSlogLogger(logger *slog.Logger, level Level) *SlogLogger {
return &SlogLogger{
logger: logger,
level: level,
}
}

func (l *SlogLogger) Level() Level {
return l.level
}

func (l *SlogLogger) Log(ctx context.Context, level Level, msg string, fields ...slog.Attr) {
slogLevel := convertLevel(level)
l.logger.LogAttrs(ctx, slogLevel, msg, fields...)
}

func (l *SlogLogger) Debug(msg string, fields ...slog.Attr) {
l.Log(context.Background(), DebugLevel, msg, fields...)
}

func (l *SlogLogger) Info(msg string, fields ...slog.Attr) {
l.Log(context.Background(), InfoLevel, msg, fields...)
}

func (l *SlogLogger) Warn(msg string, fields ...slog.Attr) {
l.Log(context.Background(), WarnLevel, msg, fields...)
}

func (l *SlogLogger) Error(msg string, fields ...slog.Attr) {
l.Log(context.Background(), ErrorLevel, msg, fields...)
}

func (l *SlogLogger) Fatal(msg string, fields ...slog.Attr) {
l.Log(context.Background(), FatalLevel, msg, fields...)
panic("fatal log called")
}

func (l *SlogLogger) WithFields(fields ...slog.Attr) Logger {
fieldKvPairs := make([]any, 0, len(fields)*2)
for _, attr := range fields {
k, v := attr.Key, attr.Value
fieldKvPairs = append(fieldKvPairs, k, v.Any())
}
return &SlogLogger{
logger: l.logger.With(fieldKvPairs...),
level: l.level,
}
}

func (l *SlogLogger) WithError(err error) Logger {
return &SlogLogger{
logger: l.logger.With("error", err.Error()),
level: l.level,
}
}

func (l *SlogLogger) Named(name string) Logger {
return &SlogLogger{
logger: l.logger.With("logger", name),
level: l.level,
}
}

func (l *SlogLogger) WithLevel(level Level) Logger {
return &SlogLogger{
logger: l.logger,
level: level,
}
}

func (l *SlogLogger) Sync() error {
// Slog does not require syncing
return nil
}

func convertLevel(level Level) slog.Level {
switch level {
case DebugLevel:
return slog.LevelDebug
case InfoLevel:
return slog.LevelInfo
case WarnLevel:
return slog.LevelWarn
case ErrorLevel:
return slog.LevelError
case FatalLevel:
return slog.LevelError
default:
return slog.LevelInfo
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

The SlogLogger adapter lacks test coverage for its core functionality including level conversion, field marshaling, error handling, and logger chaining. Similar packages in this repository (like pkg/log and pkg/sanitize) have comprehensive test files. Consider adding tests to verify: level conversion accuracy, WithFields properly converts slog.Attr to key-value pairs, WithError extracts error strings correctly, and logger chaining with Named/WithLevel preserves the underlying logger.

Copilot uses AI. Check for mistakes.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Thank you

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Thank you

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Thank you

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Thank you

Comment on lines +12 to +62
func NewNoopLogger() *NoopLogger {
return &NoopLogger{}
}

func (l *NoopLogger) Level() Level {
return DebugLevel
}

func (l *NoopLogger) Log(_ context.Context, _ Level, _ string, _ ...slog.Attr) {
// No-op
}

func (l *NoopLogger) Debug(_ string, _ ...slog.Attr) {
// No-op
}

func (l *NoopLogger) Info(_ string, _ ...slog.Attr) {
// No-op
}

func (l *NoopLogger) Warn(_ string, _ ...slog.Attr) {
// No-op
}

func (l *NoopLogger) Error(_ string, _ ...slog.Attr) {
// No-op
}

func (l *NoopLogger) Fatal(_ string, _ ...slog.Attr) {
// No-op
}

func (l *NoopLogger) WithFields(_ ...slog.Attr) Logger {
return l
}

func (l *NoopLogger) WithError(_ error) Logger {
return l
}

func (l *NoopLogger) Named(_ string) Logger {
return l
}

func (l *NoopLogger) WithLevel(_ Level) Logger {
return l
}

func (l *NoopLogger) Sync() error {
return nil
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

The NoopLogger implementation lacks test coverage. While it's a simple no-op implementation, tests should verify that it correctly implements the Logger interface and that all methods are safe to call without side effects. Similar packages in this repository have corresponding test files.

Copilot uses AI. Check for mistakes.
//
// This is useful when trying to get the string values for Level and mapping level in other external libraries. For example:
// ```
// trace.SetLogLevel(kvp.String("loglevel", log.DebugLevel.String())

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

The example code in the comment contains a syntax error. It showskvp.String("loglevel", log.DebugLevel.String()) but should likely be demonstrating a valid function call.

Suggested change
// trace.SetLogLevel(kvp.String("loglevel", log.DebugLevel.String())
// trace.SetLogLevel(kvp.String("loglevel", log.DebugLevel.String()))

Copilot uses AI. Check for mistakes.
WarnLevel = Level{"warn"}
// ErrorLevel causes all logs of level error or more severe to be logged
ErrorLevel = Level{"error"}
// FatalLevel causes only logs of level fatal to be logged

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

The comment for FatalLevel states "causes only logs of level fatal to be logged" which is inconsistent with other level comments. This suggests filtering that would suppress all other logs, but FatalLevel should be described as "causes all logs of level fatal to be logged" or "causes only fatal severity logs to be logged" to maintain consistency with the pattern of the other levels.

Suggested change
// FatalLevel causesonly logs of level fatal to be logged
// FatalLevel causesall logs of level fatal to be logged

Copilot uses AI. Check for mistakes.
Comment on lines +7 to +12
type Exporters struct {
Logger log.Logger
}

func NewExporters(logger log.Logger) *Exporters {
return &Exporters{

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

The struct name "Exporters" is unclear for a type that currently only contains a Logger. The term "exporters" typically refers to components that send telemetry data to external systems (like metrics/traces exporters in OpenTelemetry). Consider renaming to something more descriptive like "Observability", "ObservabilityContext", or "Telemetry" to better represent its purpose. If this struct is intended to hold multiple exporters in the future (metrics, traces, etc.), add a comment documenting that intent.

Suggested change
typeExportersstruct {
Loggerlog.Logger
}
funcNewExporters(logger log.Logger)*Exporters {
return&Exporters{
// Observability holds observability-related components such as logging.
// Additional fields (e.g., metrics, tracing) can be added here in the future.
typeObservabilitystruct {
Loggerlog.Logger
}
funcNewObservability(logger log.Logger)*Observability {
return&Observability{

Copilot uses AI. Check for mistakes.
return l.level
}

func (l *SlogLogger) Log(ctx context.Context, level Level, msg string, fields ...slog.Attr) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Thelevel field inSlogLogger is stored but never used for filtering log messages. The Log method ignoresl.level and passes all messages to the underlying slog.Logger regardless of their severity. This means the level parameter in NewSlogLogger and the Level() method serve no practical purpose. Either implement level filtering in the Log method to respect the configured level, or remove the unused level field if filtering is meant to be handled by the underlying slog.Logger configuration.

Suggested change
func (l*SlogLogger)Log(ctx context.Context,levelLevel,msgstring,fields...slog.Attr) {
func (l*SlogLogger)Log(ctx context.Context,levelLevel,msgstring,fields...slog.Attr) {
// Only log messages at or above the configured level
iflevel< l.level {
return
}

Copilot uses AI. Check for mistakes.
@SamMorrowDrums
Copy link
Collaborator

I am working on a major rewrite of toolsets that will mean we don't need to pass in logging as an arg, using a tool dependencies struct instead (Adam and I discussed this), as well as avoiding having to pass fake dependencies in order to only get a tool definition, by moving the closure around the tool handler and returning a function that returns a handler instead.

I'm close to done, and I think I'll publish an ADR along with change as changes are substantial.

Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

Reviewers

Copilot code reviewCopilotCopilot left review comments

+1 more reviewer

@bretthowell714-sourcebretthowell714-sourcebretthowell714-source approved these changes

Reviewers whose approvals may not affect merge requirements

At least 1 approving review is required to merge this pull request.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

5 participants

@omgitsads@SamMorrowDrums@bretthowell714-source@mattdholloway

[8]ページ先頭

©2009-2025 Movatter.jp