diff --git a/AGENTS.md b/AGENTS.md index e38eb3f0b..967e7ea1e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -282,21 +282,28 @@ make agent-finished ```go import "github.com/github/gh-aw-mcpg/internal/logger" -// Create a logger with namespace following pkg:filename convention -var log = logger.New("pkg:filename") +// Create a logger with namespace auto-derived from the calling file (PREFERRED) +var logComponent = logger.ForFile() // Log debug messages // - Writes to stderr with colors and time diffs (when DEBUG matches namespace) // - Also writes to file logger as text-only (always, when logger is enabled) -log.Printf("Processing %d items", count) -log.Print("Simple debug message") +logComponent.Printf("Processing %d items", count) +logComponent.Print("Simple debug message") // Check if logging is enabled before expensive operations -if log.Enabled() { - log.Printf("Expensive debug info: %+v", expensiveOperation()) +if logComponent.Enabled() { + logComponent.Printf("Expensive debug info: %+v", expensiveOperation()) } ``` +`logger.ForFile()` automatically derives the namespace as `"package:filename"` from the calling +file path (e.g., a call in `internal/server/unified.go` yields `"server:unified"`). This +eliminates manually maintained namespace strings and prevents namespace drift. + +Use `logger.New("pkg:component")` only when a custom namespace is intentionally different from +the file name (e.g., a file that maintains backward-compatible debug namespace for users). + **For operational/file logging, use the file logger directly:** ```go @@ -311,7 +318,7 @@ logger.LogError("category", "Operation failed: %v", err) logger.LogDebug("category", "Debug details: %+v", details) ``` -**Note:** Debug loggers created with `logger.New()` now write to both stderr (with colors/time diffs) and the file logger (text-only). This provides real-time colored output during development while ensuring all debug logs are captured to file for production troubleshooting. +**Note:** Debug loggers created with `logger.ForFile()` and `logger.New()` write to both stderr (with colors/time diffs) and the file logger (text-only). This provides real-time colored output during development while ensuring all debug logs are captured to file for production troubleshooting. **Logging Categories:** - `startup` - Gateway initialization and configuration @@ -326,26 +333,23 @@ logger.LogDebug("category", "Debug details: %+v", details) - Be consistent with existing loggers in the codebase **Logger Variable Naming Convention:** -- **Use descriptive names** that match the component: `var log = logger.New("pkg:component")` -- Examples: `var logLauncher = logger.New("launcher:launcher")`, `var logConfig = logger.New("config:config")` +- **Use descriptive names** that match the component: `var log = logger.ForFile()` +- Examples: `var logLauncher = logger.ForFile()`, `var logHandlers = logger.ForFile()` - **Avoid generic `log` name** when it might conflict with standard library or when the file already imports `log` package - Capitalize the component part after 'log' (e.g., `logAuth` with capital 'A', `logLauncher` with capital 'L') - This convention makes it clear which logger is being used and reduces naming collisions -- For components with very short files or temporary code, generic `log` is acceptable but descriptive is preferred -**Examples of good logger naming:** +**Examples of good logger declarations:** ```go -// Descriptive - clearly indicates the component (RECOMMENDED) -var logLauncher = logger.New("launcher:launcher") -var logPool = logger.New("launcher:pool") -var logConfig = logger.New("config:config") -var logValidation = logger.New("config:validation") -var logUnified = logger.New("server:unified") -var logRouted = logger.New("server:routed") - -// Generic - acceptable for simple cases but less clear -var log = logger.New("auth:header") -var log = logger.New("sys:sys") +// Using ForFile() - namespace is auto-derived from file path (RECOMMENDED) +var logLauncher = logger.ForFile() // in launcher/launcher.go → "launcher:launcher" +var logValidation = logger.ForFile() // in config/validation.go → "config:validation" +var logUnified = logger.ForFile() // in server/unified.go → "server:unified" +var logRouted = logger.ForFile() // in server/routed.go → "server:routed" + +// Using New() - only for intentionally custom namespaces +var logPool = logger.New("launcher:pool") // in connection_pool.go, custom shorter name +var logConfig = logger.New("config:config") // in config_core.go, intentional short name ``` diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5b6cbfe6d..6ddcb8a03 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -430,9 +430,9 @@ Use the logger package for debug logging: ```go import "github.com/github/gh-aw-mcpg/internal/logger" -// Create a logger with namespace following pkg:filename convention +// Create a logger with namespace auto-derived from the calling file (PREFERRED) // Use descriptive variable names (e.g., logLauncher, logConfig) for clarity -var logComponent = logger.New("pkg:filename") +var logComponent = logger.ForFile() // Log debug messages (only shown when DEBUG environment variable matches) logComponent.Printf("Processing %d items", count) @@ -443,9 +443,15 @@ if logComponent.Enabled() { } ``` +`logger.ForFile()` automatically derives the namespace as `"package:filename"` from the calling +file path, eliminating manually maintained namespace strings and preventing drift. + +Use `logger.New("pkg:component")` only when a custom namespace is intentionally different from +the file name (e.g., preserving a short backward-compatible debug namespace). + **Logger Variable Naming Convention:** -- **Prefer descriptive names**: `var log = logger.New("pkg:component")` -- Examples: `var logLauncher = logger.New("launcher:launcher")` +- **Prefer descriptive names**: `var log = logger.ForFile()` +- Examples: `var logLauncher = logger.ForFile()`, `var logHandlers = logger.ForFile()` - Avoid generic `log` when it might conflict with standard library - Capitalize the component part after 'log' (e.g., `logAuth` with capital 'A', `logLauncher` with capital 'L') diff --git a/internal/auth/header.go b/internal/auth/header.go index bcfbe860c..590664412 100644 --- a/internal/auth/header.go +++ b/internal/auth/header.go @@ -44,7 +44,7 @@ import ( "github.com/github/gh-aw-mcpg/internal/util" ) -var logAuth = logger.New("auth:header") +var logAuth = logger.ForFile() var logAPIKey = logger.New("auth:apikey") var ( diff --git a/internal/cmd/proxy.go b/internal/cmd/proxy.go index 62fa3bc5e..dbcdd45da 100644 --- a/internal/cmd/proxy.go +++ b/internal/cmd/proxy.go @@ -22,7 +22,7 @@ import ( "github.com/spf13/cobra" ) -var logProxyCmd = logger.New("cmd:proxy") +var logProxyCmd = logger.ForFile() // Proxy subcommand flag variables var ( diff --git a/internal/cmd/root.go b/internal/cmd/root.go index 81736fc4e..d87ba7df0 100644 --- a/internal/cmd/root.go +++ b/internal/cmd/root.go @@ -33,7 +33,7 @@ const ( // Package-level variables that don't belong to a specific feature var ( - debugLog = logger.New("cmd:root") + debugLog = logger.ForFile() // cliVersion stores the version string for Cobra's CLI version display. // This is kept separate from version.Get() because rootCmd.Version must be // set at initialization time (before SetVersion is called). We sync both diff --git a/internal/config/config_stdin.go b/internal/config/config_stdin.go index ebf6ab226..b19c14ec8 100644 --- a/internal/config/config_stdin.go +++ b/internal/config/config_stdin.go @@ -14,7 +14,7 @@ import ( "github.com/santhosh-tekuri/jsonschema/v6" ) -var logStdin = logger.New("config:config_stdin") +var logStdin = logger.ForFile() // StdinConfig represents the JSON configuration format read from stdin. type StdinConfig struct { diff --git a/internal/config/config_tracing.go b/internal/config/config_tracing.go index d70840782..a80e01230 100644 --- a/internal/config/config_tracing.go +++ b/internal/config/config_tracing.go @@ -4,7 +4,7 @@ package config import "github.com/github/gh-aw-mcpg/internal/logger" -var logTracingCfg = logger.New("config:config_tracing") +var logTracingCfg = logger.ForFile() // DefaultTracingSampleRate is the default sample rate for tracing (100% sampling). const DefaultTracingSampleRate = 1.0 diff --git a/internal/config/guard_policy.go b/internal/config/guard_policy.go index faf7c9b40..0838a8b90 100644 --- a/internal/config/guard_policy.go +++ b/internal/config/guard_policy.go @@ -9,7 +9,7 @@ import ( "github.com/github/gh-aw-mcpg/internal/logger" ) -var logGuardPolicy = logger.New("config:guard_policy") +var logGuardPolicy = logger.ForFile() const ( IntegrityNone = "none" diff --git a/internal/config/validation_env.go b/internal/config/validation_env.go index 4fcfa3b0c..9f768e8f1 100644 --- a/internal/config/validation_env.go +++ b/internal/config/validation_env.go @@ -9,7 +9,7 @@ import ( "github.com/github/gh-aw-mcpg/internal/sys" ) -var logEnv = logger.New("config:validation_env") +var logEnv = logger.ForFile() // RequiredEnvVars lists the environment variables that must be set for the gateway to operate var RequiredEnvVars = []string{ diff --git a/internal/config/validation_schema.go b/internal/config/validation_schema.go index 988865f7c..e66eb44ff 100644 --- a/internal/config/validation_schema.go +++ b/internal/config/validation_schema.go @@ -61,7 +61,7 @@ var schemaErrPrinter = message.NewPrinter(language.English) var ( // logSchema is the debug logger for schema validation - logSchema = logger.New("config:validation_schema") + logSchema = logger.ForFile() // Schema caching to avoid recompiling the JSON schema on every validation. // This improves performance by compiling the schema once and reusing it. diff --git a/internal/config/validation_shared.go b/internal/config/validation_shared.go index 84582b8da..d76cc1b59 100644 --- a/internal/config/validation_shared.go +++ b/internal/config/validation_shared.go @@ -6,7 +6,7 @@ import ( "github.com/github/gh-aw-mcpg/internal/logger" ) -var logValidation = logger.New("config:validation_shared") +var logValidation = logger.ForFile() // customSchemaCache stores compiled custom schemas by schema URL to avoid // repeated fetch + compile work across validations. diff --git a/internal/difc/agent.go b/internal/difc/agent.go index 7db93c72d..f926519ef 100644 --- a/internal/difc/agent.go +++ b/internal/difc/agent.go @@ -7,7 +7,7 @@ import ( "github.com/github/gh-aw-mcpg/internal/syncutil" ) -var logAgent = logger.New("difc:agent") +var logAgent = logger.ForFile() // AgentLabels associates each agent with their DIFC labels // Tracks what secrecy and integrity tags an agent has accumulated diff --git a/internal/difc/capabilities.go b/internal/difc/capabilities.go index 46fab6de4..101b854d1 100644 --- a/internal/difc/capabilities.go +++ b/internal/difc/capabilities.go @@ -2,7 +2,7 @@ package difc import "github.com/github/gh-aw-mcpg/internal/logger" -var logCapabilities = logger.New("difc:capabilities") +var logCapabilities = logger.ForFile() // Capabilities represents the global set of tags available in the system // This is used to validate and discover available DIFC tags diff --git a/internal/difc/evaluator.go b/internal/difc/evaluator.go index 6b55bb953..a39b96419 100644 --- a/internal/difc/evaluator.go +++ b/internal/difc/evaluator.go @@ -8,7 +8,7 @@ import ( "github.com/github/gh-aw-mcpg/internal/logger" ) -var logEvaluator = logger.New("difc:evaluator") +var logEvaluator = logger.ForFile() // DIFC mode string constants - use these for consistent mode references const ( diff --git a/internal/difc/labels.go b/internal/difc/labels.go index 5392b7be4..3ca8229b8 100644 --- a/internal/difc/labels.go +++ b/internal/difc/labels.go @@ -6,7 +6,7 @@ import ( "github.com/github/gh-aw-mcpg/internal/logger" ) -var logLabels = logger.New("difc:labels") +var logLabels = logger.ForFile() // Tag represents a single DIFC tag (e.g., "repo:owner/name", "agent:demo-agent") type Tag string diff --git a/internal/difc/path_labels.go b/internal/difc/path_labels.go index 44d76e3ca..117bec1f6 100644 --- a/internal/difc/path_labels.go +++ b/internal/difc/path_labels.go @@ -10,7 +10,7 @@ import ( "github.com/github/gh-aw-mcpg/internal/mcpresult" ) -var logPathLabels = logger.New("difc:path_labels") +var logPathLabels = logger.ForFile() // PathLabels represents a collection of labeled paths in a JSON response. // Guards return this structure to indicate which elements in the response diff --git a/internal/difc/pipeline_decisions.go b/internal/difc/pipeline_decisions.go index 1d6450446..d260bcd68 100644 --- a/internal/difc/pipeline_decisions.go +++ b/internal/difc/pipeline_decisions.go @@ -2,7 +2,7 @@ package difc import "github.com/github/gh-aw-mcpg/internal/logger" -var logPipeline = logger.New("difc:pipeline_decisions") +var logPipeline = logger.ForFile() // CoarseCheckOutcome is the typed result of a Phase 2 coarse-grained access check. type CoarseCheckOutcome int diff --git a/internal/difc/reflect.go b/internal/difc/reflect.go index 10759685d..4f40f91e3 100644 --- a/internal/difc/reflect.go +++ b/internal/difc/reflect.go @@ -7,7 +7,7 @@ import ( "github.com/github/gh-aw-mcpg/internal/logger" ) -var logReflect = logger.New("difc:reflect") +var logReflect = logger.ForFile() // ReflectedAgentLabels is the JSON shape for an agent's current DIFC labels. type ReflectedAgentLabels struct { diff --git a/internal/difc/resource.go b/internal/difc/resource.go index 062baa1fd..9529782aa 100644 --- a/internal/difc/resource.go +++ b/internal/difc/resource.go @@ -2,7 +2,7 @@ package difc import "github.com/github/gh-aw-mcpg/internal/logger" -var logResource = logger.New("difc:resource") +var logResource = logger.ForFile() // Resource represents an external system with label requirements (deprecated - use LabeledResource) type Resource struct { diff --git a/internal/difc/sink_server_ids.go b/internal/difc/sink_server_ids.go index e9cfbccd7..f31df8e3e 100644 --- a/internal/difc/sink_server_ids.go +++ b/internal/difc/sink_server_ids.go @@ -9,7 +9,7 @@ import ( "github.com/github/gh-aw-mcpg/internal/util" ) -var logSink = logger.New("difc:sink_server_ids") +var logSink = logger.ForFile() var ( sinkServerIDsMu sync.RWMutex diff --git a/internal/envutil/envfile.go b/internal/envutil/envfile.go index c04ba6fbc..286ee7238 100644 --- a/internal/envutil/envfile.go +++ b/internal/envutil/envfile.go @@ -10,7 +10,7 @@ import ( "github.com/github/gh-aw-mcpg/internal/sanitize" ) -var logEnvFile = logger.New("envutil:envfile") +var logEnvFile = logger.ForFile() // LoadEnvFile reads a .env file and sets environment variables. // Lines beginning with '#' and blank lines are ignored. diff --git a/internal/envutil/envutil.go b/internal/envutil/envutil.go index c4896d9b4..cc4cc3022 100644 --- a/internal/envutil/envutil.go +++ b/internal/envutil/envutil.go @@ -10,7 +10,7 @@ import ( "github.com/github/gh-aw-mcpg/internal/sanitize" ) -var logEnvUtil = logger.New("envutil:envutil") +var logEnvUtil = logger.ForFile() // HasEnvVar reports whether the named environment variable is present in the // process environment, regardless of whether its value is empty. diff --git a/internal/envutil/github.go b/internal/envutil/github.go index 19c3e2ca4..e67748878 100644 --- a/internal/envutil/github.go +++ b/internal/envutil/github.go @@ -10,7 +10,7 @@ import ( "github.com/github/gh-aw-mcpg/internal/sanitize" ) -var logGitHub = logger.New("envutil:github") +var logGitHub = logger.ForFile() // DefaultGitHubAPIBaseURL is the default GitHub API base URL. const DefaultGitHubAPIBaseURL = "https://api.github.com" diff --git a/internal/githubhttp/client.go b/internal/githubhttp/client.go index 74052ebb0..ce24905f4 100644 --- a/internal/githubhttp/client.go +++ b/internal/githubhttp/client.go @@ -11,7 +11,7 @@ import ( "github.com/github/gh-aw-mcpg/internal/logger" ) -var logHTTP = logger.New("githubhttp:client") +var logHTTP = logger.ForFile() // GitHubUserAgent is the User-Agent header value sent on all GitHub API requests. const GitHubUserAgent = "awmg/1.0" diff --git a/internal/githubhttp/collaborator.go b/internal/githubhttp/collaborator.go index 7d60acdd4..d01bcadc6 100644 --- a/internal/githubhttp/collaborator.go +++ b/internal/githubhttp/collaborator.go @@ -12,7 +12,7 @@ import ( "github.com/github/gh-aw-mcpg/internal/util" ) -var logCollab = logger.New("githubhttp:collaborator") +var logCollab = logger.ForFile() // ParseCollaboratorPermissionArgs extracts and validates the owner, repo, and // username fields from an args map for a get_collaborator_permission call. diff --git a/internal/githubhttp/visibility.go b/internal/githubhttp/visibility.go index 3b1ba15aa..62ed3b74d 100644 --- a/internal/githubhttp/visibility.go +++ b/internal/githubhttp/visibility.go @@ -10,7 +10,7 @@ import ( "github.com/github/gh-aw-mcpg/internal/logger" ) -var logVisibility = logger.New("githubhttp:visibility") +var logVisibility = logger.ForFile() // RepoVisibility represents the visibility of a GitHub repository. type RepoVisibility string diff --git a/internal/guard/context.go b/internal/guard/context.go index b46535cf2..450b47ce7 100644 --- a/internal/guard/context.go +++ b/internal/guard/context.go @@ -26,7 +26,7 @@ import ( "github.com/github/gh-aw-mcpg/internal/logger" ) -var logContext = logger.New("guard:context") +var logContext = logger.ForFile() // ContextKey is used for storing values in context type ContextKey string diff --git a/internal/guard/label_agent.go b/internal/guard/label_agent.go index 964b496d2..491932e91 100644 --- a/internal/guard/label_agent.go +++ b/internal/guard/label_agent.go @@ -8,7 +8,7 @@ import ( "github.com/github/gh-aw-mcpg/internal/logger" ) -var logLabelAgent = logger.New("guard:label_agent") +var logLabelAgent = logger.ForFile() // RunLabelAgentForAgent is a convenience wrapper around RunLabelAgent that resolves // agent labels from the registry instead of requiring the caller to do so. It calls diff --git a/internal/guard/noop.go b/internal/guard/noop.go index 1cd5de882..d834570fd 100644 --- a/internal/guard/noop.go +++ b/internal/guard/noop.go @@ -7,7 +7,7 @@ import ( "github.com/github/gh-aw-mcpg/internal/logger" ) -var logNoop = logger.New("guard:noop") +var logNoop = logger.ForFile() // NoopGuard is the default guard that performs no DIFC labeling // It allows all operations by returning empty labels (no restrictions) diff --git a/internal/guard/pipeline.go b/internal/guard/pipeline.go index 6a480b6d6..d249fa74e 100644 --- a/internal/guard/pipeline.go +++ b/internal/guard/pipeline.go @@ -9,7 +9,7 @@ import ( "github.com/github/gh-aw-mcpg/internal/logger" ) -var logPipeline = logger.New("guard:pipeline") +var logPipeline = logger.ForFile() // PipelineInput holds the shared inputs used across DIFC pipeline phases 0–2, 4, and 6. // Both the HTTP proxy and the MCP unified server populate this struct and pass it to diff --git a/internal/guard/registry.go b/internal/guard/registry.go index 6a4b3f710..563d09369 100644 --- a/internal/guard/registry.go +++ b/internal/guard/registry.go @@ -8,7 +8,7 @@ import ( "github.com/github/gh-aw-mcpg/internal/logger" ) -var logRegistry = logger.New("guard:registry") +var logRegistry = logger.ForFile() // Registry manages guard instances for different MCP servers type Registry struct { diff --git a/internal/httputil/httputil.go b/internal/httputil/httputil.go index 6c3f33453..03acbbf55 100644 --- a/internal/httputil/httputil.go +++ b/internal/httputil/httputil.go @@ -10,7 +10,7 @@ import ( "github.com/github/gh-aw-mcpg/internal/logger" ) -var logHTTP = logger.New("httputil:httputil") +var logHTTP = logger.ForFile() // WriteJSONResponse sets the Content-Type header, writes the status code, and encodes // body as JSON. It centralises the three-line pattern used across HTTP handlers. diff --git a/internal/httputil/tls.go b/internal/httputil/tls.go index fc908e1b9..15967a97f 100644 --- a/internal/httputil/tls.go +++ b/internal/httputil/tls.go @@ -22,7 +22,7 @@ import ( "github.com/github/gh-aw-mcpg/internal/logger" ) -var logTLS = logger.New("httputil:tls") +var logTLS = logger.ForFile() // MinTLSVersion is the minimum TLS version enforced across all gateway listeners // and clients. Centralizing this constant ensures a single point of change if diff --git a/internal/launcher/launcher.go b/internal/launcher/launcher.go index 48ee64280..afe75100b 100644 --- a/internal/launcher/launcher.go +++ b/internal/launcher/launcher.go @@ -18,7 +18,7 @@ import ( "github.com/github/gh-aw-mcpg/internal/sys" ) -var logLauncher = logger.New("launcher:launcher") +var logLauncher = logger.ForFile() // ErrServerNotFound is returned by getServerConfig when the requested server ID // is not present in the gateway configuration. diff --git a/internal/logger/logger.go b/internal/logger/logger.go index c5f461dea..0c55aaf4f 100644 --- a/internal/logger/logger.go +++ b/internal/logger/logger.go @@ -4,6 +4,8 @@ import ( "fmt" "hash/fnv" "os" + "path/filepath" + "runtime" "strings" "sync" "time" @@ -80,6 +82,22 @@ func New(namespace string) *Logger { } } +// ForFile creates a new Logger with a namespace automatically derived from the calling +// file's path. The namespace follows the "package:filename" convention where package is +// the last directory component of the file path and filename is the Go source file name +// without the .go extension. This eliminates manually maintained namespace strings. +// +// Example: a call from internal/server/unified.go yields namespace "server:unified". +func ForFile() *Logger { + _, file, _, ok := runtime.Caller(1) + if !ok { + return New("error:caller-not-found") + } + pkg := filepath.Base(filepath.Dir(file)) + filename := strings.TrimSuffix(filepath.Base(file), ".go") + return New(pkg + ":" + filename) +} + // selectColor selects a color for the namespace based on its hash. func selectColor(namespace string) string { if !debugColors || !isTTY { diff --git a/internal/logger/logger_namespace_test.go b/internal/logger/logger_namespace_test.go index ec114e0cd..120d80d06 100644 --- a/internal/logger/logger_namespace_test.go +++ b/internal/logger/logger_namespace_test.go @@ -25,19 +25,35 @@ func TestLoggerNamespacesMatchFileConventions(t *testing.T) { internalRoot := filepath.Join(repoRoot, "internal") exceptionNamespaces := map[string][]string{ - "internal/auth/header.go": {"auth:header", "auth:apikey"}, - "internal/config/config_core.go": {"config:config"}, - "internal/config/config_feature.go": {"config:feature"}, - "internal/envutil/expand_env_args.go": {"envutil:expand"}, - "internal/guard/wasm_lifecycle.go": {"guard:wasm"}, - "internal/guard/write_sink.go": {"guard:write-sink"}, - "internal/launcher/connection_pool.go": {"launcher:pool"}, - "internal/launcher/health_monitor.go": {"launcher:health"}, - "internal/server/http_helpers.go": {"server:helpers"}, - "internal/server/http_server.go": {"server:http_server", "server:transport"}, - "internal/server/middleware_auth.go": {"server:auth"}, - "internal/server/sdk_logging.go": {"server:sdk-frontend"}, - "internal/server/session_auto_init.go": {"server:auto-init"}, + // header.go defines two loggers: one for general auth (auto-derived via ForFile as + // "auth:header") and one for API-key auth which uses the custom namespace "auth:apikey" + // so callers can filter API-key debug logs independently with DEBUG=auth:apikey. + "internal/auth/header.go": {"auth:apikey"}, + + // The following files use intentionally shorter or semantically clearer namespaces + // instead of the full file-name-derived form. These are preserved for backward + // compatibility with existing DEBUG filter configurations. + "internal/config/config_core.go": {"config:config"}, + "internal/config/config_feature.go": {"config:feature"}, + "internal/envutil/expand_env_args.go": {"envutil:expand"}, + "internal/guard/wasm_lifecycle.go": {"guard:wasm"}, + "internal/guard/write_sink.go": {"guard:write-sink"}, + "internal/launcher/connection_pool.go": {"launcher:pool"}, + "internal/launcher/health_monitor.go": {"launcher:health"}, + "internal/server/http_helpers.go": {"server:helpers"}, + + // http_server.go defines two loggers: the primary one for the HTTP server itself + // (auto-derived via ForFile as "server:http_server") and a second one with the custom + // namespace "server:transport" for transport-layer events, allowing independent filtering. + "internal/server/http_server.go": {"server:transport"}, + + "internal/server/middleware_auth.go": {"server:auth"}, + "internal/server/sdk_logging.go": {"server:sdk-frontend"}, + "internal/server/session_auto_init.go": {"server:auto-init"}, + + // testutil/mcptest/server.go uses "testutil:mcptest" (package/feature oriented) rather + // than the file-derived "mcptest:server". testutil/mcptest/validator.go similarly uses + // "testutil:validator" to group all testutil helpers under a common DEBUG prefix. "internal/testutil/mcptest/server.go": {"testutil:mcptest"}, "internal/testutil/mcptest/validator.go": {"testutil:validator"}, } diff --git a/internal/logger/logger_test.go b/internal/logger/logger_test.go index 8fc8ec782..f38cf9541 100644 --- a/internal/logger/logger_test.go +++ b/internal/logger/logger_test.go @@ -724,3 +724,20 @@ func TestLogger_Print_WithColors(t *testing.T) { assert.Contains(t, output, "\033[", "Print() output should contain ANSI color codes when colors enabled") assert.Contains(t, output, colorReset, "Print() output should contain color reset code") } + +// TestForFile verifies that ForFile derives the correct namespace from the calling file. +func TestForFile(t *testing.T) { + t.Parallel() + + // ForFile() is called from logger_test.go in the "logger" package. + // Expected namespace: "logger:logger_test" + log := ForFile() + assert.Equal(t, "logger:logger_test", log.namespace) +} + +// TestForFile_Enabled verifies that ForFile respects the DEBUG environment variable. +func TestForFile_Enabled(t *testing.T) { + t.Setenv("DEBUG", "logger:*") + log := ForFile() + assert.True(t, log.Enabled(), "ForFile logger should be enabled when DEBUG matches its derived namespace") +} diff --git a/internal/mcp/connection.go b/internal/mcp/connection.go index 1ec5986e0..1278c3615 100644 --- a/internal/mcp/connection.go +++ b/internal/mcp/connection.go @@ -22,7 +22,7 @@ import ( sdk "github.com/modelcontextprotocol/go-sdk/mcp" ) -var logConn = logger.New("mcp:connection") +var logConn = logger.ForFile() // defaultConnectTimeout is the fallback connect timeout used when the configured timeout // is non-positive or otherwise invalid. diff --git a/internal/mcp/connection_logging.go b/internal/mcp/connection_logging.go index 2c44385fc..a77f12b13 100644 --- a/internal/mcp/connection_logging.go +++ b/internal/mcp/connection_logging.go @@ -6,7 +6,7 @@ import ( "github.com/github/gh-aw-mcpg/internal/logger" ) -var logConnLogging = logger.New("mcp:connection_logging") +var logConnLogging = logger.ForFile() // logReconnectStart emits the structured log warning that is common to all reconnect paths. func (c *Connection) logReconnectStart() { diff --git a/internal/mcp/helpers.go b/internal/mcp/helpers.go index 32fff4ddd..6ab57566f 100644 --- a/internal/mcp/helpers.go +++ b/internal/mcp/helpers.go @@ -8,7 +8,7 @@ import ( "github.com/github/gh-aw-mcpg/internal/logger" ) -var logMCPHelpers = logger.New("mcp:helpers") +var logMCPHelpers = logger.ForFile() // marshalToResponse marshals an SDK result into a Response object. // This helper reduces code duplication across all MCP method wrappers. diff --git a/internal/mcp/http_transport.go b/internal/mcp/http_transport.go index dc4f1c545..10f460756 100644 --- a/internal/mcp/http_transport.go +++ b/internal/mcp/http_transport.go @@ -51,7 +51,7 @@ const streamableMaxRetries = -1 // requestIDCounter is used to generate unique request IDs for HTTP requests var requestIDCounter uint64 -var logHTTP = logger.New("mcp:http_transport") +var logHTTP = logger.ForFile() // httpRequestResult contains the result of an HTTP request execution type httpRequestResult struct { diff --git a/internal/mcp/pagination.go b/internal/mcp/pagination.go index 320b09908..bf80a9ed8 100644 --- a/internal/mcp/pagination.go +++ b/internal/mcp/pagination.go @@ -6,7 +6,7 @@ import ( "github.com/github/gh-aw-mcpg/internal/logger" ) -var logPagination = logger.New("mcp:pagination") +var logPagination = logger.ForFile() // paginatedPage holds a single page of results from a paginated SDK list call. type paginatedPage[T any] struct { diff --git a/internal/mcp/schema.go b/internal/mcp/schema.go index 656a96a80..5a9a6ce4c 100644 --- a/internal/mcp/schema.go +++ b/internal/mcp/schema.go @@ -4,7 +4,7 @@ import ( "github.com/github/gh-aw-mcpg/internal/logger" ) -var logSchema = logger.New("mcp:schema") +var logSchema = logger.ForFile() // NormalizeInputSchema ensures tool input schemas are valid for the MCP SDK // The MCP SDK requires that object type schemas have a "properties" field, diff --git a/internal/mcp/tool_result.go b/internal/mcp/tool_result.go index dbfa8c700..0e56875e5 100644 --- a/internal/mcp/tool_result.go +++ b/internal/mcp/tool_result.go @@ -9,7 +9,7 @@ import ( sdk "github.com/modelcontextprotocol/go-sdk/mcp" ) -var logToolResult = logger.New("mcp:tool_result") +var logToolResult = logger.ForFile() func marshalValueToTextContentResult(value interface{}) (*sdk.CallToolResult, error) { dataBytes, err := json.Marshal(value) diff --git a/internal/mcpresult/mcpresult.go b/internal/mcpresult/mcpresult.go index 20972734c..69099c9e2 100644 --- a/internal/mcpresult/mcpresult.go +++ b/internal/mcpresult/mcpresult.go @@ -6,7 +6,7 @@ import ( "github.com/github/gh-aw-mcpg/internal/logger" ) -var logMCPResult = logger.New("mcpresult:mcpresult") +var logMCPResult = logger.ForFile() // NormalizeContentItems normalizes an MCP "content" field into a slice of item // maps. It supports both []interface{} values produced by json.Unmarshal and diff --git a/internal/middleware/jqschema.go b/internal/middleware/jqschema.go index 26d4a54f4..0c4d2460b 100644 --- a/internal/middleware/jqschema.go +++ b/internal/middleware/jqschema.go @@ -24,7 +24,7 @@ import ( sdk "github.com/modelcontextprotocol/go-sdk/mcp" ) -var logMiddleware = logger.New("middleware:jqschema") +var logMiddleware = logger.ForFile() // DefaultJqTimeout is the default timeout for jq query execution (5 seconds) // This prevents malformed queries or large payloads from causing hangs diff --git a/internal/oidc/provider.go b/internal/oidc/provider.go index a9a872324..06b61fb08 100644 --- a/internal/oidc/provider.go +++ b/internal/oidc/provider.go @@ -21,7 +21,7 @@ import ( "github.com/github/gh-aw-mcpg/internal/logger" ) -var logOIDC = logger.New("oidc:provider") +var logOIDC = logger.ForFile() // tokenRefreshMargin is how far before expiry we proactively refresh a cached token. const tokenRefreshMargin = 60 * time.Second diff --git a/internal/proxy/graphql.go b/internal/proxy/graphql.go index b9cb6eba7..19ff978f8 100644 --- a/internal/proxy/graphql.go +++ b/internal/proxy/graphql.go @@ -9,7 +9,7 @@ import ( "github.com/github/gh-aw-mcpg/internal/util" ) -var logGraphQL = logger.New("proxy:graphql") +var logGraphQL = logger.ForFile() // GraphQLRequest represents a parsed GraphQL request body. type GraphQLRequest struct { diff --git a/internal/proxy/graphql_rewrite.go b/internal/proxy/graphql_rewrite.go index 1b0d7d608..853417833 100644 --- a/internal/proxy/graphql_rewrite.go +++ b/internal/proxy/graphql_rewrite.go @@ -8,7 +8,7 @@ import ( "github.com/github/gh-aw-mcpg/internal/logger" ) -var logGraphQLRewrite = logger.New("proxy:graphql_rewrite") +var logGraphQLRewrite = logger.ForFile() // Pre-compiled patterns used in injectFieldsIntoQuery. // Compiling these once at package init avoids repeated regexp compilation on diff --git a/internal/proxy/handler.go b/internal/proxy/handler.go index c28ab0eb7..950154184 100644 --- a/internal/proxy/handler.go +++ b/internal/proxy/handler.go @@ -23,7 +23,7 @@ import ( "github.com/github/gh-aw-mcpg/internal/util" ) -var logHandler = logger.New("proxy:handler") +var logHandler = logger.ForFile() // writeDIFCForbidden writes a 403 JSON response for DIFC policy violations. // Uses the shared WriteErrorResponse helper so that the response shape is consistent diff --git a/internal/proxy/proxy.go b/internal/proxy/proxy.go index 86b0584eb..6f5b4624a 100644 --- a/internal/proxy/proxy.go +++ b/internal/proxy/proxy.go @@ -24,7 +24,7 @@ import ( "github.com/github/gh-aw-mcpg/internal/tracing" ) -var logProxy = logger.New("proxy:proxy") +var logProxy = logger.ForFile() const ( // DefaultGitHubAPIBase is the upstream GitHub API URL. diff --git a/internal/proxy/response_transform.go b/internal/proxy/response_transform.go index ba2d61567..baba678b2 100644 --- a/internal/proxy/response_transform.go +++ b/internal/proxy/response_transform.go @@ -8,7 +8,7 @@ import ( "github.com/github/gh-aw-mcpg/internal/util" ) -var logTransform = logger.New("proxy:response_transform") +var logTransform = logger.ForFile() // rewrapSearchResponse re-wraps filtered items into the original search response // envelope. GitHub search endpoints return {"total_count": N, "items": [...]}; diff --git a/internal/proxy/router.go b/internal/proxy/router.go index e8ebfc642..6ac0fe5b8 100644 --- a/internal/proxy/router.go +++ b/internal/proxy/router.go @@ -10,7 +10,7 @@ import ( "github.com/github/gh-aw-mcpg/internal/util" ) -var logRouter = logger.New("proxy:router") +var logRouter = logger.ForFile() // Argument key constants used when building route args maps. // Centralising these strings avoids typo-prone bare literals scattered across the file. diff --git a/internal/proxy/tls.go b/internal/proxy/tls.go index 97407f562..638308f8a 100644 --- a/internal/proxy/tls.go +++ b/internal/proxy/tls.go @@ -38,7 +38,7 @@ import ( "github.com/github/gh-aw-mcpg/internal/logger" ) -var logTLS = logger.New("proxy:tls") +var logTLS = logger.ForFile() // TLSConfig holds the paths to the generated certificate files. type TLSConfig struct { diff --git a/internal/server/circuit_breaker.go b/internal/server/circuit_breaker.go index d1795b78a..fc3172277 100644 --- a/internal/server/circuit_breaker.go +++ b/internal/server/circuit_breaker.go @@ -43,7 +43,7 @@ const DefaultRateLimitThreshold = 3 // before transitioning to HALF-OPEN to probe one request. const DefaultRateLimitCooldown = 60 * time.Second -var logCircuitBreaker = logger.New("server:circuit_breaker") +var logCircuitBreaker = logger.ForFile() // circuitBreaker implements a per-backend rate-limit circuit breaker. // diff --git a/internal/server/difc_log.go b/internal/server/difc_log.go index 758743d8b..1d0fc4e1a 100644 --- a/internal/server/difc_log.go +++ b/internal/server/difc_log.go @@ -11,7 +11,7 @@ import ( "github.com/github/gh-aw-mcpg/internal/util" ) -var logDifcLog = logger.New("server:difc_log") +var logDifcLog = logger.ForFile() // logFilteredItems logs structured details for every item removed by DIFC filtering. // Each item is written as a [DIFC-FILTERED] JSON entry to both the unified and diff --git a/internal/server/guard_init.go b/internal/server/guard_init.go index ea4637bea..f6dfd7042 100644 --- a/internal/server/guard_init.go +++ b/internal/server/guard_init.go @@ -14,7 +14,7 @@ import ( "github.com/github/gh-aw-mcpg/internal/util" ) -var logGuardInit = logger.New("server:guard_init") +var logGuardInit = logger.ForFile() // legacyPolicySource is returned by resolveGuardPolicy when no explicit policy // is configured and the caller should fall back to legacy session-label semantics. diff --git a/internal/server/handlers.go b/internal/server/handlers.go index f7162c914..91f30229e 100644 --- a/internal/server/handlers.go +++ b/internal/server/handlers.go @@ -11,7 +11,7 @@ import ( "github.com/github/gh-aw-mcpg/internal/logger" ) -var logHandlers = logger.New("server:handlers") +var logHandlers = logger.ForFile() // HandleReflect returns an http.HandlerFunc that handles the /reflect endpoint. func HandleReflect(unifiedServer *UnifiedServer) http.HandlerFunc { diff --git a/internal/server/health.go b/internal/server/health.go index c2039113a..2796f2426 100644 --- a/internal/server/health.go +++ b/internal/server/health.go @@ -8,7 +8,7 @@ import ( "github.com/github/gh-aw-mcpg/internal/version" ) -var logHealth = logger.New("server:health") +var logHealth = logger.ForFile() // HealthResponse represents the JSON structure for the /health endpoint response // as defined in MCP Gateway Specification section 8.1.1 diff --git a/internal/server/hmac.go b/internal/server/hmac.go index f37529cc5..92b6875c2 100644 --- a/internal/server/hmac.go +++ b/internal/server/hmac.go @@ -31,7 +31,7 @@ const ( nonceTTL = 2 * hmacMaxAgeSecs * time.Second ) -var logHMAC = logger.New("server:hmac") +var logHMAC = logger.ForFile() // nonceCache tracks recently-seen nonces to detect replay attacks. // Nonces are held for nonceTTL seconds after first use, then evicted. diff --git a/internal/server/http_server.go b/internal/server/http_server.go index 6835612c8..94829d24a 100644 --- a/internal/server/http_server.go +++ b/internal/server/http_server.go @@ -13,7 +13,7 @@ import ( sdk "github.com/modelcontextprotocol/go-sdk/mcp" ) -var logHTTPServer = logger.New("server:http_server") +var logHTTPServer = logger.ForFile() var logTransport = logger.New("server:transport") // newSDKServer creates a new MCP SDK server with the given implementation name and debug logger. diff --git a/internal/server/response_writer.go b/internal/server/response_writer.go index 6243cc9b9..ce46d753f 100644 --- a/internal/server/response_writer.go +++ b/internal/server/response_writer.go @@ -8,7 +8,7 @@ import ( "github.com/github/gh-aw-mcpg/internal/logger" ) -var logResponseWriter = logger.New("server:response_writer") +var logResponseWriter = logger.ForFile() // responseWriter wraps http.ResponseWriter to capture response body and status code. // It embeds httputil.BaseResponseWriter for shared status-code capture logic, and diff --git a/internal/server/routed.go b/internal/server/routed.go index e4dbae44a..566db0ca3 100644 --- a/internal/server/routed.go +++ b/internal/server/routed.go @@ -11,7 +11,7 @@ import ( sdk "github.com/modelcontextprotocol/go-sdk/mcp" ) -var logRouted = logger.New("server:routed") +var logRouted = logger.ForFile() // rejectIfShutdown is a middleware that rejects requests with HTTP 503 when gateway is shutting down // Per spec 5.1.3: "Immediately reject any new RPC requests to /mcp/{server-name} endpoints with HTTP 503" diff --git a/internal/server/session.go b/internal/server/session.go index cc48bcf9a..66b50f03f 100644 --- a/internal/server/session.go +++ b/internal/server/session.go @@ -17,7 +17,7 @@ import ( "github.com/github/gh-aw-mcpg/internal/util" ) -var logSession = logger.New("server:session") +var logSession = logger.ForFile() // extractSessionIDFromRequest extracts the session ID from X-Agent-ID and // Authorization headers. Returns "" if neither header is present or valid. diff --git a/internal/server/system_tools.go b/internal/server/system_tools.go index 15084d943..dc95a685a 100644 --- a/internal/server/system_tools.go +++ b/internal/server/system_tools.go @@ -10,7 +10,7 @@ import ( sdk "github.com/modelcontextprotocol/go-sdk/mcp" ) -var logSys = logger.New("server:system_tools") +var logSys = logger.ForFile() // SysServer implements the MCPG system tools type SysServer struct { diff --git a/internal/server/tool_registry_helpers.go b/internal/server/tool_registry_helpers.go index a34c169f9..04a06fc78 100644 --- a/internal/server/tool_registry_helpers.go +++ b/internal/server/tool_registry_helpers.go @@ -12,7 +12,7 @@ import ( sdk "github.com/modelcontextprotocol/go-sdk/mcp" ) -var logToolRegistryHelpers = logger.New("server:tool_registry_helpers") +var logToolRegistryHelpers = logger.ForFile() // launchResult stores the result of a backend server launch. type launchResult struct { diff --git a/internal/server/unified.go b/internal/server/unified.go index 9ea19bdd4..d96101dc4 100644 --- a/internal/server/unified.go +++ b/internal/server/unified.go @@ -23,7 +23,7 @@ import ( oteltrace "go.opentelemetry.io/otel/trace" ) -var logUnified = logger.New("server:unified") +var logUnified = logger.ForFile() const rateLimitExceededStatus = "rate limit exceeded" diff --git a/internal/sys/container.go b/internal/sys/container.go index 3259fdc61..673bf651f 100644 --- a/internal/sys/container.go +++ b/internal/sys/container.go @@ -8,7 +8,7 @@ import ( "github.com/github/gh-aw-mcpg/internal/logger" ) -var logSys = logger.New("sys:container") +var logSys = logger.ForFile() // containerIndicators lists the cgroup path substrings that indicate a container environment. var containerIndicators = []string{"docker", "containerd", "kubepods", "lxc"} diff --git a/internal/sys/docker.go b/internal/sys/docker.go index 804583879..8466575e4 100644 --- a/internal/sys/docker.go +++ b/internal/sys/docker.go @@ -10,7 +10,7 @@ import ( "github.com/github/gh-aw-mcpg/internal/logger" ) -var logDocker = logger.New("sys:docker") +var logDocker = logger.ForFile() // containerIDPattern validates that a container ID only contains valid characters (hex digits). // Container IDs are 64 character hex strings, but short form (12 chars) is also valid. diff --git a/internal/testutil/mcptest/driver.go b/internal/testutil/mcptest/driver.go index bb3229414..2b9819572 100644 --- a/internal/testutil/mcptest/driver.go +++ b/internal/testutil/mcptest/driver.go @@ -12,7 +12,7 @@ import ( "github.com/github/gh-aw-mcpg/internal/server" ) -var logDriver = logger.New("mcptest:driver") +var logDriver = logger.ForFile() // TestDriver manages test servers and the gateway for integration testing type TestDriver struct { diff --git a/internal/tracing/provider.go b/internal/tracing/provider.go index df285e2af..4d3bf3528 100644 --- a/internal/tracing/provider.go +++ b/internal/tracing/provider.go @@ -40,7 +40,7 @@ import ( const instrumentationName = "github.com/github/gh-aw-mcpg" -var logTracing = logger.New("tracing:provider") +var logTracing = logger.ForFile() // Provider wraps an OpenTelemetry TracerProvider and provides a Shutdown method. type Provider struct { diff --git a/internal/urlutil/domains.go b/internal/urlutil/domains.go index b8cd45430..e1ed62356 100644 --- a/internal/urlutil/domains.go +++ b/internal/urlutil/domains.go @@ -9,7 +9,7 @@ import ( "github.com/github/gh-aw-mcpg/internal/util" ) -var logDomains = logger.New("urlutil:domains") +var logDomains = logger.ForFile() // urlPattern requires a non-empty hostname candidate and then captures the rest // of the URL until common delimiter characters. The (?i) flag makes the scheme diff --git a/internal/version/version.go b/internal/version/version.go index 30efb8a30..d60b7d0ca 100644 --- a/internal/version/version.go +++ b/internal/version/version.go @@ -8,7 +8,7 @@ import ( "github.com/github/gh-aw-mcpg/internal/logger" ) -var logVersion = logger.New("version:version") +var logVersion = logger.ForFile() // readBuildInfo is a package-level variable wrapping debug.ReadBuildInfo to // allow test injection without changing the public API.