cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.81.1

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/promapi/errors.go

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