error.go 6.9 KB

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