error.go 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. package source
  2. import (
  3. "errors"
  4. "fmt"
  5. "reflect"
  6. "strings"
  7. "sync"
  8. "github.com/opencost/opencost/core/pkg/log"
  9. )
  10. // errorType used to check HasError
  11. var errorType = reflect.TypeOf((*error)(nil)).Elem()
  12. // NoStoreAPIWarning is a warning that we would consider an error. It returns partial data relating only to the
  13. // store apis which were reachable. In order to ensure integrity of data across all clusters, we'll need to identify
  14. // this warning and convert it to an error.
  15. const NoStoreAPIWarning string = "No StoreAPIs matched for this query"
  16. // IsNoStoreAPIWarning checks a warning to determine if it is equivalent to a no store API query.
  17. func IsNoStoreAPIWarning(warning string) bool {
  18. return strings.EqualFold(warning, NoStoreAPIWarning)
  19. }
  20. //--------------------------------------------------------------------------
  21. // Help Retry Error
  22. //--------------------------------------------------------------------------
  23. // HelpRetryError is a wrapper error type which indicates an error should induce a retry, and
  24. // is non-fatal
  25. type HelpRetryError struct {
  26. wrapped error
  27. }
  28. func (h *HelpRetryError) Unwrap() error {
  29. return h.wrapped
  30. }
  31. func (h *HelpRetryError) Error() string {
  32. return h.wrapped.Error()
  33. }
  34. func NewHelpRetryError(cause error) error {
  35. return &HelpRetryError{wrapped: cause}
  36. }
  37. func IsRetryable(err error) bool {
  38. return errors.Is(err, &HelpRetryError{})
  39. }
  40. //--------------------------------------------------------------------------
  41. // Error Collection
  42. //--------------------------------------------------------------------------
  43. type QueryError struct {
  44. Query string `json:"query"`
  45. Error error `json:"error"`
  46. ParseError error `json:"parseError"`
  47. }
  48. // String returns a string representation of the QueryError
  49. func (qe *QueryError) String() string {
  50. var sb strings.Builder
  51. sb.WriteString("Errors:\n")
  52. if qe.Error != nil {
  53. sb.WriteString(fmt.Sprintf(" Request Error: %s\n", qe.Error))
  54. }
  55. if qe.ParseError != nil {
  56. sb.WriteString(fmt.Sprintf(" Parse Error: %s\n", qe.ParseError))
  57. }
  58. sb.WriteString(fmt.Sprintf("for Query: %s\n", qe.Query))
  59. return sb.String()
  60. }
  61. type QueryWarning struct {
  62. Query string `json:"query"`
  63. Warnings []string `json:"warnings"`
  64. }
  65. // String returns a string representation of the QueryWarning
  66. func (qw *QueryWarning) String() string {
  67. var sb strings.Builder
  68. sb.WriteString("Warnings:\n")
  69. for i, w := range qw.Warnings {
  70. sb.WriteString(fmt.Sprintf(" %d) %s\n", i+1, w))
  71. }
  72. sb.WriteString(fmt.Sprintf("for Query: %s\n", qw.Query))
  73. return sb.String()
  74. }
  75. // QueryErrorCollection represents a collection of query errors and warnings made via context.
  76. type QueryErrorCollection interface {
  77. // Warnings is a slice of the QueryWarning instances
  78. Warnings() []*QueryWarning
  79. // Errors is a slice of the QueryError instances
  80. Errors() []*QueryError
  81. // ToErrorAndWarningStrings returns the errors and warnings in the collection
  82. // as two string slices.
  83. ToErrorAndWarningStrings() (errors []string, warnings []string)
  84. }
  85. // ErrorsAndWarningStrings is a container struct for string representation storage/caching
  86. type ErrorsAndWarningStrings struct {
  87. Errors []string
  88. Warnings []string
  89. }
  90. // QueryErrorCollector is used to collect prometheus query errors and warnings, and also meets the
  91. // Error interface
  92. type QueryErrorCollector struct {
  93. m sync.RWMutex
  94. errors []*QueryError
  95. warnings []*QueryWarning
  96. }
  97. // Appends a QueryError to the errors list
  98. func (ec *QueryErrorCollector) AppendError(err *QueryError) {
  99. ec.m.Lock()
  100. defer ec.m.Unlock()
  101. ec.errors = append(ec.errors, err)
  102. }
  103. // Appends a QueryWarning to the warnings list
  104. func (ec *QueryErrorCollector) AppendWarning(warn *QueryWarning) {
  105. ec.m.Lock()
  106. defer ec.m.Unlock()
  107. ec.warnings = append(ec.warnings, warn)
  108. }
  109. // Reports an error to the collector. Ignores if the error is nil and the warnings
  110. // are empty
  111. func (ec *QueryErrorCollector) Report(query string, warnings []string, requestError error, parseError error) {
  112. if requestError == nil && parseError == nil && len(warnings) == 0 {
  113. return
  114. }
  115. ec.m.Lock()
  116. defer ec.m.Unlock()
  117. if requestError != nil || parseError != nil {
  118. ec.errors = append(ec.errors, &QueryError{
  119. Query: query,
  120. Error: requestError,
  121. ParseError: parseError,
  122. })
  123. }
  124. if len(warnings) > 0 {
  125. ec.warnings = append(ec.warnings, &QueryWarning{
  126. Query: query,
  127. Warnings: warnings,
  128. })
  129. }
  130. }
  131. // Whether or not the collector caught any warnings
  132. func (ec *QueryErrorCollector) IsWarning() bool {
  133. ec.m.RLock()
  134. defer ec.m.RUnlock()
  135. return len(ec.warnings) > 0
  136. }
  137. // Whether or not the collector caught errors
  138. func (ec *QueryErrorCollector) IsError() bool {
  139. ec.m.RLock()
  140. defer ec.m.RUnlock()
  141. return len(ec.errors) > 0
  142. }
  143. // Warnings caught by the collector
  144. func (ec *QueryErrorCollector) Warnings() []*QueryWarning {
  145. ec.m.RLock()
  146. defer ec.m.RUnlock()
  147. warns := make([]*QueryWarning, len(ec.warnings))
  148. copy(warns, ec.warnings)
  149. return warns
  150. }
  151. // Errors caught by the collector
  152. func (ec *QueryErrorCollector) Errors() []*QueryError {
  153. ec.m.RLock()
  154. defer ec.m.RUnlock()
  155. errs := make([]*QueryError, len(ec.errors))
  156. copy(errs, ec.errors)
  157. return errs
  158. }
  159. // Implement the error interface to allow returning as an aggregated error
  160. func (ec *QueryErrorCollector) Error() string {
  161. ec.m.RLock()
  162. defer ec.m.RUnlock()
  163. var sb strings.Builder
  164. if len(ec.errors) > 0 {
  165. sb.WriteString("Error Collection:\n")
  166. for i, e := range ec.errors {
  167. sb.WriteString(fmt.Sprintf("%d) %s\n", i, e))
  168. }
  169. }
  170. if len(ec.warnings) > 0 {
  171. sb.WriteString("Warning Collection:\n")
  172. for _, w := range ec.warnings {
  173. sb.WriteString(w.String())
  174. }
  175. }
  176. return sb.String()
  177. }
  178. // ToErrorAndWarningStrings returns the errors and warnings in the collection as two string slices.
  179. func (ec *QueryErrorCollector) ToErrorAndWarningStrings() (errors []string, warnings []string) {
  180. for _, e := range ec.Errors() {
  181. errors = append(errors, e.String())
  182. }
  183. for _, w := range ec.Warnings() {
  184. warnings = append(warnings, w.String())
  185. }
  186. return
  187. }
  188. // As is a special method that implicitly works with the `errors.As()` go
  189. // helper to locate the _first_ instance of the provided target type in the
  190. // collection.
  191. func (ec *QueryErrorCollector) As(target interface{}) bool {
  192. if target == nil {
  193. log.Errorf("ErrorCollection.As() target cannot be nil")
  194. return false
  195. }
  196. val := reflect.ValueOf(target)
  197. typ := val.Type()
  198. if typ.Kind() != reflect.Ptr || val.IsNil() {
  199. log.Errorf("ErrorCollection.As() target must be a non-nil pointer")
  200. return false
  201. }
  202. if e := typ.Elem(); e.Kind() != reflect.Interface && !e.Implements(errorType) {
  203. log.Errorf("ErrorCollection.As() *target must be interface or implement error")
  204. return false
  205. }
  206. targetType := typ.Elem()
  207. for _, err := range AllErrorsFor(ec) {
  208. if reflect.TypeOf(err).AssignableTo(targetType) {
  209. val.Elem().Set(reflect.ValueOf(err))
  210. return true
  211. }
  212. if x, ok := err.(interface{ As(interface{}) bool }); ok && x.As(target) {
  213. return true
  214. }
  215. }
  216. return false
  217. }
  218. // IsErrorCollection returns true if the provided error is an ErrorCollection
  219. func IsErrorCollection(err error) bool {
  220. _, ok := err.(QueryErrorCollection)
  221. return ok
  222. }
  223. func AllErrorsFor(collection QueryErrorCollection) []error {
  224. var errs []error
  225. for _, qe := range collection.Errors() {
  226. if qe.Error != nil {
  227. errs = append(errs, qe.Error)
  228. }
  229. if qe.ParseError != nil {
  230. errs = append(errs, qe.ParseError)
  231. }
  232. }
  233. return errs
  234. }
  235. // WrapError wraps the given error with the given message, usually for adding
  236. // context, but persists the existing type of error.
  237. func WrapError(err error, msg string) error {
  238. switch e := err.(type) {
  239. case CommError:
  240. return e.Wrap(msg)
  241. case NoDataError:
  242. return e.Wrap(msg)
  243. default:
  244. return fmt.Errorf("%s: %s", msg, err)
  245. }
  246. }
  247. // CommError describes an error communicating with Prometheus
  248. type CommError struct {
  249. messages []string
  250. }
  251. // NewCommError creates a new CommError
  252. func NewCommError(messages ...string) CommError {
  253. return CommError{messages: messages}
  254. }
  255. // CommErrorf creates a new CommError using a string formatter
  256. func CommErrorf(format string, args ...interface{}) CommError {
  257. return NewCommError(fmt.Sprintf(format, args...))
  258. }
  259. // IsCommError returns true if the given error is a CommError
  260. func IsCommError(err error) bool {
  261. _, ok := err.(CommError)
  262. return ok
  263. }
  264. // Error prints the error as a string
  265. func (pce CommError) Error() string {
  266. return fmt.Sprintf("Communication error: %s", strings.Join(pce.messages, ": "))
  267. }
  268. // Wrap wraps the error with the given message, but persists the error type.
  269. func (pce CommError) Wrap(message string) CommError {
  270. pce.messages = append([]string{message}, pce.messages...)
  271. return pce
  272. }
  273. // NoDataError indicates that no data was returned by Prometheus. This should
  274. // be treated like an EOF error, in that it may be expected.
  275. type NoDataError struct {
  276. messages []string
  277. }
  278. // NewNoDataError creates a new NoDataError
  279. func NewNoDataError(messages ...string) NoDataError {
  280. return NoDataError{messages: messages}
  281. }
  282. // IsNoDataError returns true if the given error is a NoDataError
  283. func IsNoDataError(err error) bool {
  284. _, ok := err.(NoDataError)
  285. return ok
  286. }
  287. // Error prints the error as a string
  288. func (nde NoDataError) Error() string {
  289. return fmt.Sprintf("No data error: %s", strings.Join(nde.messages, ": "))
  290. }
  291. // Wrap wraps the error with the given message, but persists the error type.
  292. func (nde NoDataError) Wrap(message string) NoDataError {
  293. nde.messages = append([]string{message}, nde.messages...)
  294. return nde
  295. }