From 30748c6558bc97de49ff74871cfec717e50f3322 Mon Sep 17 00:00:00 2001 From: Yi Cai Date: Fri, 23 Oct 2026 13:31:23 +0800 Subject: [PATCH] =?UTF-8?q?fix(logging):=20=E9=9A=90=E8=97=8F=E8=AE=BF?= =?UTF-8?q?=E9=97=AE=E6=97=A5=E5=BF=97=E4=B8=AD=E7=9A=84=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E5=87=AD=E6=8D=AE=20(#7972)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(logging): 隐藏访问日志中的查询凭据 * fix(logging): redact query credentials in access logs --------- Co-authored-by: seefs001 --- middleware/logger.go | 4 +++ middleware/logger_test.go | 58 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 middleware/logger_test.go diff --git a/middleware/logger.go b/middleware/logger.go index bf16b6b61d..ab7ffe55fc 100644 --- a/middleware/logger.go +++ b/middleware/logger.go @@ -5,6 +5,7 @@ import ( "strings" "github.com/QuantumNous/new-api/common" + relaycommon "github.com/QuantumNous/new-api/relay/common" "github.com/gin-gonic/gin" ) @@ -33,6 +34,9 @@ func SetUpLogger(server *gin.Engine) { // Redact the log value only; the handler still needs the original query. if strings.HasPrefix(path, "/api/oauth/") || strings.HasPrefix(path, "/oauth/") { path, _, _ = strings.Cut(path, "?") + } else if param.Request.URL.RawQuery != "" { + query := param.Request.URL.Query().Encode() + path = param.Request.URL.EscapedPath() + relaycommon.SanitizeURLForLog("?"+query) } return fmt.Sprintf("[GIN] %s | %s | %s | %3d | %13v | %15s | %7s %s\n", param.TimeStamp.Format("2006/01/02 - 15:04:05"), diff --git a/middleware/logger_test.go b/middleware/logger_test.go new file mode 100644 index 0000000000..77588e0397 --- /dev/null +++ b/middleware/logger_test.go @@ -0,0 +1,58 @@ +package middleware + +import ( + "bytes" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestSetUpLoggerMasksQueryCredentials(t *testing.T) { + previousMode, previousWriter := gin.Mode(), gin.DefaultWriter + gin.SetMode(gin.TestMode) + t.Cleanup(func() { + gin.SetMode(previousMode) + gin.DefaultWriter = previousWriter + }) + + for _, tc := range []struct{ target, loggedPath string }{ + {"/v1/models?%6bey=first&key=second&API-Key=third&session_token=fourth&alt=sse", + "/v1/models?API-Key=%2A%2A%2Amasked%2A%2A%2A&alt=sse&key=%2A%2A%2Amasked%2A%2A%2A&session_token=%2A%2A%2Amasked%2A%2A%2A"}, + {"/v1/models%23test%25?key=#secret&alt=#sse", + "/v1/models%23test%25?alt=%23sse&key=%2A%2A%2Amasked%2A%2A%2A"}, + {"//v1%2Fmodels?key=secret", "//v1%2Fmodels?key=%2A%2A%2Amasked%2A%2A%2A"}, + {"/v1/models?key=bad%ZZ&%ZZ=bad&token=bad;other=true&alt=sse", "/v1/models?alt=sse"}, + {"/v1/models?key=bad%ZZ", "/v1/models?"}, + {"/v1/models?alt=sse&api-version=2024-02-01", "/v1/models?alt=sse&api-version=2024-02-01"}, + {"/v1/models", "/v1/models"}, + {"/%61pi/oauth/test?code=secret&state=secret", "/api/oauth/test"}, + } { + t.Run(tc.target, func(t *testing.T) { + var output bytes.Buffer + gin.DefaultWriter = &output + router := gin.New() + SetUpLogger(router) + request := httptest.NewRequest(http.MethodGet, tc.target, nil) + originalURL := *request.URL + router.NoRoute(func(c *gin.Context) { + assert.Equal(t, originalURL, *c.Request.URL) + assert.Equal(t, tc.target, c.Request.RequestURI) + c.Status(http.StatusUnauthorized) + }) + response := httptest.NewRecorder() + router.ServeHTTP(response, request) + + require.Equal(t, http.StatusUnauthorized, response.Code) + _, loggedPath, found := strings.Cut(strings.TrimSuffix(output.String(), "\n"), "GET ") + require.True(t, found) + assert.Equal(t, tc.loggedPath, loggedPath) + assert.Equal(t, originalURL, *request.URL) + assert.Equal(t, tc.target, request.RequestURI) + }) + } +}