| |
| |
| |
| package logging |
|
|
| import ( |
| "fmt" |
| "net/http" |
| "runtime/debug" |
| "strings" |
| "time" |
|
|
| "github.com/gin-gonic/gin" |
| "github.com/router-for-me/CLIProxyAPI/v6/internal/util" |
| log "github.com/sirupsen/logrus" |
| ) |
|
|
| |
| var aiAPIPrefixes = []string{ |
| "/v1/chat/completions", |
| "/v1/completions", |
| "/v1/messages", |
| "/v1/responses", |
| "/v1beta/models/", |
| "/api/provider/", |
| } |
|
|
| const skipGinLogKey = "__gin_skip_request_logging__" |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| func GinLogrusLogger() gin.HandlerFunc { |
| return func(c *gin.Context) { |
| start := time.Now() |
| path := c.Request.URL.Path |
| raw := util.MaskSensitiveQuery(c.Request.URL.RawQuery) |
|
|
| |
| var requestID string |
| if isAIAPIPath(path) { |
| requestID = GenerateRequestID() |
| SetGinRequestID(c, requestID) |
| ctx := WithRequestID(c.Request.Context(), requestID) |
| c.Request = c.Request.WithContext(ctx) |
| } |
|
|
| c.Next() |
|
|
| if shouldSkipGinRequestLogging(c) { |
| return |
| } |
|
|
| if raw != "" { |
| path = path + "?" + raw |
| } |
|
|
| latency := time.Since(start) |
| if latency > time.Minute { |
| latency = latency.Truncate(time.Second) |
| } else { |
| latency = latency.Truncate(time.Millisecond) |
| } |
|
|
| statusCode := c.Writer.Status() |
| clientIP := c.ClientIP() |
| method := c.Request.Method |
| errorMessage := c.Errors.ByType(gin.ErrorTypePrivate).String() |
|
|
| if requestID == "" { |
| requestID = "--------" |
| } |
| logLine := fmt.Sprintf("%3d | %13v | %15s | %-7s \"%s\"", statusCode, latency, clientIP, method, path) |
| if errorMessage != "" { |
| logLine = logLine + " | " + errorMessage |
| } |
|
|
| entry := log.WithField("request_id", requestID) |
|
|
| switch { |
| case statusCode >= http.StatusInternalServerError: |
| entry.Error(logLine) |
| case statusCode >= http.StatusBadRequest: |
| entry.Warn(logLine) |
| default: |
| entry.Info(logLine) |
| } |
| } |
| } |
|
|
| |
| func isAIAPIPath(path string) bool { |
| for _, prefix := range aiAPIPrefixes { |
| if strings.HasPrefix(path, prefix) { |
| return true |
| } |
| } |
| return false |
| } |
|
|
| |
| |
| |
| |
| |
| |
| func GinLogrusRecovery() gin.HandlerFunc { |
| return gin.CustomRecovery(func(c *gin.Context, recovered interface{}) { |
| log.WithFields(log.Fields{ |
| "panic": recovered, |
| "stack": string(debug.Stack()), |
| "path": c.Request.URL.Path, |
| }).Error("recovered from panic") |
|
|
| c.AbortWithStatus(http.StatusInternalServerError) |
| }) |
| } |
|
|
| |
| |
| func SkipGinRequestLogging(c *gin.Context) { |
| if c == nil { |
| return |
| } |
| c.Set(skipGinLogKey, true) |
| } |
|
|
| func shouldSkipGinRequestLogging(c *gin.Context) bool { |
| if c == nil { |
| return false |
| } |
| val, exists := c.Get(skipGinLogKey) |
| if !exists { |
| return false |
| } |
| flag, ok := val.(bool) |
| return ok && flag |
| } |
|
|