Skip to content

Commit d0aec6b

Browse files
tbphplimliu
andauthored
feat: 优化日志功能 (#209)
* feat: 添加请求和响应体日志记录功能 - 在 RequestLog 模型中添加 request_body 和 response_body 字段用于存储完整的请求和响应内容 - 修改响应处理器以捕获和返回响应体内容,支持流式和非流式响应 - 更新代理服务器逻辑,在请求日志中记录请求体和响应体 - 在前端日志表格中添加详情查看功能,支持查看完整的请求和响应内容 - 提供 JSON 格式化显示和语法高亮,提升日志查看体验 * feat: 添加是否记录响应和请求体按钮 * fix: 修改请求和响应体记录选项的默认值为false * feat: 添加分组级请求体日志记录控制功能 - 分组 - 高级配置 - 分组配置添加"禁用请求和响应体日志记录"选项 (disable_request_body_logging),覆盖系统设置 * refactor: 重构请求体响应体记录分组级别逻辑 - 不再单独处理分组设置,系统和分组使用同一个配置key - 具体逻辑: - 系统启用 + 分组不做配置 = 记录 - 系统启用 + 分组启用 = 记录 - 系统启用 + 分组禁用 = 未记录请求内容(此分组已禁用请求体记录功能) - 系统禁用 + 分组开启 = 未记录请求内容(系统设置中已关闭请求体记录功能) - 系统禁用 = 未记录请求内容(系统设置中已关闭请求体记录功能) * feat: 优化日志详情记录 * fix: space * fix: code * feat: 记录重试日志 * feat: 内容复制 * feat: 删除响应内容记录 * feat: 优化请求日志记录 * feat: 统计过滤重试日志 * feat: 优化日志详情记录 * feat: 移除url的key记录 * feat: 移除请求key * feat: remove highlight.js * feat: format * fix: 恢复误删的keydelete --------- Co-authored-by: limliu <[email protected]>
1 parent 19988f8 commit d0aec6b

File tree

16 files changed

+633
-537
lines changed

16 files changed

+633
-537
lines changed

internal/app/app.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"time"
1010

1111
"gpt-load/internal/config"
12+
db "gpt-load/internal/db/migrations"
1213
"gpt-load/internal/keypool"
1314
"gpt-load/internal/models"
1415
"gpt-load/internal/proxy"
@@ -89,7 +90,7 @@ func (a *App) Start() error {
8990
return fmt.Errorf("database auto-migration failed: %w", err)
9091
}
9192
// 数据修复
92-
// db.MigrateDatabase(a.db)
93+
db.MigrateDatabase(a.db)
9394
logrus.Info("Database auto-migration completed.")
9495

9596
// 初始化系统设置

internal/config/system_settings.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,6 @@ func (sm *SystemSettingsManager) ValidateGroupConfigOverrides(configMap map[stri
334334
continue
335335
}
336336

337-
338337
field, ok := jsonToField[key]
339338
if !ok {
340339
return fmt.Errorf("invalid setting key: %s", key)

internal/db/migrations/migration.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,5 @@ import (
55
)
66

77
func MigrateDatabase(db *gorm.DB) error {
8-
// return V1_0_13_FixRequestLogs(db)
9-
return nil
8+
return V1_0_22_DropRetriesColumn(db)
109
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package db
2+
3+
import "gorm.io/gorm"
4+
5+
// RequestLog 用于迁移的临时结构体
6+
type RequestLog struct {
7+
Retries int `gorm:"column:retries"`
8+
}
9+
10+
// V1_0_22_DropRetriesColumn 删除request_logs表的retries字段
11+
func V1_0_22_DropRetriesColumn(db *gorm.DB) error {
12+
// 检查retries列是否存在
13+
if db.Migrator().HasColumn(&RequestLog{}, "retries") {
14+
// 删除retries列
15+
if err := db.Migrator().DropColumn(&RequestLog{}, "retries"); err != nil {
16+
return err
17+
}
18+
}
19+
return nil
20+
}

internal/handler/dashboard_handler.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ func (s *Server) getRPMStats(now time.Time) (models.StatCard, error) {
194194
var result rpmStatResult
195195
err := s.DB.Model(&models.RequestLog{}).
196196
Select("count(case when timestamp >= ? then 1 end) as current_requests, count(case when timestamp >= ? and timestamp < ? then 1 end) as previous_requests", tenMinutesAgo, twentyMinutesAgo, tenMinutesAgo).
197-
Where("timestamp >= ?", twentyMinutesAgo).
197+
Where("timestamp >= ? AND request_type = ?", twentyMinutesAgo, models.RequestTypeFinal).
198198
Scan(&result).Error
199199

200200
if err != nil {

internal/handler/group_handler.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -782,13 +782,13 @@ func (s *Server) GetGroupStats(c *gin.Context) {
782782
now := time.Now()
783783
oneHourAgo := now.Add(-1 * time.Hour)
784784

785-
if err := s.DB.Model(&models.RequestLog{}).Where("group_id = ? AND timestamp BETWEEN ? AND ?", groupID, oneHourAgo, now).Count(&total).Error; err != nil {
785+
if err := s.DB.Model(&models.RequestLog{}).Where("group_id = ? AND timestamp BETWEEN ? AND ? AND request_type = ?", groupID, oneHourAgo, now, models.RequestTypeFinal).Count(&total).Error; err != nil {
786786
mu.Lock()
787787
errors = append(errors, fmt.Errorf("failed to get hourly total requests: %w", err))
788788
mu.Unlock()
789789
return
790790
}
791-
if err := s.DB.Model(&models.RequestLog{}).Where("group_id = ? AND timestamp BETWEEN ? AND ? AND is_success = ?", groupID, oneHourAgo, now, false).Count(&failed).Error; err != nil {
791+
if err := s.DB.Model(&models.RequestLog{}).Where("group_id = ? AND timestamp BETWEEN ? AND ? AND is_success = ? AND request_type = ?", groupID, oneHourAgo, now, false, models.RequestTypeFinal).Count(&failed).Error; err != nil {
792792
mu.Lock()
793793
errors = append(errors, fmt.Errorf("failed to get hourly failed requests: %w", err))
794794
mu.Unlock()

internal/middleware/middleware.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,13 @@ func isMonitoringEndpoint(path string) bool {
232232

233233
// extractAuthKey extracts a auth key.
234234
func extractAuthKey(c *gin.Context) string {
235+
// Query key
236+
if key := c.Query("key"); key != "" {
237+
query := c.Request.URL.Query()
238+
query.Del("key")
239+
c.Request.URL.RawQuery = query.Encode()
240+
return key
241+
}
235242

236243
// Bearer token
237244
authHeader := c.GetHeader("Authorization")
@@ -252,11 +259,6 @@ func extractAuthKey(c *gin.Context) string {
252259
return key
253260
}
254261

255-
// Query key
256-
if key := c.Query("key"); key != "" {
257-
return key
258-
}
259-
260262
return ""
261263
}
262264

internal/models/types.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,12 @@ type APIKey struct {
8787
UpdatedAt time.Time `json:"updated_at"`
8888
}
8989

90+
// RequestType 请求类型常量
91+
const (
92+
RequestTypeRetry = "retry"
93+
RequestTypeFinal = "final"
94+
)
95+
9096
// RequestLog 对应 request_logs 表
9197
type RequestLog struct {
9298
ID string `gorm:"type:varchar(36);primaryKey" json:"id"`
@@ -102,12 +108,10 @@ type RequestLog struct {
102108
Duration int64 `gorm:"not null" json:"duration_ms"`
103109
ErrorMessage string `gorm:"type:text" json:"error_message"`
104110
UserAgent string `gorm:"type:varchar(512)" json:"user_agent"`
105-
Retries int `gorm:"not null" json:"retries"`
111+
RequestType string `gorm:"type:varchar(20);not null;default:'final';index" json:"request_type"`
106112
UpstreamAddr string `gorm:"type:varchar(500)" json:"upstream_addr"`
107113
IsStream bool `gorm:"not null" json:"is_stream"`
108-
RequestBody string `gorm:"type:longtext" json:"request_body"`
109-
ResponseBody string `gorm:"type:longtext" json:"response_body"`
110-
BodyLogStatus string `gorm:"type:varchar(50)" json:"body_log_status"` // "enabled", "system_disabled", "group_disabled"
114+
RequestBody string `gorm:"type:longtext" json:"request_body"`
111115
}
112116

113117
// StatCard 用于仪表盘的单个统计卡片数据
Lines changed: 7 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
package proxy
22

33
import (
4-
"bytes"
54
"io"
65
"net/http"
76

87
"github.com/gin-gonic/gin"
98
"github.com/sirupsen/logrus"
109
)
1110

12-
func (ps *ProxyServer) handleStreamingResponse(c *gin.Context, resp *http.Response) string {
11+
func (ps *ProxyServer) handleStreamingResponse(c *gin.Context, resp *http.Response) {
1312
c.Header("Content-Type", "text/event-stream")
1413
c.Header("Cache-Control", "no-cache")
1514
c.Header("Connection", "keep-alive")
@@ -18,46 +17,32 @@ func (ps *ProxyServer) handleStreamingResponse(c *gin.Context, resp *http.Respon
1817
flusher, ok := c.Writer.(http.Flusher)
1918
if !ok {
2019
logrus.Error("Streaming unsupported by the writer, falling back to normal response")
21-
return ps.handleNormalResponse(c, resp)
20+
ps.handleNormalResponse(c, resp)
21+
return
2222
}
2323

24-
var responseBuffer bytes.Buffer
2524
buf := make([]byte, 4*1024)
2625
for {
2726
n, err := resp.Body.Read(buf)
2827
if n > 0 {
29-
// Write to client
3028
if _, writeErr := c.Writer.Write(buf[:n]); writeErr != nil {
3129
logUpstreamError("writing stream to client", writeErr)
32-
return responseBuffer.String()
30+
return
3331
}
34-
// Also capture for logging
35-
responseBuffer.Write(buf[:n])
3632
flusher.Flush()
3733
}
3834
if err == io.EOF {
3935
break
4036
}
4137
if err != nil {
4238
logUpstreamError("reading from upstream", err)
43-
return responseBuffer.String()
39+
return
4440
}
4541
}
46-
return responseBuffer.String()
4742
}
4843

49-
func (ps *ProxyServer) handleNormalResponse(c *gin.Context, resp *http.Response) string {
50-
// Read the response body
51-
responseBody, err := io.ReadAll(resp.Body)
52-
if err != nil {
53-
logUpstreamError("reading response body", err)
54-
return ""
55-
}
56-
57-
// Write to client
58-
if _, err := c.Writer.Write(responseBody); err != nil {
44+
func (ps *ProxyServer) handleNormalResponse(c *gin.Context, resp *http.Response) {
45+
if _, err := io.Copy(c.Writer, resp.Body); err != nil {
5946
logUpstreamError("copying response body", err)
6047
}
61-
62-
return string(responseBody)
6348
}

0 commit comments

Comments
 (0)