Conversation
…to add-logging-stack-v2
…to add-logging-stack-v2
There was a problem hiding this comment.
Pull request overview
This PR introduces a new pkg/observability layer (logger + metrics adapters) and wires it into the GitHub tool dependency construction for both HTTP and stdio server paths.
Changes:
- Added
pkg/observabilitywith backend-agnosticlog.Logger/metrics.Metricsinterfaces plus noop +slogadapters and tests. - Extended
pkg/githubdependency plumbing to provideLogger()/Metrics()onToolDependencies, and to accept a*slog.LoggerinNewBaseDeps/NewRequestDeps. - Threaded the HTTP server’s
slog.Loggerinto request deps and the stdio server’scfg.Loggerinto base deps.
Reviewed changes
Copilot reviewed 20 out of 20 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| pkg/observability/observability.go | Defines Exporters interface and basic implementation. |
| pkg/observability/observability_test.go | Unit tests for NewExporters. |
| pkg/observability/log/logger.go | Adds backend-agnostic logger interface. |
| pkg/observability/log/level.go | Adds log level type/constants. |
| pkg/observability/log/field.go | Adds structured field type + helpers. |
| pkg/observability/log/noop_adapter.go | No-op logger implementation. |
| pkg/observability/log/slog_adapter.go | slog-backed logger adapter. |
| pkg/observability/log/slog_adapter_test.go | Tests for SlogLogger. |
| pkg/observability/metrics/metrics.go | Adds backend-agnostic metrics interface. |
| pkg/observability/metrics/noop_adapter.go | No-op metrics implementation. |
| pkg/observability/metrics/noop_adapter_test.go | Tests for NoopMetrics. |
| pkg/observability/metrics/slog_adapter.go | slog-backed metrics adapter. |
| pkg/observability/metrics/slog_adapter_test.go | Tests for SlogMetrics. |
| pkg/github/dependencies.go | Adds observability to ToolDependencies; updates constructors to accept *slog.Logger. |
| pkg/github/dependencies_test.go | Updates constructor calls for new signature. |
| pkg/github/feature_flags_test.go | Updates constructor calls for new signature. |
| pkg/github/dynamic_tools_test.go | Updates constructor calls for new signature. |
| pkg/github/server_test.go | Updates test deps to provide Logger/Metrics. |
| pkg/http/server.go | Passes HTTP server logger into NewRequestDeps. |
| internal/ghmcp/server.go | Passes cfg.Logger into NewBaseDeps. |
Comments suppressed due to low confidence (3)
pkg/github/dependencies.go:145
- NewBaseDeps’ signature now requires a *slog.Logger, which is a breaking change for downstream callers. Consider preserving the existing constructor (old signature) and adding a new constructor/option for observability (e.g., NewBaseDepsWithLogger or functional options) to avoid forcing updates for all consumers.
flags FeatureFlags,
contentWindowSize int,
featureChecker inventory.FeatureFlagChecker,
logger *slog.Logger,
) *BaseDeps {
pkg/github/dependencies.go:296
- NewRequestDeps’ signature now requires a *slog.Logger, which is a breaking change for downstream callers. Consider keeping the old signature and adding an alternate constructor/option for supplying a logger to avoid a hard API break.
t translations.TranslationHelperFunc,
contentWindowSize int,
featureChecker inventory.FeatureFlagChecker,
logger *slog.Logger,
) *RequestDeps {
pkg/github/server_test.go:76
- stubDeps.Metrics ignores the provided ctx and calls the exporter with context.Background(), which can hide context-dependent metrics behavior in tests. Pass the incoming ctx through to s.obsv.Metrics(ctx) for consistency.
func (s stubDeps) Metrics(_ context.Context) mcpMetrics.Metrics {
if s.obsv != nil {
return s.obsv.Metrics(context.Background())
}
return mcpMetrics.NewNoopMetrics()
pkg/github/dependencies.go
Outdated
| var obsv observability.Exporters | ||
| if logger != nil { | ||
| obsv = observability.NewExporters(obsvLog.NewSlogLogger(logger, obsvLog.InfoLevel), obsvMetrics.NewNoopMetrics()) | ||
| } else { | ||
| obsv = observability.NewExporters(obsvLog.NewNoopLogger(), obsvMetrics.NewNoopMetrics()) |
There was a problem hiding this comment.
The default observability/exporters initialization logic is duplicated in NewBaseDeps and NewRequestDeps. Consider extracting a small helper (e.g., newDefaultExporters(logger *slog.Logger) observability.Exporters) to keep behavior consistent and avoid future drift.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
The slog-based metrics adapter was never used — OSS always uses NoopMetrics and the remote server has its own DataDog-backed adapter. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 18 changed files in this pull request and generated 6 comments.
Comments suppressed due to low confidence (1)
pkg/github/dependencies.go:151
- Observability exporter initialization is duplicated in both
NewBaseDepsandNewRequestDeps. Consider extracting a small helper (e.g., build exporters from*slog.Logger) so future changes (adding real metrics, changing default level, etc.) don’t need to be made in multiple places.
var obsv observability.Exporters
if logger != nil {
obsv = observability.NewExporters(obsvLog.NewSlogLogger(logger, obsvLog.InfoLevel), obsvMetrics.NewNoopMetrics())
} else {
obsv = observability.NewExporters(obsvLog.NewNoopLogger(), obsvMetrics.NewNoopMetrics())
}
| return l | ||
| } | ||
| return &SlogLogger{ | ||
| logger: l.logger.With("error", err.Error()), |
There was a problem hiding this comment.
WithError logs err.Error() as a string, which discards the original error value (wrapping/unwrapping, error type, and any structured formatting a handler might provide). Prefer attaching the error itself (or reusing the Err(err) field helper) so downstream handlers can render/encode errors consistently.
| logger: l.logger.With("error", err.Error()), | |
| logger: l.logger.With("error", err), |
| func (l *SlogLogger) Fatal(msg string, fields ...Field) { | ||
| l.Log(context.Background(), FatalLevel, msg, fields...) | ||
| _ = l.Sync() | ||
| panic("fatal: " + msg) | ||
| } |
There was a problem hiding this comment.
Fatal currently calls panic(...). If this logger is ever used in server code, a fatal log will crash the process (and may bypass graceful shutdown / deferred cleanup). Consider aligning semantics with common logging expectations (e.g., os.Exit(1) after logging/sync, or removing Fatal from the interface and returning errors instead).
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
omgitsads
left a comment
There was a problem hiding this comment.
Looking good, but let's just firm up the interface so that there is always a logger instance available and we don't have to do nil checks at usage.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 12 out of 12 changed files in this pull request and generated 5 comments.
Comments suppressed due to low confidence (2)
pkg/github/dependencies.go:202
BaseDeps.Metricsreturns nil whenObsvis nil. Similar to the logger, this makesToolDependencies.Metrics(ctx)unsafe to call and pushes nil checks onto every caller. Consider defaulting to a noop metrics implementation whenObsvis nil (or makeObsvrequired).
func (d BaseDeps) Metrics(ctx context.Context) metrics.Metrics {
if d.Obsv == nil {
return nil
}
return d.Obsv.Metrics(ctx)
pkg/github/dependencies.go:422
RequestDeps.Metricsdereferencesd.obsvwithout guarding against nil. IfNewRequestDepsis called with a nilobsv, this will panic; consider defaultingobsvin the constructor or returning a noop metrics implementation whenobsvis nil.
// Metrics implements ToolDependencies.
func (d *RequestDeps) Metrics(ctx context.Context) metrics.Metrics {
return d.obsv.Metrics(ctx)
}
| func (d BaseDeps) Logger(_ context.Context) *slog.Logger { | ||
| if d.Obsv == nil { | ||
| return nil | ||
| } | ||
| return d.Obsv.Logger() |
There was a problem hiding this comment.
BaseDeps.Logger returns nil when Obsv is nil. Since NewExporters enforces a non-nil logger and ToolDependencies.Logger(ctx) is expected to be callable, returning nil here is an inconsistent contract and invites nil-pointer panics at call sites. Consider returning a discard logger when Obsv is nil (or ensuring BaseDeps always has a non-nil Obsv).
This issue also appears on line 198 of the same file.
There was a problem hiding this comment.
@copilot open a new pull request to apply changes based on this feedback
| func (d *RequestDeps) Logger(_ context.Context) *slog.Logger { | ||
| return d.obsv.Logger() | ||
| } | ||
|
|
||
| // Metrics implements ToolDependencies. | ||
| func (d *RequestDeps) Metrics(ctx context.Context) metrics.Metrics { |
There was a problem hiding this comment.
RequestDeps.Logger dereferences d.obsv without a nil check, but NewRequestDeps accepts obsv observability.Exporters and call sites/tests can pass nil. This can panic if obsv is not provided; consider making obsv required (and validating it) or defaulting it to discard/noop exporters in the constructor.
This issue also appears on line 419 of the same file.
| func (d *RequestDeps) Logger(_ context.Context) *slog.Logger { | |
| return d.obsv.Logger() | |
| } | |
| // Metrics implements ToolDependencies. | |
| func (d *RequestDeps) Metrics(ctx context.Context) metrics.Metrics { | |
| func (d *RequestDeps) Logger(_ context.Context) *slog.Logger { | |
| if d.obsv == nil { | |
| return slog.Default() | |
| } | |
| return d.obsv.Logger() | |
| } | |
| // Metrics implements ToolDependencies. | |
| func (d *RequestDeps) Metrics(ctx context.Context) metrics.Metrics { | |
| if d.obsv == nil { | |
| return nil | |
| } |
| func NewExporters(logger *slog.Logger, metrics metrics.Metrics) (Exporters, error) { | ||
| if logger == nil { | ||
| return nil, errors.New("logger must not be nil: use slog.New(slog.DiscardHandler) to discard logs") | ||
| } |
There was a problem hiding this comment.
NewExporters allows metrics to be nil, which means Exporters.Metrics(...) will return nil. Since callers will typically expect a usable metrics client (even if noop), consider defaulting nil metrics to a NoopMetrics implementation or returning an error when metrics is nil.
| } | |
| } | |
| if metrics == nil { | |
| return nil, errors.New("metrics must not be nil; provide a no-op metrics implementation if metrics are disabled") | |
| } |
| func NewExporters(logger *slog.Logger, metrics metrics.Metrics) (Exporters, error) { | ||
| if logger == nil { | ||
| return nil, errors.New("logger must not be nil: use slog.New(slog.DiscardHandler) to discard logs") | ||
| } | ||
| return &exporters{ | ||
| logger: logger, | ||
| metrics: metrics, |
There was a problem hiding this comment.
The metrics parameter name shadows the imported metrics package name, which makes this function harder to read/maintain. Renaming the parameter (e.g., m) avoids the shadowing.
| func NewExporters(logger *slog.Logger, metrics metrics.Metrics) (Exporters, error) { | |
| if logger == nil { | |
| return nil, errors.New("logger must not be nil: use slog.New(slog.DiscardHandler) to discard logs") | |
| } | |
| return &exporters{ | |
| logger: logger, | |
| metrics: metrics, | |
| func NewExporters(logger *slog.Logger, m metrics.Metrics) (Exporters, error) { | |
| if logger == nil { | |
| return nil, errors.New("logger must not be nil: use slog.New(slog.DiscardHandler) to discard logs") | |
| } | |
| return &exporters{ | |
| logger: logger, | |
| metrics: m, |
|
@mattdholloway I've opened a new pull request, #2264, to work on those changes. Once the pull request is ready, I'll request review from you. |
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…tation (#2264) * Initial plan * Fix BaseDeps.Logger and BaseDeps.Metrics to return safe defaults when Obsv is nil Agent-Logs-Url: https://github.com/github/github-mcp-server/sessions/53221b0b-abb4-4138-a147-3ce9e13b379a Co-authored-by: mattdholloway <918573+mattdholloway@users.noreply.github.com> * Fix nil metrics in server.go by passing metrics.NewNoopMetrics() to NewExporters Agent-Logs-Url: https://github.com/github/github-mcp-server/sessions/53221b0b-abb4-4138-a147-3ce9e13b379a Co-authored-by: mattdholloway <918573+mattdholloway@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: mattdholloway <918573+mattdholloway@users.noreply.github.com> Co-authored-by: Matt Holloway <mattdholloway@github.com>
Summary
This pull request introduces a new observability layer to the codebase, providing a unified interface for logging and metrics. The main changes include the addition of a backend-agnostic
Metricsinterface (with a no-op implementation), a newExporterstype to bundle logger and metrics for dependency injection, and the integration of these observability primitives into the server and dependency injection systems.Why
Fixes #
What changed
Observability Abstraction and Integration
observabilitypackage, which provides theExportersinterface for bundling a structured logger (slog.Logger) and a metrics client, along with a default implementation and test coverage. [1] [2]metricssubpackage, defining a backend-agnosticMetricsinterface and aNoopMetricsimplementation for use in tests or as a default, with comprehensive tests. [1] [2] [3]Dependency Injection and Interface Updates
ToolDependenciesinterface to addLogger(ctx)andMetrics(ctx)methods, and implemented these in bothBaseDepsandRequestDepsusing the newobservability.Exportersbundle. [1] [2] [3] [4] [5]NewBaseDeps,NewRequestDeps) and their usages throughout the codebase and tests to accept and propagate anobservability.Exportersparameter. [1] [2] [3] [4] [5] [6]Test and Stub Updates
Imports and Boilerplate
These changes collectively provide a unified and extensible foundation for observability (logging and metrics) throughout the codebase, making it easier to integrate different backends, improve testability, and maintain consistent patterns.
MCP impact
Prompts tested (tool changes only)
Security / limits
Tool renaming
deprecated_tool_aliases.goNote: if you're renaming tools, you must add the tool aliases. For more information on how to do so, please refer to the official docs.
Lint & tests
./script/lint./script/testDocs