cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.77.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/promapi/errors.go

164lines · modecode

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