cloudflare/pint

Public

mirrored from https://github.com/cloudflare/pintAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.62.0

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

internal/promapi/errors.go

133lines · modecode

1package promapi
2
3import (
4 "context"
5 "encoding/json"
6 "errors"
7 "fmt"
8 "log/slog"
9 "net"
10 "net/http"
11 "strings"
12 "syscall"
13
14 v1 "github.com/prometheus/client_golang/api/prometheus/v1"
15 "github.com/prymitive/current"
16)
17
18func IsUnavailableError(err error) bool {
19 var e1 APIError
20 if ok := errors.As(err, &e1); ok {
21 return e1.ErrorType == v1.ErrServer
22 }
23 return true
24}
25
26func IsQueryTooExpensive(err error) bool {
27 var e1 APIError
28 if ok := errors.As(err, &e1); ok {
29 if e1.ErrorType != v1.ErrExec {
30 return false
31 }
32 if strings.HasPrefix(e1.Err, "query processing would load too many samples into memory in ") {
33 return true
34 }
35 if strings.HasSuffix(e1.Err, "expanding series: context deadline exceeded") {
36 return true
37 }
38 }
39 return false
40}
41
42type APIError struct {
43 Status string `json:"status"`
44 ErrorType v1.ErrorType `json:"errorType"`
45 Err string `json:"error"`
46}
47
48func (e APIError) Error() string {
49 return e.Err
50}
51
52const (
53 ErrUnknown v1.ErrorType = "unknown"
54 ErrJSONStream v1.ErrorType = "json_stream"
55)
56
57func decodeErrorType(s string) v1.ErrorType {
58 switch s {
59 case string(v1.ErrBadData):
60 return v1.ErrBadData
61 case string(v1.ErrTimeout):
62 return v1.ErrTimeout
63 case string(v1.ErrCanceled):
64 return v1.ErrCanceled
65 case string(v1.ErrExec):
66 return v1.ErrExec
67 case string(v1.ErrBadResponse):
68 return v1.ErrBadResponse
69 case string(v1.ErrServer):
70 return v1.ErrServer
71 case string(v1.ErrClient):
72 return v1.ErrClient
73 default:
74 return ErrUnknown
75 }
76}
77
78const (
79 errConnRefused = "connection refused"
80 errConnTimeout = "connection timeout"
81)
82
83func decodeError(err error) string {
84 if errors.Is(err, context.Canceled) {
85 return context.Canceled.Error()
86 }
87
88 if errors.Is(err, syscall.ECONNREFUSED) {
89 return errConnRefused
90 }
91
92 var neterr net.Error
93 if ok := errors.As(err, &neterr); ok && neterr.Timeout() {
94 return errConnTimeout
95 }
96
97 var e1 APIError
98 if ok := errors.As(err, &e1); ok {
99 return fmt.Sprintf("%s: %s", e1.ErrorType, e1.Err)
100 }
101
102 return err.Error()
103}
104
105func tryDecodingAPIError(resp *http.Response) error {
106 slog.Debug("Trying to parse Prometheus error response", slog.Int("code", resp.StatusCode))
107
108 var status, errType, errText string
109 decoder := current.Object(
110 current.Key("status", current.Value(func(s string, _ bool) {
111 status = s
112 })),
113 current.Key("error", current.Value(func(s string, _ bool) {
114 errText = s
115 })),
116 current.Key("errorType", current.Value(func(s string, _ bool) {
117 errType = s
118 })),
119 )
120
121 dec := json.NewDecoder(resp.Body)
122 if err := decoder.Stream(dec); err != nil {
123 switch resp.StatusCode / 100 {
124 case 4:
125 return APIError{Status: "error", ErrorType: v1.ErrClient, Err: fmt.Sprintf("client error: %d", resp.StatusCode)}
126 case 5:
127 return APIError{Status: "error", ErrorType: v1.ErrServer, Err: fmt.Sprintf("server error: %d", resp.StatusCode)}
128 }
129 return APIError{Status: "error", ErrorType: v1.ErrBadResponse, Err: fmt.Sprintf("bad response code: %d", resp.StatusCode)}
130 }
131
132 return APIError{Status: status, ErrorType: decodeErrorType(errType), Err: errText}
133}
134