feat: 日志增强

This commit is contained in:
tbphp
2025-07-14 00:18:15 +08:00
parent 2740254cf1
commit 807b766e79
6 changed files with 57 additions and 15 deletions

View File

@@ -80,11 +80,13 @@ type RequestLog struct {
IsSuccess bool `gorm:"not null" json:"is_success"` IsSuccess bool `gorm:"not null" json:"is_success"`
SourceIP string `gorm:"type:varchar(45)" json:"source_ip"` SourceIP string `gorm:"type:varchar(45)" json:"source_ip"`
StatusCode int `gorm:"not null" json:"status_code"` 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"` Duration int64 `gorm:"not null" json:"duration_ms"`
ErrorMessage string `gorm:"type:text" json:"error_message"` ErrorMessage string `gorm:"type:text" json:"error_message"`
UserAgent string `gorm:"type:varchar(512)" json:"user_agent"` UserAgent string `gorm:"type:varchar(512)" json:"user_agent"`
Retries int `gorm:"not null" json:"retries"` 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 用于仪表盘的单个统计卡片数据 // StatCard 用于仪表盘的单个统计卡片数据

View File

@@ -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) 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) 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 { } else {
response.Error(c, app_errors.ErrMaxRetriesExceeded) response.Error(c, app_errors.ErrMaxRetriesExceeded)
logrus.Debugf("Max retries exceeded for group %s after %d attempts.", group.Name, retryCount) 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 return
} }
@@ -129,7 +129,7 @@ func (ps *ProxyServer) executeRequestWithRetry(
if err != nil { if err != nil {
logrus.Errorf("Failed to select a key for group %s on attempt %d: %v", group.Name, retryCount+1, err) 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())) 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 return
} }
@@ -177,7 +177,7 @@ func (ps *ProxyServer) executeRequestWithRetry(
if err != nil || (resp != nil && resp.StatusCode >= 400) { if err != nil || (resp != nil && resp.StatusCode >= 400) {
if err != nil && app_errors.IsIgnorableError(err) { if err != nil && app_errors.IsIgnorableError(err) {
logrus.Debugf("Client-side ignorable error for key %s, aborting retries: %v", utils.MaskAPIKey(apiKey.KeyValue), 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 return
} }
@@ -212,6 +212,7 @@ func (ps *ProxyServer) executeRequestWithRetry(
ParsedErrorMessage: parsedError, ParsedErrorMessage: parsedError,
KeyID: fmt.Sprintf("%d", apiKey.ID), KeyID: fmt.Sprintf("%d", apiKey.ID),
Attempt: retryCount + 1, Attempt: retryCount + 1,
UpstreamAddr: upstreamURL,
}) })
ps.executeRequestWithRetry(c, channelHandler, group, bodyBytes, isStream, startTime, retryCount+1, newRetryErrors) ps.executeRequestWithRetry(c, channelHandler, group, bodyBytes, isStream, startTime, retryCount+1, newRetryErrors)
return return
@@ -219,7 +220,7 @@ func (ps *ProxyServer) executeRequestWithRetry(
ps.keyProvider.UpdateStatus(apiKey, group, true) 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)) 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 key, values := range resp.Header {
for _, value := range values { for _, value := range values {
@@ -244,6 +245,8 @@ func (ps *ProxyServer) logRequest(
statusCode int, statusCode int,
retries int, retries int,
finalError error, finalError error,
isStream bool,
upstreamAddr string,
) { ) {
if ps.requestLogService == nil { if ps.requestLogService == nil {
return return
@@ -252,15 +255,17 @@ func (ps *ProxyServer) logRequest(
duration := time.Since(startTime).Milliseconds() duration := time.Since(startTime).Milliseconds()
logEntry := &models.RequestLog{ logEntry := &models.RequestLog{
GroupID: group.ID, GroupID: group.ID,
KeyID: keyID, KeyID: keyID,
IsSuccess: finalError == nil && statusCode < 400, IsSuccess: finalError == nil && statusCode < 400,
SourceIP: c.ClientIP(), SourceIP: c.ClientIP(),
StatusCode: statusCode, StatusCode: statusCode,
RequestPath: c.Request.URL.String(), RequestPath: utils.TruncateString(c.Request.URL.String(), 500),
Duration: duration, Duration: duration,
UserAgent: c.Request.UserAgent(), UserAgent: c.Request.UserAgent(),
Retries: retries, Retries: retries,
IsStream: isStream,
UpstreamAddr: utils.TruncateString(upstreamAddr, 500),
} }
if finalError != nil { if finalError != nil {

View File

@@ -85,4 +85,5 @@ type RetryError struct {
ParsedErrorMessage string `json:"-"` ParsedErrorMessage string `json:"-"`
KeyID string `json:"key_id"` KeyID string `json:"key_id"`
Attempt int `json:"attempt"` Attempt int `json:"attempt"`
UpstreamAddr string `json:"-"`
} }

View File

@@ -10,3 +10,11 @@ func MaskAPIKey(key string) string {
} }
return fmt.Sprintf("%s****%s", key[:4], key[length-4:]) 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
}

View File

@@ -112,8 +112,20 @@ const createColumns = () => [
{ default: () => (row.is_success ? "成功" : "失败") } { 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: "状态码", key: "status_code", width: 80 },
{ title: "耗时(ms)", key: "duration_ms", width: 100 }, { title: "耗时(ms)", key: "duration_ms", width: 100 },
{ title: "重试", key: "retries", width: 60 },
{ title: "分组", key: "group_name", width: 120 }, { title: "分组", key: "group_name", width: 120 },
{ {
title: "Key", title: "Key",
@@ -142,6 +154,12 @@ const createColumns = () => [
render: (row: LogRow) => render: (row: LogRow) =>
h(NEllipsis, { style: "max-width: 200px" }, { default: () => row.request_path }), 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: "源IP", key: "source_ip", width: 130 },
{ {
title: "错误信息", title: "错误信息",
@@ -149,6 +167,12 @@ const createColumns = () => [
render: (row: LogRow) => render: (row: LogRow) =>
h(NEllipsis, { style: "max-width: 250px" }, { default: () => row.error_message || "-" }), 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(); const columns = createColumns();

View File

@@ -105,6 +105,8 @@ export interface RequestLog {
retries: number; retries: number;
group_name?: string; group_name?: string;
key_value?: string; key_value?: string;
upstream_addr: string;
is_stream: boolean;
} }
export interface Pagination { export interface Pagination {