diff --git a/backend/internal/service/ops_models.go b/backend/internal/service/ops_models.go index 78f7cdd0..98be759f 100644 --- a/backend/internal/service/ops_models.go +++ b/backend/internal/service/ops_models.go @@ -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"` diff --git a/backend/internal/service/ops_port.go b/backend/internal/service/ops_port.go index 37a8107c..cdeea241 100644 --- a/backend/internal/service/ops_port.go +++ b/backend/internal/service/ops_port.go @@ -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) diff --git a/backend/internal/service/ops_service.go b/backend/internal/service/ops_service.go index 3a40c0f6..f1240aaf 100644 --- a/backend/internal/service/ops_service.go +++ b/backend/internal/service/ops_service.go @@ -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) { diff --git a/backend/internal/service/ops_settings.go b/backend/internal/service/ops_settings.go index 6c2f6551..a6a4a0d7 100644 --- a/backend/internal/service/ops_settings.go +++ b/backend/internal/service/ops_settings.go @@ -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, } } diff --git a/backend/internal/service/ops_settings_models.go b/backend/internal/service/ops_settings_models.go index 9ff83ccb..45280a81 100644 --- a/backend/internal/service/ops_settings_models.go +++ b/backend/internal/service/ops_settings_models.go @@ -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"` } diff --git a/backend/internal/service/ops_upstream_context.go b/backend/internal/service/ops_upstream_context.go index 615ae6a1..20c0ea11 100644 --- a/backend/internal/service/ops_upstream_context.go +++ b/backend/internal/service/ops_upstream_context.go @@ -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"`