debug: structured tool/HTTP logging, request IDs, config dump
check-and-test / check-and-test (pull_request) Has been cancelled
check-and-test / check-and-test (pull_request) Has been cancelled
Closes #13. Today GITEA_DEBUG=true only flips the zap level to debug, dumps a contextless `Text Result:` blob per tool call, and enables the gitea SDK's own debug stream (which doesn't merge with our log file). That is not enough to diagnose a misbehaving tool call in production. This change introduces: - A `pkg/middleware.ToolLogging` ToolHandlerMiddleware that logs each tool invocation on entry and exit with structured fields: tool name, redacted args, request_id, token source, duration_ms, and status. The request_id is stashed in the context so downstream layers can correlate. - A `loggingRoundTripper` in `pkg/gitea` that wraps the shared HTTP transport and emits one structured line per upstream Gitea API call (method, token-stripped URL, status, duration_ms, bytes). This replaces — and is far more useful than — `gitea.SetDebugMode()`. - An effective-config dump at startup (`logEffectiveConfig`) when debug+config scope is on, so "did my env var get picked up?" becomes answerable from the log file. - Token source tracking. CLI flag / GITEA_ACCESS_TOKEN / GITEA_ACCESS_TOKEN_FILE / per-request Authorization header are now distinguished in logs as flag/env/env-file/header. - A baseline redactor in `pkg/debug` that masks args / query params whose keys look like secrets (token, password, secret, api_key, authorization, …). This is the placeholder for issue #5's shared redactor; once that lands, callers here should defer to it. - `GITEA_DEBUG_SCOPES` env var to scope debug output to any subset of `tools,http,config`. Default (unset) is "everything". Test coverage was added for the redactor, scope flag, request-id helpers, preview truncation (multibyte-safe), and the middleware's context wiring + error propagation. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+49
-1
@@ -27,9 +27,11 @@ import (
|
||||
mcpContext "gitea.com/gitea/gitea-mcp/pkg/context"
|
||||
"gitea.com/gitea/gitea-mcp/pkg/flag"
|
||||
"gitea.com/gitea/gitea-mcp/pkg/log"
|
||||
"gitea.com/gitea/gitea-mcp/pkg/middleware"
|
||||
"gitea.com/gitea/gitea-mcp/pkg/tool"
|
||||
|
||||
"github.com/mark3labs/mcp-go/server"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -80,12 +82,18 @@ func getContextWithToken(ctx context.Context, r *http.Request) context.Context {
|
||||
return ctx
|
||||
}
|
||||
|
||||
return context.WithValue(ctx, mcpContext.TokenContextKey, token)
|
||||
ctx = context.WithValue(ctx, mcpContext.TokenContextKey, token)
|
||||
// Mark per-request header tokens so debug logs can show whether a tool
|
||||
// call was authenticated via the header (per-request) or fell through
|
||||
// to the process-wide flag/env token.
|
||||
ctx = context.WithValue(ctx, mcpContext.TokenSourceContextKey, "header")
|
||||
return ctx
|
||||
}
|
||||
|
||||
func Run() error {
|
||||
mcpServer = newMCPServer(flag.Version)
|
||||
RegisterTool(mcpServer)
|
||||
logEffectiveConfig()
|
||||
switch flag.Mode {
|
||||
case "stdio":
|
||||
if err := server.ServeStdio(
|
||||
@@ -135,5 +143,45 @@ func newMCPServer(version string) *server.MCPServer {
|
||||
server.WithToolCapabilities(true),
|
||||
server.WithLogging(),
|
||||
server.WithRecovery(),
|
||||
// ToolLogging emits structured entry/exit logs and attaches a
|
||||
// request_id + tool name to the context so downstream layers
|
||||
// (the gitea RoundTripper, pkg/to result logging) can correlate.
|
||||
server.WithToolHandlerMiddleware(middleware.ToolLogging),
|
||||
)
|
||||
}
|
||||
|
||||
// logEffectiveConfig records the runtime configuration once at startup
|
||||
// when debug mode is on. This makes "did my env var actually take effect?"
|
||||
// answerable from the log file without having to instrument the process.
|
||||
//
|
||||
// Token *values* are never logged — only the source (flag / env / env-file)
|
||||
// so the operator can tell which auth path was taken.
|
||||
func logEffectiveConfig() {
|
||||
if !flag.DebugScopeEnabled("config") {
|
||||
return
|
||||
}
|
||||
tokenSource := flag.TokenSource
|
||||
if tokenSource == "" {
|
||||
tokenSource = "none"
|
||||
}
|
||||
scopes := "all"
|
||||
if len(flag.DebugScopes) > 0 {
|
||||
keys := make([]string, 0, len(flag.DebugScopes))
|
||||
for k := range flag.DebugScopes {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
scopes = strings.Join(keys, ",")
|
||||
}
|
||||
log.Info("gitea-mcp effective config",
|
||||
zap.String("version", flag.Version),
|
||||
zap.String("host", flag.Host),
|
||||
zap.String("mode", flag.Mode),
|
||||
zap.Int("port", flag.Port),
|
||||
zap.Bool("read_only", flag.ReadOnly),
|
||||
zap.Bool("insecure", flag.Insecure),
|
||||
zap.Bool("debug", flag.Debug),
|
||||
zap.String("debug_scopes", scopes),
|
||||
zap.String("token_source", tokenSource),
|
||||
zap.Int("allowed_tools", len(flag.AllowedTools)),
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user