From 807b766e79c33c80584369716ea2e15cb3948c01 Mon Sep 17 00:00:00 2001 From: tbphp Date: Mon, 14 Jul 2025 00:18:15 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=97=A5=E5=BF=97=E5=A2=9E=E5=BC=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/models/types.go | 4 +++- internal/proxy/server.go | 33 ++++++++++++++++------------ internal/types/types.go | 1 + internal/utils/string_utils.go | 8 +++++++ web/src/components/logs/LogTable.vue | 24 ++++++++++++++++++++ web/src/types/models.ts | 2 ++ 6 files changed, 57 insertions(+), 15 deletions(-) diff --git a/internal/models/types.go b/internal/models/types.go index 6c59680..f619f37 100644 --- a/internal/models/types.go +++ b/internal/models/types.go @@ -80,11 +80,13 @@ type RequestLog struct { IsSuccess bool `gorm:"not null" json:"is_success"` SourceIP string `gorm:"type:varchar(45)" json:"source_ip"` StatusCode int `gorm:"not null" json:"status_code"` - RequestPath string `gorm:"type:varchar(1024)" json:"request_path"` + RequestPath string `gorm:"type:varchar(500)" json:"request_path"` Duration int64 `gorm:"not null" json:"duration_ms"` ErrorMessage string `gorm:"type:text" json:"error_message"` UserAgent string `gorm:"type:varchar(512)" json:"user_agent"` Retries int `gorm:"not null" json:"retries"` + UpstreamAddr string `gorm:"type:varchar(500)" json:"upstream_addr"` + IsStream bool `gorm:"not null" json:"is_stream"` } // StatCard 用于仪表盘的单个统计卡片数据 diff --git a/internal/proxy/server.go b/internal/proxy/server.go index b72478a..b6622e9 100644 --- a/internal/proxy/server.go +++ b/internal/proxy/server.go @@ -116,11 +116,11 @@ func (ps *ProxyServer) executeRequestWithRetry( logrus.Debugf("Max retries exceeded for group %s after %d attempts. Parsed Error: %s", group.Name, retryCount, logMessage) keyID, _ := strconv.ParseUint(lastError.KeyID, 10, 64) - ps.logRequest(c, group, uint(keyID), startTime, lastError.StatusCode, retryCount, errors.New(logMessage)) + ps.logRequest(c, group, uint(keyID), startTime, lastError.StatusCode, retryCount, errors.New(logMessage), isStream, lastError.UpstreamAddr) } else { response.Error(c, app_errors.ErrMaxRetriesExceeded) logrus.Debugf("Max retries exceeded for group %s after %d attempts.", group.Name, retryCount) - ps.logRequest(c, group, 0, startTime, http.StatusServiceUnavailable, retryCount, app_errors.ErrMaxRetriesExceeded) + ps.logRequest(c, group, 0, startTime, http.StatusServiceUnavailable, retryCount, app_errors.ErrMaxRetriesExceeded, isStream, "") } return } @@ -129,7 +129,7 @@ func (ps *ProxyServer) executeRequestWithRetry( if err != nil { logrus.Errorf("Failed to select a key for group %s on attempt %d: %v", group.Name, retryCount+1, err) response.Error(c, app_errors.NewAPIError(app_errors.ErrNoKeysAvailable, err.Error())) - ps.logRequest(c, group, 0, startTime, http.StatusServiceUnavailable, retryCount, err) + ps.logRequest(c, group, 0, startTime, http.StatusServiceUnavailable, retryCount, err, isStream, "") return } @@ -177,7 +177,7 @@ func (ps *ProxyServer) executeRequestWithRetry( if err != nil || (resp != nil && resp.StatusCode >= 400) { if err != nil && app_errors.IsIgnorableError(err) { logrus.Debugf("Client-side ignorable error for key %s, aborting retries: %v", utils.MaskAPIKey(apiKey.KeyValue), err) - ps.logRequest(c, group, apiKey.ID, startTime, 499, retryCount+1, err) + ps.logRequest(c, group, apiKey.ID, startTime, 499, retryCount+1, err, isStream, upstreamURL) return } @@ -212,6 +212,7 @@ func (ps *ProxyServer) executeRequestWithRetry( ParsedErrorMessage: parsedError, KeyID: fmt.Sprintf("%d", apiKey.ID), Attempt: retryCount + 1, + UpstreamAddr: upstreamURL, }) ps.executeRequestWithRetry(c, channelHandler, group, bodyBytes, isStream, startTime, retryCount+1, newRetryErrors) return @@ -219,7 +220,7 @@ func (ps *ProxyServer) executeRequestWithRetry( ps.keyProvider.UpdateStatus(apiKey, group, true) logrus.Debugf("Request for group %s succeeded on attempt %d with key %s", group.Name, retryCount+1, utils.MaskAPIKey(apiKey.KeyValue)) - ps.logRequest(c, group, apiKey.ID, startTime, resp.StatusCode, retryCount+1, nil) + ps.logRequest(c, group, apiKey.ID, startTime, resp.StatusCode, retryCount+1, nil, isStream, upstreamURL) for key, values := range resp.Header { for _, value := range values { @@ -244,6 +245,8 @@ func (ps *ProxyServer) logRequest( statusCode int, retries int, finalError error, + isStream bool, + upstreamAddr string, ) { if ps.requestLogService == nil { return @@ -252,15 +255,17 @@ func (ps *ProxyServer) logRequest( duration := time.Since(startTime).Milliseconds() logEntry := &models.RequestLog{ - GroupID: group.ID, - KeyID: keyID, - IsSuccess: finalError == nil && statusCode < 400, - SourceIP: c.ClientIP(), - StatusCode: statusCode, - RequestPath: c.Request.URL.String(), - Duration: duration, - UserAgent: c.Request.UserAgent(), - Retries: retries, + GroupID: group.ID, + KeyID: keyID, + IsSuccess: finalError == nil && statusCode < 400, + SourceIP: c.ClientIP(), + StatusCode: statusCode, + RequestPath: utils.TruncateString(c.Request.URL.String(), 500), + Duration: duration, + UserAgent: c.Request.UserAgent(), + Retries: retries, + IsStream: isStream, + UpstreamAddr: utils.TruncateString(upstreamAddr, 500), } if finalError != nil { diff --git a/internal/types/types.go b/internal/types/types.go index d166c8e..4ef8beb 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -85,4 +85,5 @@ type RetryError struct { ParsedErrorMessage string `json:"-"` KeyID string `json:"key_id"` Attempt int `json:"attempt"` + UpstreamAddr string `json:"-"` } diff --git a/internal/utils/string_utils.go b/internal/utils/string_utils.go index 00b0c9b..36f12b9 100644 --- a/internal/utils/string_utils.go +++ b/internal/utils/string_utils.go @@ -10,3 +10,11 @@ func MaskAPIKey(key string) string { } return fmt.Sprintf("%s****%s", key[:4], key[length-4:]) } + +// TruncateString shortens a string to a maximum length. +func TruncateString(s string, maxLength int) string { + if len(s) > maxLength { + return s[:maxLength] + } + return s +} diff --git a/web/src/components/logs/LogTable.vue b/web/src/components/logs/LogTable.vue index 13ed8a7..f0ef526 100644 --- a/web/src/components/logs/LogTable.vue +++ b/web/src/components/logs/LogTable.vue @@ -112,8 +112,20 @@ const createColumns = () => [ { default: () => (row.is_success ? "成功" : "失败") } ), }, + { + title: "类型", + key: "is_stream", + width: 80, + render: (row: LogRow) => + h( + NTag, + { type: row.is_stream ? "info" : "default", size: "small", round: true }, + { default: () => (row.is_stream ? "流式" : "非流") } + ), + }, { title: "状态码", key: "status_code", width: 80 }, { title: "耗时(ms)", key: "duration_ms", width: 100 }, + { title: "重试", key: "retries", width: 60 }, { title: "分组", key: "group_name", width: 120 }, { title: "Key", @@ -142,6 +154,12 @@ const createColumns = () => [ render: (row: LogRow) => h(NEllipsis, { style: "max-width: 200px" }, { default: () => row.request_path }), }, + { + title: "上游地址", + key: "upstream_addr", + render: (row: LogRow) => + h(NEllipsis, { style: "max-width: 200px" }, { default: () => row.upstream_addr }), + }, { title: "源IP", key: "source_ip", width: 130 }, { title: "错误信息", @@ -149,6 +167,12 @@ const createColumns = () => [ render: (row: LogRow) => h(NEllipsis, { style: "max-width: 250px" }, { default: () => row.error_message || "-" }), }, + { + title: "User Agent", + key: "user_agent", + render: (row: LogRow) => + h(NEllipsis, { style: "max-width: 200px" }, { default: () => row.user_agent }), + }, ]; const columns = createColumns(); diff --git a/web/src/types/models.ts b/web/src/types/models.ts index 13f4fc6..a4979ea 100644 --- a/web/src/types/models.ts +++ b/web/src/types/models.ts @@ -105,6 +105,8 @@ export interface RequestLog { retries: number; group_name?: string; key_value?: string; + upstream_addr: string; + is_stream: boolean; } export interface Pagination {