chore: import upstream snapshot with attribution
govulncheck / govulncheck (push) Has been cancelled
Lint / golangci-lint (push) Has been cancelled
Run Tests / Unit Tests (push) Has been cancelled
Run Tests / Etcd Integration Tests (push) Has been cancelled
Harness (E2E) / Harnesses (mock LLM) (push) Has been cancelled
Harness (E2E) / Provider harnesses (live LLM conformance) (push) Has been cancelled
govulncheck / govulncheck (push) Has been cancelled
Lint / golangci-lint (push) Has been cancelled
Run Tests / Unit Tests (push) Has been cancelled
Run Tests / Etcd Integration Tests (push) Has been cancelled
Harness (E2E) / Harnesses (mock LLM) (push) Has been cancelled
Harness (E2E) / Provider harnesses (live LLM conformance) (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
# Prometheus Wrapper
|
||||
|
||||
The `prometheus` wrapper package exposes standard request metrics (request
|
||||
count, latency, errors) for go-micro services and clients, so they can be
|
||||
scraped by a Prometheus server with zero extra boilerplate.
|
||||
|
||||
Resolves [micro/go-micro#2893](https://github.com/micro/go-micro/issues/2893).
|
||||
|
||||
## Installation
|
||||
|
||||
```go
|
||||
import prom "go-micro.dev/v5/wrapper/monitoring/prometheus"
|
||||
```
|
||||
|
||||
## Exported Metrics
|
||||
|
||||
All metrics are labelled with `service`, `endpoint` and `status`
|
||||
(`"success"` or `"fail"`). Labels are kept small on purpose to avoid
|
||||
blowing up Prometheus memory.
|
||||
|
||||
| Metric | Type | Description |
|
||||
|---------------------------------|-----------|---------------------------------------------|
|
||||
| `micro_request_total` | Counter | Total number of requests handled. |
|
||||
| `micro_request_duration_seconds`| Histogram | Request latency distribution (seconds). |
|
||||
|
||||
The `micro` prefix can be overridden with `prom.ServiceName("myapp")`.
|
||||
|
||||
## Basic Usage
|
||||
|
||||
```go
|
||||
import (
|
||||
"go-micro.dev/v5"
|
||||
prom "go-micro.dev/v5/wrapper/monitoring/prometheus"
|
||||
)
|
||||
|
||||
func main() {
|
||||
service := micro.NewService(
|
||||
micro.Name("example.service"),
|
||||
micro.WrapHandler(prom.NewHandlerWrapper()),
|
||||
micro.WrapClient(prom.NewClientWrapper()),
|
||||
micro.WrapSubscriber(prom.NewSubscriberWrapper()),
|
||||
)
|
||||
|
||||
service.Init()
|
||||
|
||||
if err := service.Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
To expose the metrics to Prometheus, serve the default `promhttp` handler
|
||||
on a side HTTP endpoint:
|
||||
|
||||
```go
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
)
|
||||
|
||||
go func() {
|
||||
http.Handle("/metrics", promhttp.Handler())
|
||||
_ = http.ListenAndServe(":9100", nil)
|
||||
}()
|
||||
```
|
||||
|
||||
Then point Prometheus at it:
|
||||
|
||||
```yaml
|
||||
scrape_configs:
|
||||
- job_name: 'example.service'
|
||||
static_configs:
|
||||
- targets: ['localhost:9100']
|
||||
```
|
||||
|
||||
## Wrappers
|
||||
|
||||
| Constructor | Wraps | Notes |
|
||||
|---------------------------|-------------------------|--------------------------------------------|
|
||||
| `NewHandlerWrapper` | `server.HandlerWrapper` | Incoming RPC handlers. |
|
||||
| `NewSubscriberWrapper` | `server.SubscriberWrapper` | Event subscribers (uses topic as endpoint). |
|
||||
| `NewCallWrapper` | `client.CallWrapper` | Outgoing unary RPC calls only. |
|
||||
| `NewClientWrapper` | `client.Wrapper` | Outgoing `Call` **and** `Publish`. |
|
||||
|
||||
`NewClientWrapper` is the right choice when you want metrics for both
|
||||
`Call` and `Publish`; use `NewCallWrapper` if you only care about unary
|
||||
calls and want lower overhead.
|
||||
|
||||
## Configuration
|
||||
|
||||
All constructors accept functional options:
|
||||
|
||||
```go
|
||||
prom.NewHandlerWrapper(
|
||||
prom.ServiceName("myapp"), // metric name prefix
|
||||
prom.Namespace("prod"), // Prometheus namespace
|
||||
prom.Subsystem("api"), // Prometheus subsystem
|
||||
prom.ConstLabels(prometheus.Labels{"dc": "eu-1"}), // labels on every metric
|
||||
prom.Buckets([]float64{0.005, 0.05, 0.5, 1, 5}), // latency buckets
|
||||
prom.Registerer(myRegistry), // custom registerer
|
||||
)
|
||||
```
|
||||
|
||||
Defaults:
|
||||
|
||||
- `ServiceName`: `"micro"`
|
||||
- `Buckets`: `prometheus.DefBuckets`
|
||||
- `Registerer`: `prometheus.DefaultRegisterer`
|
||||
|
||||
## Reusing Collectors
|
||||
|
||||
Creating multiple wrappers with the same options (e.g. `NewHandlerWrapper`
|
||||
and `NewClientWrapper` together) is safe: the collectors are cached per
|
||||
`(name, namespace, subsystem)` triple and `AlreadyRegisteredError` from
|
||||
Prometheus is handled transparently, so the existing collector is reused.
|
||||
|
||||
## Testing
|
||||
|
||||
The package ships with unit tests that use a fresh `prometheus.Registry`
|
||||
per test to keep assertions isolated:
|
||||
|
||||
```bash
|
||||
go test ./wrapper/monitoring/prometheus/...
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
Apache 2.0
|
||||
@@ -0,0 +1,95 @@
|
||||
package prometheus
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
// metrics bundles the counters/histograms used by the wrappers.
|
||||
// A metrics value is keyed by (name + namespace + subsystem) so that
|
||||
// multiple wrappers created with the same options share the same
|
||||
// collectors instead of failing with AlreadyRegisteredError.
|
||||
type metrics struct {
|
||||
requestTotal *prometheus.CounterVec
|
||||
requestDuration *prometheus.HistogramVec
|
||||
}
|
||||
|
||||
// metricLabels are the labels we use on every metric. They intentionally
|
||||
// stay on a small, low-cardinality set: high-cardinality labels (e.g. the
|
||||
// full request body) must not end up in Prometheus.
|
||||
var metricLabels = []string{"service", "endpoint", "status"}
|
||||
|
||||
var (
|
||||
metricsMu sync.Mutex
|
||||
metricsCache = map[string]*metrics{}
|
||||
)
|
||||
|
||||
// getMetrics returns a cached metrics bundle for the given options, creating
|
||||
// and registering it on first use. Collectors that were already registered
|
||||
// on the underlying Registerer (e.g. because a user constructed two wrappers
|
||||
// with identical options) are reused transparently.
|
||||
func getMetrics(opts Options) *metrics {
|
||||
key := opts.Name + "\x00" + opts.Namespace + "\x00" + opts.Subsystem
|
||||
|
||||
metricsMu.Lock()
|
||||
defer metricsMu.Unlock()
|
||||
|
||||
if m, ok := metricsCache[key]; ok {
|
||||
return m
|
||||
}
|
||||
|
||||
counter := prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Namespace: opts.Namespace,
|
||||
Subsystem: opts.Subsystem,
|
||||
Name: opts.Name + "_request_total",
|
||||
Help: "How many go-micro requests processed, partitioned by service, endpoint and status.",
|
||||
ConstLabels: opts.ConstLabels,
|
||||
},
|
||||
metricLabels,
|
||||
)
|
||||
|
||||
histogram := prometheus.NewHistogramVec(
|
||||
prometheus.HistogramOpts{
|
||||
Namespace: opts.Namespace,
|
||||
Subsystem: opts.Subsystem,
|
||||
Name: opts.Name + "_request_duration_seconds",
|
||||
Help: "Histogram of go-micro request latencies in seconds, partitioned by service, endpoint and status.",
|
||||
ConstLabels: opts.ConstLabels,
|
||||
Buckets: opts.Buckets,
|
||||
},
|
||||
metricLabels,
|
||||
)
|
||||
|
||||
m := &metrics{
|
||||
requestTotal: register(opts.Registerer, counter).(*prometheus.CounterVec),
|
||||
requestDuration: register(opts.Registerer, histogram).(*prometheus.HistogramVec),
|
||||
}
|
||||
metricsCache[key] = m
|
||||
return m
|
||||
}
|
||||
|
||||
// register registers c on r. If an identical collector is already registered
|
||||
// (AlreadyRegisteredError), the existing collector is returned so that the
|
||||
// wrapper can be constructed more than once without panicking.
|
||||
func register(r prometheus.Registerer, c prometheus.Collector) prometheus.Collector {
|
||||
if err := r.Register(c); err != nil {
|
||||
if are, ok := err.(prometheus.AlreadyRegisteredError); ok {
|
||||
return are.ExistingCollector
|
||||
}
|
||||
// Any other registration error is a programming mistake (e.g.
|
||||
// inconsistent label dimensions) and should surface loudly.
|
||||
panic(err)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// status returns "success" or "fail" depending on whether err is nil.
|
||||
// Using a fixed, low-cardinality set keeps Prometheus memory bounded.
|
||||
func status(err error) string {
|
||||
if err != nil {
|
||||
return "fail"
|
||||
}
|
||||
return "success"
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
// Package prometheus provides a go-micro wrapper that exposes standard
|
||||
// request/response metrics (request count, latency, errors) to Prometheus.
|
||||
package prometheus
|
||||
|
||||
import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
// Options holds configuration for the Prometheus wrapper.
|
||||
type Options struct {
|
||||
// Name is the metric name prefix (Prometheus "name").
|
||||
// Default: "micro".
|
||||
Name string
|
||||
|
||||
// Namespace for the Prometheus metrics.
|
||||
// Default: "" (empty).
|
||||
Namespace string
|
||||
|
||||
// Subsystem for the Prometheus metrics.
|
||||
// Default: "" (empty).
|
||||
Subsystem string
|
||||
|
||||
// ConstLabels are labels applied to every metric.
|
||||
ConstLabels prometheus.Labels
|
||||
|
||||
// Buckets defines the histogram buckets (seconds) for latency.
|
||||
// When nil, prometheus.DefBuckets is used.
|
||||
Buckets []float64
|
||||
|
||||
// Registerer is used to register the metrics.
|
||||
// When nil, prometheus.DefaultRegisterer is used.
|
||||
Registerer prometheus.Registerer
|
||||
}
|
||||
|
||||
// Option applies a single configuration value.
|
||||
type Option func(*Options)
|
||||
|
||||
// ServiceName sets the metric name prefix (Prometheus "name").
|
||||
func ServiceName(name string) Option {
|
||||
return func(o *Options) {
|
||||
o.Name = name
|
||||
}
|
||||
}
|
||||
|
||||
// Namespace sets the Prometheus namespace for metrics.
|
||||
func Namespace(namespace string) Option {
|
||||
return func(o *Options) {
|
||||
o.Namespace = namespace
|
||||
}
|
||||
}
|
||||
|
||||
// Subsystem sets the Prometheus subsystem for metrics.
|
||||
func Subsystem(subsystem string) Option {
|
||||
return func(o *Options) {
|
||||
o.Subsystem = subsystem
|
||||
}
|
||||
}
|
||||
|
||||
// ConstLabels sets labels applied to every metric.
|
||||
func ConstLabels(labels prometheus.Labels) Option {
|
||||
return func(o *Options) {
|
||||
o.ConstLabels = labels
|
||||
}
|
||||
}
|
||||
|
||||
// Buckets sets the histogram buckets (in seconds) for latency metrics.
|
||||
func Buckets(buckets []float64) Option {
|
||||
return func(o *Options) {
|
||||
o.Buckets = buckets
|
||||
}
|
||||
}
|
||||
|
||||
// Registerer sets the Prometheus registerer used to register metrics.
|
||||
// When unset, prometheus.DefaultRegisterer is used.
|
||||
func Registerer(r prometheus.Registerer) Option {
|
||||
return func(o *Options) {
|
||||
o.Registerer = r
|
||||
}
|
||||
}
|
||||
|
||||
// newOptions builds Options from the provided Option functions, applying
|
||||
// sensible defaults.
|
||||
func newOptions(opts ...Option) Options {
|
||||
options := Options{
|
||||
Name: "micro",
|
||||
Buckets: prometheus.DefBuckets,
|
||||
Registerer: prometheus.DefaultRegisterer,
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
if options.Registerer == nil {
|
||||
options.Registerer = prometheus.DefaultRegisterer
|
||||
}
|
||||
if len(options.Buckets) == 0 {
|
||||
options.Buckets = prometheus.DefBuckets
|
||||
}
|
||||
return options
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package prometheus
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"go-micro.dev/v6/client"
|
||||
"go-micro.dev/v6/registry"
|
||||
"go-micro.dev/v6/server"
|
||||
)
|
||||
|
||||
// NewHandlerWrapper returns a server.HandlerWrapper that records Prometheus
|
||||
// metrics (request count and latency) for every incoming RPC handled by the
|
||||
// server.
|
||||
func NewHandlerWrapper(opts ...Option) server.HandlerWrapper {
|
||||
m := getMetrics(newOptions(opts...))
|
||||
return func(fn server.HandlerFunc) server.HandlerFunc {
|
||||
return func(ctx context.Context, req server.Request, rsp interface{}) error {
|
||||
start := time.Now()
|
||||
err := fn(ctx, req, rsp)
|
||||
observe(m, req.Service(), req.Endpoint(), err, start)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewSubscriberWrapper returns a server.SubscriberWrapper that records
|
||||
// Prometheus metrics for every message delivered to a subscriber.
|
||||
func NewSubscriberWrapper(opts ...Option) server.SubscriberWrapper {
|
||||
m := getMetrics(newOptions(opts...))
|
||||
return func(fn server.SubscriberFunc) server.SubscriberFunc {
|
||||
return func(ctx context.Context, msg server.Message) error {
|
||||
start := time.Now()
|
||||
err := fn(ctx, msg)
|
||||
observe(m, "subscriber", msg.Topic(), err, start)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewCallWrapper returns a client.CallWrapper that records Prometheus
|
||||
// metrics for every outgoing RPC issued by the client.
|
||||
func NewCallWrapper(opts ...Option) client.CallWrapper {
|
||||
m := getMetrics(newOptions(opts...))
|
||||
return func(fn client.CallFunc) client.CallFunc {
|
||||
return func(ctx context.Context, node *registry.Node, req client.Request, rsp interface{}, opts client.CallOptions) error {
|
||||
start := time.Now()
|
||||
err := fn(ctx, node, req, rsp, opts)
|
||||
observe(m, req.Service(), req.Endpoint(), err, start)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewClientWrapper returns a client.Wrapper that records Prometheus metrics
|
||||
// for every Call and Publish issued on the wrapped client. Use it when you
|
||||
// need metrics for Publish as well as Call; NewCallWrapper only covers Call.
|
||||
func NewClientWrapper(opts ...Option) client.Wrapper {
|
||||
m := getMetrics(newOptions(opts...))
|
||||
return func(c client.Client) client.Client {
|
||||
return &clientWrapper{Client: c, metrics: m}
|
||||
}
|
||||
}
|
||||
|
||||
type clientWrapper struct {
|
||||
client.Client
|
||||
metrics *metrics
|
||||
}
|
||||
|
||||
func (w *clientWrapper) Call(ctx context.Context, req client.Request, rsp interface{}, opts ...client.CallOption) error {
|
||||
start := time.Now()
|
||||
err := w.Client.Call(ctx, req, rsp, opts...)
|
||||
observe(w.metrics, req.Service(), req.Endpoint(), err, start)
|
||||
return err
|
||||
}
|
||||
|
||||
func (w *clientWrapper) Publish(ctx context.Context, p client.Message, opts ...client.PublishOption) error {
|
||||
start := time.Now()
|
||||
err := w.Client.Publish(ctx, p, opts...)
|
||||
observe(w.metrics, "publisher", p.Topic(), err, start)
|
||||
return err
|
||||
}
|
||||
|
||||
// observe records a single request into the counter and histogram.
|
||||
func observe(m *metrics, service, endpoint string, err error, start time.Time) {
|
||||
st := status(err)
|
||||
m.requestTotal.WithLabelValues(service, endpoint, st).Inc()
|
||||
m.requestDuration.WithLabelValues(service, endpoint, st).Observe(time.Since(start).Seconds())
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
package prometheus
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
dto "github.com/prometheus/client_model/go"
|
||||
"go-micro.dev/v6/client"
|
||||
"go-micro.dev/v6/codec"
|
||||
"go-micro.dev/v6/registry"
|
||||
"go-micro.dev/v6/server"
|
||||
)
|
||||
|
||||
// mockServerRequest is a minimal implementation of server.Request.
|
||||
type mockServerRequest struct {
|
||||
service string
|
||||
endpoint string
|
||||
}
|
||||
|
||||
func (r *mockServerRequest) Service() string { return r.service }
|
||||
func (r *mockServerRequest) Method() string { return r.endpoint }
|
||||
func (r *mockServerRequest) Endpoint() string { return r.endpoint }
|
||||
func (r *mockServerRequest) ContentType() string { return "" }
|
||||
func (r *mockServerRequest) Header() map[string]string { return nil }
|
||||
func (r *mockServerRequest) Body() interface{} { return nil }
|
||||
func (r *mockServerRequest) Read() ([]byte, error) { return nil, nil }
|
||||
func (r *mockServerRequest) Codec() codec.Reader { return nil }
|
||||
func (r *mockServerRequest) Stream() bool { return false }
|
||||
|
||||
// mockMessage implements server.Message for subscriber tests.
|
||||
type mockMessage struct {
|
||||
topic string
|
||||
}
|
||||
|
||||
func (m *mockMessage) Topic() string { return m.topic }
|
||||
func (m *mockMessage) Payload() interface{} { return nil }
|
||||
func (m *mockMessage) ContentType() string { return "" }
|
||||
func (m *mockMessage) Header() map[string]string { return nil }
|
||||
func (m *mockMessage) Body() []byte { return nil }
|
||||
func (m *mockMessage) Codec() codec.Reader { return nil }
|
||||
|
||||
// mockClientRequest is a minimal implementation of client.Request.
|
||||
type mockClientRequest struct {
|
||||
service string
|
||||
endpoint string
|
||||
}
|
||||
|
||||
func (r *mockClientRequest) Service() string { return r.service }
|
||||
func (r *mockClientRequest) Method() string { return r.endpoint }
|
||||
func (r *mockClientRequest) Endpoint() string { return r.endpoint }
|
||||
func (r *mockClientRequest) ContentType() string { return "" }
|
||||
func (r *mockClientRequest) Body() interface{} { return nil }
|
||||
func (r *mockClientRequest) Codec() codec.Writer { return nil }
|
||||
func (r *mockClientRequest) Stream() bool { return false }
|
||||
|
||||
// isolatedOpts returns wrapper options pinned to a fresh registry so each
|
||||
// test starts with its own counters. We also vary Name so cached metrics
|
||||
// bundles don't bleed between tests.
|
||||
func isolatedOpts(name string) []Option {
|
||||
reg := prometheus.NewRegistry()
|
||||
return []Option{ServiceName(name), Registerer(reg)}
|
||||
}
|
||||
|
||||
func counterValue(t *testing.T, vec *prometheus.CounterVec, labels ...string) float64 {
|
||||
t.Helper()
|
||||
c, err := vec.GetMetricWithLabelValues(labels...)
|
||||
if err != nil {
|
||||
t.Fatalf("GetMetricWithLabelValues: %v", err)
|
||||
}
|
||||
var m dto.Metric
|
||||
if err := c.Write(&m); err != nil {
|
||||
t.Fatalf("Write: %v", err)
|
||||
}
|
||||
return m.GetCounter().GetValue()
|
||||
}
|
||||
|
||||
func histogramCount(t *testing.T, vec *prometheus.HistogramVec, labels ...string) uint64 {
|
||||
t.Helper()
|
||||
obs, err := vec.GetMetricWithLabelValues(labels...)
|
||||
if err != nil {
|
||||
t.Fatalf("GetMetricWithLabelValues: %v", err)
|
||||
}
|
||||
h, ok := obs.(prometheus.Histogram)
|
||||
if !ok {
|
||||
t.Fatalf("expected Histogram, got %T", obs)
|
||||
}
|
||||
var m dto.Metric
|
||||
if err := h.Write(&m); err != nil {
|
||||
t.Fatalf("Write: %v", err)
|
||||
}
|
||||
return m.GetHistogram().GetSampleCount()
|
||||
}
|
||||
|
||||
func TestHandlerWrapperSuccess(t *testing.T) {
|
||||
opts := isolatedOpts("test_handler_success")
|
||||
wrap := NewHandlerWrapper(opts...)
|
||||
|
||||
called := false
|
||||
handler := wrap(func(ctx context.Context, req server.Request, rsp interface{}) error {
|
||||
called = true
|
||||
return nil
|
||||
})
|
||||
|
||||
err := handler(context.Background(), &mockServerRequest{service: "svc", endpoint: "Foo.Bar"}, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("handler returned error: %v", err)
|
||||
}
|
||||
if !called {
|
||||
t.Fatal("inner handler was not called")
|
||||
}
|
||||
|
||||
m := getMetrics(newOptions(opts...))
|
||||
if got := counterValue(t, m.requestTotal, "svc", "Foo.Bar", "success"); got != 1 {
|
||||
t.Errorf("success counter = %v, want 1", got)
|
||||
}
|
||||
if got := histogramCount(t, m.requestDuration, "svc", "Foo.Bar", "success"); got != 1 {
|
||||
t.Errorf("histogram count = %v, want 1", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerWrapperFailure(t *testing.T) {
|
||||
opts := isolatedOpts("test_handler_failure")
|
||||
wrap := NewHandlerWrapper(opts...)
|
||||
|
||||
boom := errors.New("boom")
|
||||
handler := wrap(func(ctx context.Context, req server.Request, rsp interface{}) error {
|
||||
return boom
|
||||
})
|
||||
|
||||
err := handler(context.Background(), &mockServerRequest{service: "svc", endpoint: "Foo.Bar"}, nil)
|
||||
if !errors.Is(err, boom) {
|
||||
t.Fatalf("error not propagated, got %v", err)
|
||||
}
|
||||
|
||||
m := getMetrics(newOptions(opts...))
|
||||
if got := counterValue(t, m.requestTotal, "svc", "Foo.Bar", "fail"); got != 1 {
|
||||
t.Errorf("fail counter = %v, want 1", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubscriberWrapper(t *testing.T) {
|
||||
opts := isolatedOpts("test_subscriber")
|
||||
wrap := NewSubscriberWrapper(opts...)
|
||||
|
||||
sub := wrap(func(ctx context.Context, msg server.Message) error {
|
||||
return nil
|
||||
})
|
||||
|
||||
if err := sub(context.Background(), &mockMessage{topic: "events"}); err != nil {
|
||||
t.Fatalf("subscriber returned error: %v", err)
|
||||
}
|
||||
|
||||
m := getMetrics(newOptions(opts...))
|
||||
if got := counterValue(t, m.requestTotal, "subscriber", "events", "success"); got != 1 {
|
||||
t.Errorf("subscriber counter = %v, want 1", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCallWrapperSuccess(t *testing.T) {
|
||||
opts := isolatedOpts("test_call")
|
||||
wrap := NewCallWrapper(opts...)
|
||||
|
||||
cf := wrap(func(ctx context.Context, node *registry.Node, req client.Request, rsp interface{}, opts client.CallOptions) error {
|
||||
return nil
|
||||
})
|
||||
|
||||
err := cf(context.Background(), ®istry.Node{}, &mockClientRequest{service: "svc", endpoint: "Foo.Bar"}, nil, client.CallOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("call returned error: %v", err)
|
||||
}
|
||||
|
||||
m := getMetrics(newOptions(opts...))
|
||||
if got := counterValue(t, m.requestTotal, "svc", "Foo.Bar", "success"); got != 1 {
|
||||
t.Errorf("call counter = %v, want 1", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegisterAlreadyRegistered(t *testing.T) {
|
||||
// Creating two wrappers against the same Registerer with the same
|
||||
// options must not panic, and the second one must reuse the already
|
||||
// registered collector.
|
||||
reg := prometheus.NewRegistry()
|
||||
opts := []Option{ServiceName("test_dup"), Registerer(reg)}
|
||||
|
||||
_ = NewHandlerWrapper(opts...)
|
||||
_ = NewHandlerWrapper(opts...)
|
||||
|
||||
// After resetting the cache we must still not panic, which proves the
|
||||
// AlreadyRegisteredError branch in register() is exercised.
|
||||
metricsMu.Lock()
|
||||
delete(metricsCache, "test_dup\x00\x00")
|
||||
metricsMu.Unlock()
|
||||
|
||||
_ = NewHandlerWrapper(opts...)
|
||||
}
|
||||
|
||||
func TestStatusHelper(t *testing.T) {
|
||||
if status(nil) != "success" {
|
||||
t.Error("nil error should yield success")
|
||||
}
|
||||
if status(errors.New("x")) != "fail" {
|
||||
t.Error("non-nil error should yield fail")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user