error.go 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  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. // Warnings is a slice of the QueryWarning instances
  49. Warnings() []*QueryWarning
  50. // Errors is a slice of the QueryError instances
  51. Errors() []*QueryError
  52. // ToErrorAndWarningStrings returns the errors and warnings in the collection
  53. // as two string slices.
  54. ToErrorAndWarningStrings() (errors []string, warnings []string)
  55. }
  56. // ErrorsAndWarningStrings is a container struct for string representation storage/caching
  57. type ErrorsAndWarningStrings struct {
  58. Errors []string
  59. Warnings []string
  60. }
  61. // QueryErrorCollector is used to collect prometheus query errors and warnings, and also meets the
  62. // Error
  63. type QueryErrorCollector struct {
  64. m sync.RWMutex
  65. errors []*QueryError
  66. warnings []*QueryWarning
  67. }
  68. // Reports an error to the collector. Ignores if the error is nil and the warnings
  69. // are empty
  70. func (ec *QueryErrorCollector) Report(query string, warnings []string, requestError error, parseError error) {
  71. if requestError == nil && parseError == nil && len(warnings) == 0 {
  72. return
  73. }
  74. ec.m.Lock()
  75. defer ec.m.Unlock()
  76. if requestError != nil || parseError != nil {
  77. ec.errors = append(ec.errors, &QueryError{
  78. Query: query,
  79. Error: requestError,
  80. ParseError: parseError,
  81. })
  82. }
  83. if len(warnings) > 0 {
  84. ec.warnings = append(ec.warnings, &QueryWarning{
  85. Query: query,
  86. Warnings: warnings,
  87. })
  88. }
  89. }
  90. // Whether or not the collector caught any warnings
  91. func (ec *QueryErrorCollector) IsWarning() bool {
  92. ec.m.RLock()
  93. defer ec.m.RUnlock()
  94. return len(ec.warnings) > 0
  95. }
  96. // Whether or not the collector caught errors
  97. func (ec *QueryErrorCollector) IsError() bool {
  98. ec.m.RLock()
  99. defer ec.m.RUnlock()
  100. return len(ec.errors) > 0
  101. }
  102. // Warnings caught by the collector
  103. func (ec *QueryErrorCollector) Warnings() []*QueryWarning {
  104. ec.m.RLock()
  105. defer ec.m.RUnlock()
  106. warns := make([]*QueryWarning, len(ec.warnings))
  107. copy(warns, ec.warnings)
  108. return warns
  109. }
  110. // Errors caught by the collector
  111. func (ec *QueryErrorCollector) Errors() []*QueryError {
  112. ec.m.RLock()
  113. defer ec.m.RUnlock()
  114. errs := make([]*QueryError, len(ec.errors))
  115. copy(errs, ec.errors)
  116. return errs
  117. }
  118. // Implement the error interface to allow returning as an aggregated error
  119. func (ec *QueryErrorCollector) Error() string {
  120. ec.m.RLock()
  121. defer ec.m.RUnlock()
  122. var sb strings.Builder
  123. if len(ec.errors) > 0 {
  124. sb.WriteString("Error Collection:\n")
  125. for i, e := range ec.errors {
  126. sb.WriteString(fmt.Sprintf("%d) %s\n", i, e))
  127. }
  128. }
  129. if len(ec.warnings) > 0 {
  130. sb.WriteString("Warning Collection:\n")
  131. for _, w := range ec.warnings {
  132. sb.WriteString(w.String())
  133. }
  134. }
  135. return sb.String()
  136. }
  137. // ToErrorAndWarningStrings returns the errors and warnings in the collection as two string slices.
  138. func (ec *QueryErrorCollector) ToErrorAndWarningStrings() (errors []string, warnings []string) {
  139. for _, e := range ec.Errors() {
  140. errors = append(errors, e.String())
  141. }
  142. for _, w := range ec.Warnings() {
  143. warnings = append(warnings, w.String())
  144. }
  145. return
  146. }
  147. // As is a special method that implicitly works with the `errors.As()` go
  148. // helper to locate the _first_ instance of the provided target type in the
  149. // collection.
  150. func (ec *QueryErrorCollector) As(target interface{}) bool {
  151. if target == nil {
  152. log.Errorf("ErrorCollection.As() target cannot be nil")
  153. return false
  154. }
  155. val := reflect.ValueOf(target)
  156. typ := val.Type()
  157. if typ.Kind() != reflect.Ptr || val.IsNil() {
  158. log.Errorf("ErrorCollection.As() target must be a non-nil pointer")
  159. return false
  160. }
  161. if e := typ.Elem(); e.Kind() != reflect.Interface && !e.Implements(errorType) {
  162. log.Errorf("ErrorCollection.As() *target must be interface or implement error")
  163. return false
  164. }
  165. targetType := typ.Elem()
  166. for _, err := range AllErrorsFor(ec) {
  167. if reflect.TypeOf(err).AssignableTo(targetType) {
  168. val.Elem().Set(reflect.ValueOf(err))
  169. return true
  170. }
  171. if x, ok := err.(interface{ As(interface{}) bool }); ok && x.As(target) {
  172. return true
  173. }
  174. }
  175. return false
  176. }
  177. // IsErrorCollection returns true if the provided error is an ErrorCollection
  178. func IsErrorCollection(err error) bool {
  179. _, ok := err.(QueryErrorCollection)
  180. return ok
  181. }
  182. func AllErrorsFor(collection QueryErrorCollection) []error {
  183. var errs []error
  184. for _, qe := range collection.Errors() {
  185. if qe.Error != nil {
  186. errs = append(errs, qe.Error)
  187. }
  188. if qe.ParseError != nil {
  189. errs = append(errs, qe.ParseError)
  190. }
  191. }
  192. return errs
  193. }
  194. // WrapError wraps the given error with the given message, usually for adding
  195. // context, but persists the existing type of error.
  196. func WrapError(err error, msg string) error {
  197. switch e := err.(type) {
  198. case CommError:
  199. return e.Wrap(msg)
  200. case NoDataError:
  201. return e.Wrap(msg)
  202. default:
  203. return fmt.Errorf("%s: %s", msg, err)
  204. }
  205. }
  206. // CommError describes an error communicating with Prometheus
  207. type CommError struct {
  208. messages []string
  209. }
  210. // NewCommError creates a new CommError
  211. func NewCommError(messages ...string) CommError {
  212. return CommError{messages: messages}
  213. }
  214. // IsCommError returns true if the given error is a CommError
  215. func IsCommError(err error) bool {
  216. _, ok := err.(CommError)
  217. return ok
  218. }
  219. // Error prints the error as a string
  220. func (pce CommError) Error() string {
  221. return fmt.Sprintf("Prometheus communication error: %s", strings.Join(pce.messages, ": "))
  222. }
  223. // Wrap wraps the error with the given message, but persists the error type.
  224. func (pce CommError) Wrap(message string) CommError {
  225. pce.messages = append([]string{message}, pce.messages...)
  226. return pce
  227. }
  228. // NoDataError indicates that no data was returned by Prometheus. This should
  229. // be treated like an EOF error, in that it may be expected.
  230. type NoDataError struct {
  231. messages []string
  232. }
  233. // NewNoDataError creates a new NoDataError
  234. func NewNoDataError(messages ...string) NoDataError {
  235. return NoDataError{messages: messages}
  236. }
  237. // IsNoDataError returns true if the given error is a NoDataError
  238. func IsNoDataError(err error) bool {
  239. _, ok := err.(NoDataError)
  240. return ok
  241. }
  242. // Error prints the error as a string
  243. func (nde NoDataError) Error() string {
  244. return fmt.Sprintf("No data error: %s", strings.Join(nde.messages, ": "))
  245. }
  246. // Wrap wraps the error with the given message, but persists the error type.
  247. func (nde NoDataError) Wrap(message string) NoDataError {
  248. nde.messages = append([]string{message}, nde.messages...)
  249. return nde
  250. }