feat(observability): enrich request logs with auth and knowledge context

- Collect per-request auth diagnostics (header presence, scheme, token
  fingerprint, failure stage/reason, subject, expiry) in the auth
  middleware and expose them through RequestLogContextFromGin so the
  shared logger middleware can emit them.
- Add route, query, user-agent, and referer fields to the request log.
- Log knowledge resolve outcome per stage (no_search_points,
  no_candidate_text, no_active_candidates, rerank_failed_fallback,
  resolved) with candidate/selected snippet previews and precise facts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-18 17:16:57 +08:00
parent 4bbce5f083
commit 052bf38dc4
4 changed files with 357 additions and 2 deletions
@@ -6,6 +6,7 @@ import (
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"github.com/geo-platform/tenant-api/internal/shared/auth"
"github.com/geo-platform/tenant-api/internal/shared/response"
)
@@ -22,6 +23,18 @@ func Logger(logger *zap.Logger) gin.HandlerFunc {
zap.String("request_id", RequestIDFromGin(c)),
zap.String("client_ip", c.ClientIP()),
}
if route := c.FullPath(); route != "" {
fields = append(fields, zap.String("route", route))
}
if rawQuery := c.Request.URL.RawQuery; rawQuery != "" {
fields = append(fields, zap.String("query", rawQuery))
}
if userAgent := c.Request.UserAgent(); userAgent != "" {
fields = append(fields, zap.String("user_agent", userAgent))
}
if referer := c.Request.Referer(); referer != "" {
fields = append(fields, zap.String("referer", referer))
}
if appErr, ok := response.AppErrorFromGin(c); ok {
fields = append(fields,
@@ -38,6 +51,31 @@ func Logger(logger *zap.Logger) gin.HandlerFunc {
fields = append(fields, zap.String("error", c.Errors.String()))
}
if authCtx, ok := auth.RequestLogContextFromGin(c); ok {
fields = append(fields, zap.Bool("auth_header_present", authCtx.HeaderPresent))
if authCtx.Scheme != "" {
fields = append(fields, zap.String("auth_scheme", authCtx.Scheme))
}
if authCtx.TokenFingerprint != "" {
fields = append(fields, zap.String("auth_token_fingerprint", authCtx.TokenFingerprint))
}
if authCtx.FailureStage != "" {
fields = append(fields, zap.String("auth_failure_stage", authCtx.FailureStage))
}
if authCtx.FailureReason != "" {
fields = append(fields, zap.String("auth_failure_reason", authCtx.FailureReason))
}
if authCtx.ParseError != "" {
fields = append(fields, zap.String("auth_parse_error", authCtx.ParseError))
}
if authCtx.TokenSubject != "" {
fields = append(fields, zap.String("auth_token_subject", authCtx.TokenSubject))
}
if authCtx.TokenExpiresAt != "" {
fields = append(fields, zap.String("auth_token_expires_at", authCtx.TokenExpiresAt))
}
}
switch status := c.Writer.Status(); {
case status >= 500:
logger.Error("request", fields...)