GraphQL API and utilities for the rpdata project
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

143 lines
4.1 KiB

  1. package instrumentation
  2. import (
  3. "context"
  4. "time"
  5. "github.com/99designs/gqlgen/graphql"
  6. prometheusclient "github.com/prometheus/client_golang/prometheus"
  7. )
  8. const (
  9. existStatusFailure = "failure"
  10. exitStatusSuccess = "success"
  11. )
  12. var (
  13. requestStartedCounter prometheusclient.Counter
  14. requestCompletedCounter prometheusclient.Counter
  15. resolverStartedCounter *prometheusclient.CounterVec
  16. resolverCompletedCounter *prometheusclient.CounterVec
  17. timeToResolveField *prometheusclient.HistogramVec
  18. timeToHandleRequest *prometheusclient.HistogramVec
  19. )
  20. // Register registers the prometheus metrics.
  21. func Register() {
  22. requestStartedCounter = prometheusclient.NewCounter(
  23. prometheusclient.CounterOpts{
  24. Name: "graphql_request_started_total",
  25. Help: "Total number of requests started on the graphql server.",
  26. },
  27. )
  28. requestCompletedCounter = prometheusclient.NewCounter(
  29. prometheusclient.CounterOpts{
  30. Name: "graphql_request_completed_total",
  31. Help: "Total number of requests completed on the graphql server.",
  32. },
  33. )
  34. resolverStartedCounter = prometheusclient.NewCounterVec(
  35. prometheusclient.CounterOpts{
  36. Name: "graphql_resolver_started_total",
  37. Help: "Total number of resolver started on the graphql server.",
  38. },
  39. []string{"object", "field"},
  40. )
  41. resolverCompletedCounter = prometheusclient.NewCounterVec(
  42. prometheusclient.CounterOpts{
  43. Name: "graphql_resolver_completed_total",
  44. Help: "Total number of resolver completed on the graphql server.",
  45. },
  46. []string{"object", "field"},
  47. )
  48. timeToResolveField = prometheusclient.NewHistogramVec(prometheusclient.HistogramOpts{
  49. Name: "graphql_resolver_duration_ms",
  50. Help: "The time taken to resolve a field by graphql server.",
  51. Buckets: prometheusclient.ExponentialBuckets(1, 2, 11),
  52. }, []string{"exitStatus"})
  53. timeToHandleRequest = prometheusclient.NewHistogramVec(prometheusclient.HistogramOpts{
  54. Name: "graphql_request_duration_ms",
  55. Help: "The time taken to handle a request by graphql server.",
  56. Buckets: prometheusclient.ExponentialBuckets(1, 2, 11),
  57. }, []string{"exitStatus"})
  58. prometheusclient.MustRegister(
  59. requestStartedCounter,
  60. requestCompletedCounter,
  61. resolverStartedCounter,
  62. resolverCompletedCounter,
  63. timeToResolveField,
  64. timeToHandleRequest,
  65. )
  66. }
  67. // UnRegister unregisters
  68. func UnRegister() {
  69. prometheusclient.Unregister(requestStartedCounter)
  70. prometheusclient.Unregister(requestCompletedCounter)
  71. prometheusclient.Unregister(resolverStartedCounter)
  72. prometheusclient.Unregister(resolverCompletedCounter)
  73. prometheusclient.Unregister(timeToResolveField)
  74. prometheusclient.Unregister(timeToHandleRequest)
  75. }
  76. // ResolverMiddleware is a resolver middleware that logs metrics for prometheus.
  77. func ResolverMiddleware() graphql.FieldMiddleware {
  78. return func(ctx context.Context, next graphql.Resolver) (interface{}, error) {
  79. rctx := graphql.GetResolverContext(ctx)
  80. resolverStartedCounter.WithLabelValues(rctx.Object, rctx.Field.Name).Inc()
  81. observerStart := time.Now()
  82. res, err := next(ctx)
  83. var exitStatus string
  84. if err != nil {
  85. exitStatus = existStatusFailure
  86. } else {
  87. exitStatus = exitStatusSuccess
  88. }
  89. timeToResolveField.With(prometheusclient.Labels{"exitStatus": exitStatus}).
  90. Observe(float64(time.Since(observerStart).Nanoseconds() / int64(time.Millisecond)))
  91. resolverCompletedCounter.WithLabelValues(rctx.Object, rctx.Field.Name).Inc()
  92. return res, err
  93. }
  94. }
  95. // RequestMiddleware is a request middleware that logs metrics for prometheus.
  96. func RequestMiddleware() graphql.RequestMiddleware {
  97. return func(ctx context.Context, next func(ctx context.Context) []byte) []byte {
  98. requestStartedCounter.Inc()
  99. observerStart := time.Now()
  100. res := next(ctx)
  101. rctx := graphql.GetResolverContext(ctx)
  102. reqCtx := graphql.GetRequestContext(ctx)
  103. errList := reqCtx.GetErrors(rctx)
  104. var exitStatus string
  105. if len(errList) > 0 {
  106. exitStatus = existStatusFailure
  107. } else {
  108. exitStatus = exitStatusSuccess
  109. }
  110. timeToHandleRequest.With(prometheusclient.Labels{"exitStatus": exitStatus}).
  111. Observe(float64(time.Since(observerStart).Nanoseconds() / int64(time.Millisecond)))
  112. requestCompletedCounter.Inc()
  113. return res
  114. }
  115. }