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:
+23
-4
@@ -18,6 +18,8 @@ var (
|
||||
clientCache sync.Map // token -> *gitea.Client
|
||||
sharedTransOnce sync.Once
|
||||
sharedTrans *http.Transport
|
||||
sharedRTOnce sync.Once
|
||||
sharedRT http.RoundTripper
|
||||
)
|
||||
|
||||
func sharedTransport() *http.Transport {
|
||||
@@ -30,6 +32,22 @@ func sharedTransport() *http.Transport {
|
||||
return sharedTrans
|
||||
}
|
||||
|
||||
// sharedRoundTripper returns the RoundTripper used by every HTTP client in
|
||||
// this package. In debug mode it wraps the underlying transport with a
|
||||
// logger that records one entry per upstream Gitea call. Wrapping at the
|
||||
// RoundTripper layer means both the SDK-driven path (NewClient) and the
|
||||
// hand-rolled REST helpers (DoJSON/DoBytes) get the same instrumentation.
|
||||
func sharedRoundTripper() http.RoundTripper {
|
||||
sharedRTOnce.Do(func() {
|
||||
var rt http.RoundTripper = sharedTransport()
|
||||
if flag.Debug {
|
||||
rt = &loggingRoundTripper{base: rt}
|
||||
}
|
||||
sharedRT = rt
|
||||
})
|
||||
return sharedRT
|
||||
}
|
||||
|
||||
// NewClient returns a cached *gitea.Client keyed by host+token. The SDK's per-client
|
||||
// version cache and the shared transport let us reuse keep-alive connections
|
||||
// and avoid the SDK's /api/v1/version preflight on every tool call.
|
||||
@@ -40,16 +58,17 @@ func NewClient(token string) (*gitea.Client, error) {
|
||||
}
|
||||
|
||||
httpClient := &http.Client{
|
||||
Transport: sharedTransport(),
|
||||
Transport: sharedRoundTripper(),
|
||||
CheckRedirect: checkRedirect,
|
||||
}
|
||||
// We intentionally don't enable gitea.SetDebugMode() even when
|
||||
// flag.Debug is on: that mode writes to a separate, unstructured stream
|
||||
// that doesn't merge with our zap log file. The loggingRoundTripper
|
||||
// installed above provides equivalent (and structured) visibility.
|
||||
opts := []gitea.ClientOption{
|
||||
gitea.SetToken(token),
|
||||
gitea.SetHTTPClient(httpClient),
|
||||
}
|
||||
if flag.Debug {
|
||||
opts = append(opts, gitea.SetDebugMode())
|
||||
}
|
||||
client, err := gitea.NewClient(flag.Host, opts...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create gitea client err: %w", err)
|
||||
|
||||
Reference in New Issue
Block a user