refactor(proxy): migrate to zerolog structured logging
This commit is contained in:
+82
-12
@@ -3,13 +3,15 @@ package proxy
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/tidwall/gjson"
|
||||
|
||||
"github.com/fujin/anthropic-proxy/internal/auth"
|
||||
"github.com/fujin/anthropic-proxy/internal/logging"
|
||||
)
|
||||
|
||||
func HandleMessages(pool *auth.Pool, profile *SniffedProfile, getSanitizer func() *Sanitizer) gin.HandlerFunc {
|
||||
@@ -22,7 +24,15 @@ func HandleMessages(pool *auth.Pool, profile *SniffedProfile, getSanitizer func(
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("incoming: %s %s (%d bytes) model=%s", c.Request.Method, c.Request.URL.Path, len(body), gjson.GetBytes(body, "model").String())
|
||||
originalBody := make([]byte, len(body))
|
||||
copy(originalBody, body)
|
||||
|
||||
log.Info().
|
||||
Str("method", c.Request.Method).
|
||||
Str("path", c.Request.URL.Path).
|
||||
Int("body_size", len(body)).
|
||||
Str("model", gjson.GetBytes(body, "model").String()).
|
||||
Msg("incoming request")
|
||||
|
||||
san := getSanitizer()
|
||||
body = san.SanitizeRequest(body)
|
||||
@@ -36,24 +46,54 @@ func HandleMessages(pool *auth.Pool, profile *SniffedProfile, getSanitizer func(
|
||||
isStream := gjson.GetBytes(body, "stream").Bool()
|
||||
|
||||
if isStream {
|
||||
handleStream(c, upstream, san, pool, cred, body)
|
||||
handleStream(c, upstream, san, pool, cred, body, originalBody)
|
||||
} else {
|
||||
handleNonStream(c, upstream, san, pool, cred, body)
|
||||
handleNonStream(c, upstream, san, pool, cred, body, originalBody)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleNonStream(c *gin.Context, upstream *UpstreamClient, san *Sanitizer, pool *auth.Pool, cred *auth.Credential, body []byte) {
|
||||
func handleNonStream(c *gin.Context, upstream *UpstreamClient, san *Sanitizer, pool *auth.Pool, cred *auth.Credential, body []byte, originalBody []byte) {
|
||||
startTime := time.Now()
|
||||
respBody, headers, statusCode, err := upstream.Execute(c.Request.Context(), cred, body)
|
||||
if err != nil {
|
||||
log.Printf("upstream error for %s: %v", cred.Email, err)
|
||||
latencyMs := float64(time.Since(startTime).Milliseconds())
|
||||
model := gjson.GetBytes(body, "model").String()
|
||||
log.Error().
|
||||
Err(err).
|
||||
Str("credential", cred.Email).
|
||||
Str("model", model).
|
||||
Bool("stream", false).
|
||||
Str("request_body_original", string(originalBody)).
|
||||
Str("request_body_sanitized", string(body)).
|
||||
Int("request_body_size", len(body)).
|
||||
Float64("latency_ms", latencyMs).
|
||||
Msg("upstream connection error")
|
||||
c.JSON(http.StatusBadGateway, gin.H{"error": "upstream request failed"})
|
||||
return
|
||||
}
|
||||
|
||||
if statusCode >= 400 {
|
||||
pool.MarkFailure(cred, statusCode)
|
||||
log.Printf("upstream %d for %s: %s", statusCode, cred.Email, string(respBody))
|
||||
latencyMs := float64(time.Since(startTime).Milliseconds())
|
||||
model := gjson.GetBytes(body, "model").String()
|
||||
errorType := gjson.GetBytes(respBody, "error.type").String()
|
||||
errorMessage := gjson.GetBytes(respBody, "error.message").String()
|
||||
log.Error().
|
||||
Int("status", statusCode).
|
||||
Str("error_type", errorType).
|
||||
Str("error_message", errorMessage).
|
||||
Str("response_body", string(respBody)).
|
||||
Str("request_id", headers.Get("X-Request-Id")).
|
||||
Float64("latency_ms", latencyMs).
|
||||
Str("credential", cred.Email).
|
||||
Str("model", model).
|
||||
Bool("stream", false).
|
||||
Str("request_body_original", string(originalBody)).
|
||||
Str("request_body_sanitized", string(body)).
|
||||
Int("request_body_size", len(body)).
|
||||
Str("request_headers", logging.RedactHeaders(c.Request.Header)).
|
||||
Msg("upstream error")
|
||||
} else {
|
||||
pool.MarkSuccess(cred)
|
||||
respBody = san.DesanitizeResponse(respBody)
|
||||
@@ -68,10 +108,22 @@ func handleNonStream(c *gin.Context, upstream *UpstreamClient, san *Sanitizer, p
|
||||
c.Data(statusCode, headers.Get("Content-Type"), respBody)
|
||||
}
|
||||
|
||||
func handleStream(c *gin.Context, upstream *UpstreamClient, san *Sanitizer, pool *auth.Pool, cred *auth.Credential, body []byte) {
|
||||
func handleStream(c *gin.Context, upstream *UpstreamClient, san *Sanitizer, pool *auth.Pool, cred *auth.Credential, body []byte, originalBody []byte) {
|
||||
startTime := time.Now()
|
||||
resp, err := upstream.ExecuteStream(c.Request.Context(), cred, body)
|
||||
if err != nil {
|
||||
log.Printf("upstream stream error for %s: %v", cred.Email, err)
|
||||
latencyMs := float64(time.Since(startTime).Milliseconds())
|
||||
model := gjson.GetBytes(body, "model").String()
|
||||
log.Error().
|
||||
Err(err).
|
||||
Str("credential", cred.Email).
|
||||
Str("model", model).
|
||||
Bool("stream", true).
|
||||
Str("request_body_original", string(originalBody)).
|
||||
Str("request_body_sanitized", string(body)).
|
||||
Int("request_body_size", len(body)).
|
||||
Float64("latency_ms", latencyMs).
|
||||
Msg("upstream connection error")
|
||||
c.JSON(http.StatusBadGateway, gin.H{"error": "upstream stream request failed"})
|
||||
return
|
||||
}
|
||||
@@ -80,7 +132,25 @@ func handleStream(c *gin.Context, upstream *UpstreamClient, san *Sanitizer, pool
|
||||
if resp.StatusCode >= 400 {
|
||||
pool.MarkFailure(cred, resp.StatusCode)
|
||||
respBody, _ := io.ReadAll(resp.Body)
|
||||
log.Printf("upstream stream %d for %s: %s", resp.StatusCode, cred.Email, string(respBody))
|
||||
latencyMs := float64(time.Since(startTime).Milliseconds())
|
||||
model := gjson.GetBytes(body, "model").String()
|
||||
errorType := gjson.GetBytes(respBody, "error.type").String()
|
||||
errorMessage := gjson.GetBytes(respBody, "error.message").String()
|
||||
log.Error().
|
||||
Int("status", resp.StatusCode).
|
||||
Str("error_type", errorType).
|
||||
Str("error_message", errorMessage).
|
||||
Str("response_body", string(respBody)).
|
||||
Str("request_id", resp.Header.Get("X-Request-Id")).
|
||||
Float64("latency_ms", latencyMs).
|
||||
Str("credential", cred.Email).
|
||||
Str("model", model).
|
||||
Bool("stream", true).
|
||||
Str("request_body_original", string(originalBody)).
|
||||
Str("request_body_sanitized", string(body)).
|
||||
Int("request_body_size", len(body)).
|
||||
Str("request_headers", logging.RedactHeaders(c.Request.Header)).
|
||||
Msg("upstream error")
|
||||
c.Data(resp.StatusCode, resp.Header.Get("Content-Type"), respBody)
|
||||
return
|
||||
}
|
||||
@@ -94,7 +164,7 @@ func handleStream(c *gin.Context, upstream *UpstreamClient, san *Sanitizer, pool
|
||||
|
||||
flusher, ok := c.Writer.(http.Flusher)
|
||||
if !ok {
|
||||
log.Printf("response writer does not support flushing")
|
||||
log.Error().Msg("response writer does not support flushing")
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "streaming not supported"})
|
||||
return
|
||||
}
|
||||
@@ -108,6 +178,6 @@ func handleStream(c *gin.Context, upstream *UpstreamClient, san *Sanitizer, pool
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
log.Printf("stream scan error: %v", err)
|
||||
log.Error().Err(err).Msg("stream scan error")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user