error.go 8.4 KB

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