refactor(ops): 重构ops核心服务层代码

This commit is contained in:
IanShaw027
2026-01-14 12:40:12 +08:00
parent 182683814b
commit 967e25878f
6 changed files with 82 additions and 13 deletions

View File

@@ -22,14 +22,13 @@ type OpsErrorLog struct {
Platform string `json:"platform"`
Model string `json:"model"`
LatencyMs *int `json:"latency_ms"`
IsRetryable bool `json:"is_retryable"`
RetryCount int `json:"retry_count"`
Resolved bool `json:"resolved"`
ResolvedAt *time.Time `json:"resolved_at"`
ResolvedByUserID *int64 `json:"resolved_by_user_id"`
ResolvedByUserName string `json:"resolved_by_user_name"`
ResolvedRetryID *int64 `json:"resolved_retry_id"`
ResolvedStatusRaw string `json:"-"`
@@ -37,10 +36,13 @@ type OpsErrorLog struct {
RequestID string `json:"request_id"`
Message string `json:"message"`
UserID *int64 `json:"user_id"`
APIKeyID *int64 `json:"api_key_id"`
AccountID *int64 `json:"account_id"`
GroupID *int64 `json:"group_id"`
UserID *int64 `json:"user_id"`
UserEmail string `json:"user_email"`
APIKeyID *int64 `json:"api_key_id"`
AccountID *int64 `json:"account_id"`
AccountName string `json:"account_name"`
GroupID *int64 `json:"group_id"`
GroupName string `json:"group_name"`
ClientIP *string `json:"client_ip"`
RequestPath string `json:"request_path"`
@@ -110,6 +112,7 @@ type OpsRetryAttempt struct {
SourceErrorID int64 `json:"source_error_id"`
Mode string `json:"mode"`
PinnedAccountID *int64 `json:"pinned_account_id"`
PinnedAccountName string `json:"pinned_account_name"`
Status string `json:"status"`
StartedAt *time.Time `json:"started_at"`
@@ -121,6 +124,7 @@ type OpsRetryAttempt struct {
HTTPStatusCode *int `json:"http_status_code"`
UpstreamRequestID *string `json:"upstream_request_id"`
UsedAccountID *int64 `json:"used_account_id"`
UsedAccountName string `json:"used_account_name"`
ResponsePreview *string `json:"response_preview"`
ResponseTruncated *bool `json:"response_truncated"`

View File

@@ -98,7 +98,6 @@ type OpsInsertErrorLogInput struct {
// It is set by OpsService.RecordError before persisting.
UpstreamErrorsJSON *string
DurationMs *int
TimeToFirstTokenMs *int64
RequestBodyJSON *string // sanitized json string (not raw bytes)

View File

@@ -236,7 +236,68 @@ func (s *OpsService) GetErrorLogs(ctx context.Context, filter *OpsErrorLogFilter
if s.opsRepo == nil {
return &OpsErrorLogList{Errors: []*OpsErrorLog{}, Total: 0, Page: 1, PageSize: 20}, nil
}
return s.opsRepo.ListErrorLogs(ctx, filter)
result, err := s.opsRepo.ListErrorLogs(ctx, filter)
if err != nil {
log.Printf("[Ops] GetErrorLogs failed: %v", err)
return nil, err
}
// Apply error filtering based on settings (for historical data)
result = s.filterErrorLogsBySettings(ctx, result)
return result, nil
}
// filterErrorLogsBySettings filters error logs based on advanced settings.
// This ensures that historical errors are also filtered when viewing the dashboard.
func (s *OpsService) filterErrorLogsBySettings(ctx context.Context, result *OpsErrorLogList) *OpsErrorLogList {
if result == nil || len(result.Errors) == 0 {
return result
}
settings, err := s.GetOpsAdvancedSettings(ctx)
if err != nil || settings == nil {
// If we can't get settings, return unfiltered (fail open)
return result
}
filtered := make([]*OpsErrorLog, 0, len(result.Errors))
for _, errLog := range result.Errors {
if shouldFilterErrorLog(settings, errLog) {
continue // Skip this error
}
filtered = append(filtered, errLog)
}
// Update total count to reflect filtered results
result.Errors = filtered
result.Total = len(filtered)
return result
}
// shouldFilterErrorLog determines if an error log should be filtered based on settings.
func shouldFilterErrorLog(settings *OpsAdvancedSettings, errLog *OpsErrorLog) bool {
if settings == nil || errLog == nil {
return false
}
msgLower := strings.ToLower(errLog.Message)
// Check if count_tokens errors should be ignored
if settings.IgnoreCountTokensErrors && strings.Contains(errLog.RequestPath, "/count_tokens") {
return true
}
// Check if context canceled errors should be ignored
if settings.IgnoreContextCanceled && strings.Contains(msgLower, "context canceled") {
return true
}
// Check if "no available accounts" errors should be ignored
if settings.IgnoreNoAvailableAccounts && strings.Contains(msgLower, "no available accounts") {
return true
}
return false
}
func (s *OpsService) GetErrorLogByID(ctx context.Context, id int64) (*OpsErrorLogDetail, error) {

View File

@@ -368,9 +368,11 @@ func defaultOpsAdvancedSettings() *OpsAdvancedSettings {
Aggregation: OpsAggregationSettings{
AggregationEnabled: false,
},
IgnoreCountTokensErrors: false,
AutoRefreshEnabled: false,
AutoRefreshIntervalSec: 30,
IgnoreCountTokensErrors: false,
IgnoreContextCanceled: true, // Default to true - client disconnects are not errors
IgnoreNoAvailableAccounts: false, // Default to false - this is a real routing issue
AutoRefreshEnabled: false,
AutoRefreshIntervalSec: 30,
}
}

View File

@@ -81,6 +81,8 @@ type OpsAdvancedSettings struct {
DataRetention OpsDataRetentionSettings `json:"data_retention"`
Aggregation OpsAggregationSettings `json:"aggregation"`
IgnoreCountTokensErrors bool `json:"ignore_count_tokens_errors"`
IgnoreContextCanceled bool `json:"ignore_context_canceled"`
IgnoreNoAvailableAccounts bool `json:"ignore_no_available_accounts"`
AutoRefreshEnabled bool `json:"auto_refresh_enabled"`
AutoRefreshIntervalSec int `json:"auto_refresh_interval_seconds"`
}

View File

@@ -38,8 +38,9 @@ type OpsUpstreamErrorEvent struct {
AtUnixMs int64 `json:"at_unix_ms,omitempty"`
// Context
Platform string `json:"platform,omitempty"`
AccountID int64 `json:"account_id,omitempty"`
Platform string `json:"platform,omitempty"`
AccountID int64 `json:"account_id,omitempty"`
AccountName string `json:"account_name,omitempty"`
// Outcome
UpstreamStatusCode int `json:"upstream_status_code,omitempty"`