cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.83.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/promapi/errors.go

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