141 lines
4.8 KiB
Go
141 lines
4.8 KiB
Go
package metrics
|
|
|
|
import (
|
|
"github.com/ThreeDotsLabs/watermill/internal"
|
|
"github.com/ThreeDotsLabs/watermill/message"
|
|
"github.com/pkg/errors"
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
)
|
|
|
|
type PrometheusMetricsBuilderConfig struct {
|
|
Namespace string
|
|
Subsystem string
|
|
AdditionalLabels []MetricLabel
|
|
}
|
|
|
|
func NewPrometheusMetricsBuilderWithConfig(prometheusRegistry prometheus.Registerer, config PrometheusMetricsBuilderConfig) PrometheusMetricsBuilder {
|
|
builder := PrometheusMetricsBuilder{
|
|
Namespace: config.Namespace,
|
|
Subsystem: config.Subsystem,
|
|
PrometheusRegistry: prometheusRegistry,
|
|
additionalLabels: config.AdditionalLabels,
|
|
}
|
|
return builder
|
|
}
|
|
|
|
func NewPrometheusMetricsBuilder(prometheusRegistry prometheus.Registerer, namespace string, subsystem string) PrometheusMetricsBuilder {
|
|
return NewPrometheusMetricsBuilderWithConfig(prometheusRegistry, PrometheusMetricsBuilderConfig{
|
|
Namespace: namespace,
|
|
Subsystem: subsystem,
|
|
})
|
|
}
|
|
|
|
// PrometheusMetricsBuilder provides methods to decorate publishers, subscribers and handlers.
|
|
type PrometheusMetricsBuilder struct {
|
|
// PrometheusRegistry may be filled with a pre-existing Prometheus registry, or left empty for the default registry.
|
|
PrometheusRegistry prometheus.Registerer
|
|
|
|
Namespace string
|
|
Subsystem string
|
|
// PublishBuckets defines the histogram buckets for publish time histogram, defaulted if nil.
|
|
PublishBuckets []float64
|
|
// HandlerBuckets defines the histogram buckets for handle execution time histogram, defaulted to watermill's default.
|
|
HandlerBuckets []float64
|
|
|
|
additionalLabels []MetricLabel
|
|
}
|
|
|
|
// AddPrometheusRouterMetrics is a convenience function that acts on the message router to add the metrics middleware
|
|
// to all its handlers. The handlers' publishers and subscribers are also decorated.
|
|
// The default buckets are used for the handler execution time histogram (use your own provisioning
|
|
// with NewRouterMiddlewareWithConfig if needed).
|
|
func (b PrometheusMetricsBuilder) AddPrometheusRouterMetrics(r *message.Router) {
|
|
r.AddPublisherDecorators(b.DecoratePublisher)
|
|
r.AddSubscriberDecorators(b.DecorateSubscriber)
|
|
r.AddMiddleware(b.NewRouterMiddleware().Middleware)
|
|
}
|
|
|
|
// DecoratePublisher wraps the underlying publisher with Prometheus metrics.
|
|
func (b PrometheusMetricsBuilder) DecoratePublisher(pub message.Publisher) (message.Publisher, error) {
|
|
var err error
|
|
d := PublisherPrometheusMetricsDecorator{
|
|
pub: pub,
|
|
publisherName: internal.StructName(pub),
|
|
additionalLabels: b.additionalLabels,
|
|
}
|
|
|
|
d.publishTimeSeconds, err = b.registerHistogramVec(prometheus.NewHistogramVec(
|
|
prometheus.HistogramOpts{
|
|
Namespace: b.Namespace,
|
|
Subsystem: b.Subsystem,
|
|
Name: "publish_time_seconds",
|
|
Help: "The time that a publishing attempt (success or not) took in seconds",
|
|
Buckets: b.PublishBuckets,
|
|
},
|
|
toLabelsSlice(publisherLabelKeys, b.additionalLabels),
|
|
))
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "could not register publish time metric")
|
|
}
|
|
return d, nil
|
|
}
|
|
|
|
// DecorateSubscriber wraps the underlying subscriber with Prometheus metrics.
|
|
func (b PrometheusMetricsBuilder) DecorateSubscriber(sub message.Subscriber) (message.Subscriber, error) {
|
|
var err error
|
|
d := &SubscriberPrometheusMetricsDecorator{
|
|
closing: make(chan struct{}),
|
|
subscriberName: internal.StructName(sub),
|
|
additionalLabels: b.additionalLabels,
|
|
}
|
|
|
|
d.subscriberMessagesReceivedTotal, err = b.registerCounterVec(prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Namespace: b.Namespace,
|
|
Subsystem: b.Subsystem,
|
|
Name: "subscriber_messages_received_total",
|
|
Help: "The total number of messages received by the subscriber",
|
|
},
|
|
toLabelsSlice(append(subscriberLabelKeys, labelAcked), b.additionalLabels),
|
|
))
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "could not register time to ack metric")
|
|
}
|
|
|
|
d.Subscriber, err = message.MessageTransformSubscriberDecorator(d.recordMetrics)(sub)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "could not decorate subscriber with metrics decorator")
|
|
}
|
|
|
|
return d, nil
|
|
}
|
|
|
|
func (b PrometheusMetricsBuilder) register(c prometheus.Collector) (prometheus.Collector, error) {
|
|
err := b.PrometheusRegistry.Register(c)
|
|
if err == nil {
|
|
return c, nil
|
|
}
|
|
|
|
if are, ok := err.(prometheus.AlreadyRegisteredError); ok {
|
|
return are.ExistingCollector, nil
|
|
}
|
|
|
|
return nil, err
|
|
}
|
|
|
|
func (b PrometheusMetricsBuilder) registerCounterVec(c *prometheus.CounterVec) (*prometheus.CounterVec, error) {
|
|
col, err := b.register(c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return col.(*prometheus.CounterVec), nil
|
|
}
|
|
|
|
func (b PrometheusMetricsBuilder) registerHistogramVec(h *prometheus.HistogramVec) (*prometheus.HistogramVec, error) {
|
|
col, err := b.register(h)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return col.(*prometheus.HistogramVec), nil
|
|
}
|