-
Notifications
You must be signed in to change notification settings - Fork 3.8k
Initial OSS logging adapter for http #2008
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 our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
a8bf0b0
4a2f8ed
ba9d315
cbf99e7
cc5b8e7
77f6e57
3bbfaa0
dd57d84
2a20a2a
1648775
7c5ea0a
121daa3
39445b0
f7d3a75
30b2952
c0a68f5
457f64d
f94b729
e31ea3b
095d9f2
1ae4a03
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -4,13 +4,16 @@ import ( | |||||||||||||||||||||||||||||||||||||||
| "context" | ||||||||||||||||||||||||||||||||||||||||
| "errors" | ||||||||||||||||||||||||||||||||||||||||
| "fmt" | ||||||||||||||||||||||||||||||||||||||||
| "log/slog" | ||||||||||||||||||||||||||||||||||||||||
| "net/http" | ||||||||||||||||||||||||||||||||||||||||
| "os" | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| ghcontext "github.com/github/github-mcp-server/pkg/context" | ||||||||||||||||||||||||||||||||||||||||
| "github.com/github/github-mcp-server/pkg/http/transport" | ||||||||||||||||||||||||||||||||||||||||
| "github.com/github/github-mcp-server/pkg/inventory" | ||||||||||||||||||||||||||||||||||||||||
| "github.com/github/github-mcp-server/pkg/lockdown" | ||||||||||||||||||||||||||||||||||||||||
| "github.com/github/github-mcp-server/pkg/observability" | ||||||||||||||||||||||||||||||||||||||||
| "github.com/github/github-mcp-server/pkg/observability/metrics" | ||||||||||||||||||||||||||||||||||||||||
| "github.com/github/github-mcp-server/pkg/raw" | ||||||||||||||||||||||||||||||||||||||||
| "github.com/github/github-mcp-server/pkg/scopes" | ||||||||||||||||||||||||||||||||||||||||
| "github.com/github/github-mcp-server/pkg/translations" | ||||||||||||||||||||||||||||||||||||||||
|
|
@@ -94,6 +97,14 @@ type ToolDependencies interface { | |||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| // IsFeatureEnabled checks if a feature flag is enabled. | ||||||||||||||||||||||||||||||||||||||||
| IsFeatureEnabled(ctx context.Context, flagName string) bool | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| // Logger returns the structured logger, optionally enriched with | ||||||||||||||||||||||||||||||||||||||||
| // request-scoped data from ctx. Integrators provide their own slog.Handler | ||||||||||||||||||||||||||||||||||||||||
| // to control where logs are sent. | ||||||||||||||||||||||||||||||||||||||||
| Logger(ctx context.Context) *slog.Logger | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| // Metrics returns the metrics client | ||||||||||||||||||||||||||||||||||||||||
| Metrics(ctx context.Context) metrics.Metrics | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| // BaseDeps is the standard implementation of ToolDependencies for the local server. | ||||||||||||||||||||||||||||||||||||||||
|
|
@@ -113,6 +124,9 @@ type BaseDeps struct { | |||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| // Feature flag checker for runtime checks | ||||||||||||||||||||||||||||||||||||||||
| featureChecker inventory.FeatureFlagChecker | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| // Observability exporters (includes logger) | ||||||||||||||||||||||||||||||||||||||||
| Obsv observability.Exporters | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| // Compile-time assertion to verify that BaseDeps implements the ToolDependencies interface. | ||||||||||||||||||||||||||||||||||||||||
|
|
@@ -128,6 +142,7 @@ func NewBaseDeps( | |||||||||||||||||||||||||||||||||||||||
| flags FeatureFlags, | ||||||||||||||||||||||||||||||||||||||||
| contentWindowSize int, | ||||||||||||||||||||||||||||||||||||||||
| featureChecker inventory.FeatureFlagChecker, | ||||||||||||||||||||||||||||||||||||||||
| obsv observability.Exporters, | ||||||||||||||||||||||||||||||||||||||||
| ) *BaseDeps { | ||||||||||||||||||||||||||||||||||||||||
| return &BaseDeps{ | ||||||||||||||||||||||||||||||||||||||||
| Client: client, | ||||||||||||||||||||||||||||||||||||||||
|
|
@@ -138,6 +153,7 @@ func NewBaseDeps( | |||||||||||||||||||||||||||||||||||||||
| Flags: flags, | ||||||||||||||||||||||||||||||||||||||||
| ContentWindowSize: contentWindowSize, | ||||||||||||||||||||||||||||||||||||||||
| featureChecker: featureChecker, | ||||||||||||||||||||||||||||||||||||||||
| Obsv: obsv, | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
|
|
@@ -170,6 +186,22 @@ func (d BaseDeps) GetFlags(_ context.Context) FeatureFlags { return d.Flags } | |||||||||||||||||||||||||||||||||||||||
| // GetContentWindowSize implements ToolDependencies. | ||||||||||||||||||||||||||||||||||||||||
| func (d BaseDeps) GetContentWindowSize() int { return d.ContentWindowSize } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| // Logger implements ToolDependencies. | ||||||||||||||||||||||||||||||||||||||||
| func (d BaseDeps) Logger(_ context.Context) *slog.Logger { | ||||||||||||||||||||||||||||||||||||||||
| if d.Obsv == nil { | ||||||||||||||||||||||||||||||||||||||||
| return slog.New(slog.DiscardHandler) | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| return d.Obsv.Logger() | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| // Metrics implements ToolDependencies. | ||||||||||||||||||||||||||||||||||||||||
| func (d BaseDeps) Metrics(ctx context.Context) metrics.Metrics { | ||||||||||||||||||||||||||||||||||||||||
| if d.Obsv == nil { | ||||||||||||||||||||||||||||||||||||||||
| return metrics.NewNoopMetrics() | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| return d.Obsv.Metrics(ctx) | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| // IsFeatureEnabled checks if a feature flag is enabled. | ||||||||||||||||||||||||||||||||||||||||
| // Returns false if the feature checker is nil, flag name is empty, or an error occurs. | ||||||||||||||||||||||||||||||||||||||||
| // This allows tools to conditionally change behavior based on feature flags. | ||||||||||||||||||||||||||||||||||||||||
|
|
@@ -247,6 +279,9 @@ type RequestDeps struct { | |||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| // Feature flag checker for runtime checks | ||||||||||||||||||||||||||||||||||||||||
| featureChecker inventory.FeatureFlagChecker | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| // Observability exporters (includes logger) | ||||||||||||||||||||||||||||||||||||||||
| obsv observability.Exporters | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| // NewRequestDeps creates a RequestDeps with the provided clients and configuration. | ||||||||||||||||||||||||||||||||||||||||
|
|
@@ -258,6 +293,7 @@ func NewRequestDeps( | |||||||||||||||||||||||||||||||||||||||
| t translations.TranslationHelperFunc, | ||||||||||||||||||||||||||||||||||||||||
| contentWindowSize int, | ||||||||||||||||||||||||||||||||||||||||
| featureChecker inventory.FeatureFlagChecker, | ||||||||||||||||||||||||||||||||||||||||
| obsv observability.Exporters, | ||||||||||||||||||||||||||||||||||||||||
| ) *RequestDeps { | ||||||||||||||||||||||||||||||||||||||||
| return &RequestDeps{ | ||||||||||||||||||||||||||||||||||||||||
| apiHosts: apiHosts, | ||||||||||||||||||||||||||||||||||||||||
|
|
@@ -267,6 +303,7 @@ func NewRequestDeps( | |||||||||||||||||||||||||||||||||||||||
| T: t, | ||||||||||||||||||||||||||||||||||||||||
| ContentWindowSize: contentWindowSize, | ||||||||||||||||||||||||||||||||||||||||
| featureChecker: featureChecker, | ||||||||||||||||||||||||||||||||||||||||
| obsv: obsv, | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
|
|
@@ -374,6 +411,16 @@ func (d *RequestDeps) GetFlags(ctx context.Context) FeatureFlags { | |||||||||||||||||||||||||||||||||||||||
| // GetContentWindowSize implements ToolDependencies. | ||||||||||||||||||||||||||||||||||||||||
| func (d *RequestDeps) GetContentWindowSize() int { return d.ContentWindowSize } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| // Logger implements ToolDependencies. | ||||||||||||||||||||||||||||||||||||||||
| func (d *RequestDeps) Logger(_ context.Context) *slog.Logger { | ||||||||||||||||||||||||||||||||||||||||
| return d.obsv.Logger() | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| // Metrics implements ToolDependencies. | ||||||||||||||||||||||||||||||||||||||||
| func (d *RequestDeps) Metrics(ctx context.Context) metrics.Metrics { | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+415
to
+420
|
||||||||||||||||||||||||||||||||||||||||
| 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 | |
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| package metrics | ||
|
|
||
| import "time" | ||
|
|
||
| // Metrics is a backend-agnostic interface for emitting metrics. | ||
| // Implementations can route to DataDog, log to slog, or discard (noop). | ||
| type Metrics interface { | ||
| Increment(key string, tags map[string]string) | ||
| Counter(key string, tags map[string]string, value int64) | ||
| Distribution(key string, tags map[string]string, value float64) | ||
| DistributionMs(key string, tags map[string]string, value time.Duration) | ||
| WithTags(tags map[string]string) Metrics | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| package metrics | ||
|
|
||
| import "time" | ||
|
|
||
| // NoopMetrics is a no-op implementation of the Metrics interface. | ||
| type NoopMetrics struct{} | ||
|
|
||
| var _ Metrics = (*NoopMetrics)(nil) | ||
|
|
||
| // NewNoopMetrics returns a new NoopMetrics. | ||
| func NewNoopMetrics() *NoopMetrics { | ||
| return &NoopMetrics{} | ||
| } | ||
|
|
||
| func (n *NoopMetrics) Increment(_ string, _ map[string]string) {} | ||
| func (n *NoopMetrics) Counter(_ string, _ map[string]string, _ int64) {} | ||
| func (n *NoopMetrics) Distribution(_ string, _ map[string]string, _ float64) {} | ||
| func (n *NoopMetrics) DistributionMs(_ string, _ map[string]string, _ time.Duration) {} | ||
| func (n *NoopMetrics) WithTags(_ map[string]string) Metrics { return n } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| package metrics | ||
|
|
||
| import ( | ||
| "testing" | ||
| "time" | ||
|
|
||
| "github.com/stretchr/testify/assert" | ||
| ) | ||
|
|
||
| func TestNoopMetrics_ImplementsInterface(_ *testing.T) { | ||
| var _ Metrics = (*NoopMetrics)(nil) | ||
| } | ||
|
|
||
| func TestNoopMetrics_NoPanics(t *testing.T) { | ||
| m := NewNoopMetrics() | ||
|
|
||
| assert.NotPanics(t, func() { | ||
| m.Increment("key", map[string]string{"a": "b"}) | ||
| m.Counter("key", map[string]string{"a": "b"}, 1) | ||
| m.Distribution("key", map[string]string{"a": "b"}, 1.5) | ||
| m.DistributionMs("key", map[string]string{"a": "b"}, time.Second) | ||
| }) | ||
| } | ||
|
|
||
| func TestNoopMetrics_NilTags(t *testing.T) { | ||
| m := NewNoopMetrics() | ||
|
|
||
| assert.NotPanics(t, func() { | ||
| m.Increment("key", nil) | ||
| m.Counter("key", nil, 1) | ||
| m.Distribution("key", nil, 1.5) | ||
| m.DistributionMs("key", nil, time.Second) | ||
| }) | ||
| } | ||
|
|
||
| func TestNoopMetrics_WithTags(t *testing.T) { | ||
| m := NewNoopMetrics() | ||
| tagged := m.WithTags(map[string]string{"env": "prod"}) | ||
|
|
||
| assert.NotNil(t, tagged) | ||
| assert.Equal(t, m, tagged) | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,42 @@ | ||||||||||||||||||||||||||||||
| package observability | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| import ( | ||||||||||||||||||||||||||||||
| "context" | ||||||||||||||||||||||||||||||
| "errors" | ||||||||||||||||||||||||||||||
| "log/slog" | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| "github.com/github/github-mcp-server/pkg/observability/metrics" | ||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // Exporters bundles observability primitives (logger + metrics) for dependency injection. | ||||||||||||||||||||||||||||||
| // The logger is Go's stdlib *slog.Logger — integrators provide their own slog.Handler. | ||||||||||||||||||||||||||||||
| type Exporters interface { | ||||||||||||||||||||||||||||||
| Logger() *slog.Logger | ||||||||||||||||||||||||||||||
| Metrics(context.Context) metrics.Metrics | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| type exporters struct { | ||||||||||||||||||||||||||||||
| logger *slog.Logger | ||||||||||||||||||||||||||||||
| metrics metrics.Metrics | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // NewExporters creates an Exporters bundle. Pass a configured *slog.Logger | ||||||||||||||||||||||||||||||
| // (with whatever slog.Handler you need) and a Metrics implementation. | ||||||||||||||||||||||||||||||
| // The logger must not be nil; use slog.New(slog.DiscardHandler) if logging is unwanted. | ||||||||||||||||||||||||||||||
| 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") | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
| } | |
| } | |
| if metrics == nil { | |
| return nil, errors.New("metrics must not be nil; provide a no-op metrics implementation if metrics are disabled") | |
| } |
Copilot
AI
Mar 26, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BaseDeps.Loggerreturns nil whenObsvis nil. SinceNewExportersenforces a non-nil logger andToolDependencies.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 whenObsvis nil (or ensuringBaseDepsalways has a non-nilObsv).This issue also appears on line 198 of the same file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot open a new pull request to apply changes based on this feedback