Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions indexer/pkg/solingest/logger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ func TestTemporalLoggerErrorLevel(t *testing.T) {
err: errors.New("Code: 62. DB::Exception: Syntax error"),
want: slog.LevelError,
},
{
name: "third-party http 503 demoted to warn",
msg: "Activity error.",
err: errors.New("validatorsapp refresh: failed to get validators: unexpected status code 503: <html><body><h1>503 Service Unavailable</h1></body></html>"),
want: slog.LevelWarn,
},
{
name: "context cancellation demoted to warn",
msg: "Activity error.",
Expand Down
11 changes: 11 additions & 0 deletions utils/pkg/dberror/dberror.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ var eofRe = regexp.MustCompile(`\beof\b`)
// longer digit run (e.g. "statuscode: 2001").
var awsRespErrRe = regexp.MustCompile(`https response error statuscode: (200|500|502|503|504)\b`)
Comment thread
ben-dz marked this conversation as resolved.
Outdated

// httpStatus5xxRe matches the "status code NNN" shape non-AWS HTTP clients use
// (e.g. validators.app's "unexpected status code 503") for the same retryable
// set as awsRespErrRe. 501/505 are permanent endpoint failures and 4xx are
// actionable, so all keep paging.
var httpStatus5xxRe = regexp.MustCompile(`status[ _]?code[:= ]?\s*(500|502|503|504)\b`)
Comment thread
ben-dz marked this conversation as resolved.
Outdated

// ErrTransient is a sentinel that explicitly marks an error as transient for
// IsTransient, independent of its message. Wrap a return with it (e.g. via
// errors.Join or fmt.Errorf("...: %w", ErrTransient)) when the caller knows a
Expand Down Expand Up @@ -117,6 +123,11 @@ func Classify(err error) ErrorType {
return ErrorTypeConnectivity
}

// Retryable 5xx from any other HTTP upstream.
if httpStatus5xxRe.MatchString(errStr) {
return ErrorTypeConnectivity
}

// Connection/connectivity patterns
connectivityPatterns := []string{
"connectivityerror",
Expand Down
9 changes: 9 additions & 0 deletions utils/pkg/dberror/dberror_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,15 @@ func TestClassifyAndIsTransient(t *testing.T) {
// A non-AWS message mentioning statuscode: 200 without the SDK prefix must not match.
{"non-aws statuscode 200", errors.New("handler returned statuscode: 200 but body was empty"), dberror.ErrorTypeUnknown, false},

// Non-AWS HTTP clients format retryable 5xx as "status code NNN" (the
// validators.app shape that paged on 2026-08-30). Same 5xx set as the AWS
// shape; 4xx and 501 stay actionable.
{"validatorsapp 503", errors.New(`validatorsapp refresh: failed to get validators: unexpected status code 503: <html><body><h1>503 Service Unavailable</h1></body></html>`), dberror.ErrorTypeConnectivity, true},
{"generic status code 500", errors.New("unexpected status code 500: internal server error"), dberror.ErrorTypeConnectivity, true},
{"generic status_code variant", errors.New("request failed: status_code=502"), dberror.ErrorTypeConnectivity, true},
{"generic status code 400 stays actionable", errors.New("unexpected status code 400: bad request"), dberror.ErrorTypeUnknown, false},
{"generic status code 501 stays actionable", errors.New("unexpected status code 501: not implemented"), dberror.ErrorTypeUnknown, false},

// Non-transient: real, actionable failures should still escalate to ERROR.
{"syntax error", errors.New("Code: 62. DB::Exception: Syntax error"), dberror.ErrorTypeQuery, false},
{"access denied", errors.New("access denied for user"), dberror.ErrorTypeAuth, false},
Expand Down
Loading