Skip to content

Initial OSS logging adapter for http#2008

Open
mattdholloway wants to merge 13 commits intomainfrom
add-logging-stack-v2
Open

Initial OSS logging adapter for http#2008
mattdholloway wants to merge 13 commits intomainfrom
add-logging-stack-v2

Conversation

@mattdholloway
Copy link
Contributor

@mattdholloway mattdholloway commented Feb 12, 2026

Summary

Why

Fixes #

What changed

Observability and Logging Infrastructure

  • Added a new logging interface (Logger) and supporting types (Field, Level) in the pkg/observability/log package, providing structured, backend-agnostic logging primitives. [1] [2] [3]
  • Implemented logging adapters: a no-op logger (NoopLogger) and an adapter for Go's slog (SlogLogger), allowing flexible logger injection and usage. [1] [2]

Dependency Injection and Configuration

  • Extended ToolDependencies, BaseDeps, and RequestDeps to include logger and metrics clients, and updated their constructors to accept and initialize a logger. This enables tools and server code to access observability components via dependency injection. [1] [2] [3] [4] [5] [6] [7] [8] [9]

Server Integration

  • Modified server initialization (internal/ghmcp/server.go, pkg/http/server.go) to pass the logger into dependency constructors, ensuring observability is available throughout server components. [1] [2]

Testing and Stub Updates

  • Updated test and stub code to handle the new logger argument and interface, ensuring compatibility and testability with the observability layer. [1] [2] [3] [4] [5] [6] [7] [8] [9] [10]

Imports and Package Organization

  • Added imports for new observability packages and updated references throughout the codebase to support the new logging and metrics infrastructure. [1] [2]

MCP impact

  • No tool or API changes
  • Tool schema or behavior changed
  • New tool added

Prompts tested (tool changes only)

Security / limits

  • No security or limits impact
  • Auth / permissions considered
  • Data exposure, filtering, or token/size limits considered

Tool renaming

  • I am renaming tools as part of this PR (e.g. a part of a consolidation effort)
    • I have added the new tool aliases in deprecated_tool_aliases.go
  • I am not renaming tools as part of this PR

Note: 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

  • Linted locally with ./script/lint
  • Tested locally with ./script/test

Docs

  • Not needed
  • Updated (README / docs / examples)

@mattdholloway mattdholloway marked this pull request as ready for review February 18, 2026 14:29
@mattdholloway mattdholloway requested a review from a team as a code owner February 18, 2026 14:29
Copilot AI review requested due to automatic review settings February 18, 2026 14:29
Copy link
Contributor

Copilot AI 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 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/observability with backend-agnostic log.Logger / metrics.Metrics interfaces plus noop + slog adapters and tests.
  • Extended pkg/github dependency plumbing to provide Logger() / Metrics() on ToolDependencies, and to accept a *slog.Logger in NewBaseDeps / NewRequestDeps.
  • Threaded the HTTP server’s slog.Logger into request deps and the stdio server’s cfg.Logger into 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()

Comment on lines +146 to +150
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())
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
mattdholloway and others added 4 commits February 25, 2026 11:34
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>
Copy link
Contributor

Copilot AI 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

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 NewBaseDeps and NewRequestDeps. 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())
	}

Comment on lines +9 to +36
type SlogLogger struct {
logger *slog.Logger
level Level
}

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 ...Field) {
slogLevel := convertLevel(level)
l.logger.LogAttrs(ctx, slogLevel, msg, fieldsToSlogAttrs(fields)...)
}

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

func (l *SlogLogger) Info(msg string, fields ...Field) {
l.Log(context.Background(), InfoLevel, msg, fields...)
}
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

SlogLogger stores a level but it is never used to filter log emission (and the convenience methods always log at fixed levels). As a result, NewSlogLogger(..., InfoLevel) does not actually suppress debug logs if the underlying slog handler allows them, and WithLevel(...) only changes Level() without changing behavior. Consider enforcing l.level inside Log (drop messages below the configured minimum) or remove the level field/WithLevel API and rely solely on the underlying slog handler’s level configuration.

Copilot uses AI. Check for mistakes.
return l
}
return &SlogLogger{
logger: l.logger.With("error", err.Error()),
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
logger: l.logger.With("error", err.Error()),
logger: l.logger.With("error", err),

Copilot uses AI. Check for mistakes.
Comment on lines +46 to +50
func (l *SlogLogger) Fatal(msg string, fields ...Field) {
l.Log(context.Background(), FatalLevel, msg, fields...)
_ = l.Sync()
panic("fatal: " + msg)
}
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

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).

Copilot uses AI. Check for mistakes.
mattdholloway and others added 2 commits February 26, 2026 15:03
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants