cloudflare/pint

Public

mirrored fromhttps://github.com/cloudflare/pintAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.75.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/promapi/errors.go

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