diff --git a/examples/exemplars/main.go b/examples/exemplars/main.go index 798c2dfd0..b5fd3de85 100644 --- a/examples/exemplars/main.go +++ b/examples/exemplars/main.go @@ -14,6 +14,54 @@ // A simple example of how to record a latency metric with exemplars, using a fictional id // as a prometheus label. +package main + +import ( + "context" + "fmt" + "github.com/prometheus/client_golang/prometheus/collectors" + "github.com/prometheus/client_golang/prometheus/promhttp" + "log" + "math/rand" + "net/http" + "time" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/graphite" + "go.uber.org/zap" +) + +func main() { + logger, _ := zap.NewProduction() + defer logger.Sync() + + ctx := context.Background() + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + c := &Config{ + URL: "graphite.example.org:3099", + Gatherer: prometheus.DefaultGatherer, + Prefix: "prefix", + Interval: 5 * time.Second, + Timeout: 2 * time.Second, + ErrorHandling: testCase.errorHandling, + ErrorCallbackFunc: func(err error) { if err != nil { logger.Error("run", zap.Error(err)); cancel() } }, + } + + + b, err := graphite.NewBridge(c) + if err != nil { + t.Fatal(err) + } + + b.Run(ctx) +} + + + + + package main import ( diff --git a/prometheus/graphite/bridge.go b/prometheus/graphite/bridge.go index 84eac0de9..f0693c61d 100644 --- a/prometheus/graphite/bridge.go +++ b/prometheus/graphite/bridge.go @@ -79,6 +79,9 @@ type Config struct { // logged regardless of the configured ErrorHandling provided Logger // is not nil. ErrorHandling HandlerErrorHandling + + // ErrorCallbackFunc is a callback function that can be executed when error is occurred + ErrorCallbackFunc ErrorCallbackFunc } // Bridge pushes metrics to the configured Graphite server. @@ -89,8 +92,9 @@ type Bridge struct { interval time.Duration timeout time.Duration - errorHandling HandlerErrorHandling - logger Logger + errorHandling HandlerErrorHandling + errorCallbackFunc ErrorCallbackFunc + logger Logger g prometheus.Gatherer } @@ -102,6 +106,9 @@ type Logger interface { Println(v ...interface{}) } +// ErrorCallbackFunc is a special type for callback functions +type ErrorCallbackFunc func(error) + // NewBridge returns a pointer to a new Bridge struct. func NewBridge(c *Config) (*Bridge, error) { b := &Bridge{} @@ -142,6 +149,10 @@ func NewBridge(c *Config) (*Bridge, error) { b.errorHandling = c.ErrorHandling + if c.ErrorCallbackFunc != nil { + b.errorCallbackFunc = c.ErrorCallbackFunc + } + return b, nil } @@ -164,19 +175,29 @@ func (b *Bridge) Run(ctx context.Context) { // Push pushes Prometheus metrics to the configured Graphite server. func (b *Bridge) Push() error { - mfs, err := b.g.Gather() - if err != nil || len(mfs) == 0 { - switch b.errorHandling { - case AbortOnError: - return err - case ContinueOnError: - if b.logger != nil { - b.logger.Println("continue on error:", err) - } - default: - panic("unrecognized error handling value") + err := b.push() + if b.errorCallbackFunc != nil { + b.errorCallbackFunc(err) + } + switch b.errorHandling { + case AbortOnError: + return err + case ContinueOnError: + if b.logger != nil { + b.logger.Println("continue on error:", err) } } + return nil +} + +func (b *Bridge) push() error { + mfs, err := b.g.Gather() + if err != nil { + return err + } + if len(mfs) == 0 { + return nil + } conn, err := net.DialTimeout("tcp", b.url, b.timeout) if err != nil { diff --git a/prometheus/graphite/bridge_test.go b/prometheus/graphite/bridge_test.go index df0cfff3f..01f0079eb 100644 --- a/prometheus/graphite/bridge_test.go +++ b/prometheus/graphite/bridge_test.go @@ -467,3 +467,43 @@ func ExampleBridge() { // Start pushing metrics to Graphite in the Run() loop. b.Run(ctx) } + +func TestErrorHandling(t *testing.T) { + var testCases = []struct { + errorHandling HandlerErrorHandling + receivedError error + interceptedError error + }{ + { + errorHandling: ContinueOnError, + receivedError: nil, + interceptedError: &net.OpError{}, + }, + { + errorHandling: AbortOnError, + receivedError: &net.OpError{}, + interceptedError: &net.OpError{}, + }, + } + + for _, testCase := range testCases { + var interceptedError error + c := &Config{ + URL: "localhost", + ErrorHandling: testCase.errorHandling, + ErrorCallbackFunc: func(err error) { interceptedError = err }, + } + b, err := NewBridge(c) + if err != nil { + t.Fatal(err) + } + + receivedError := b.Push() + if reflect.TypeOf(receivedError) != reflect.TypeOf(testCase.receivedError) { + t.Errorf("expected to receive: %T, received: %T", testCase.receivedError, receivedError) + } + if reflect.TypeOf(interceptedError) != reflect.TypeOf(testCase.interceptedError) { + t.Errorf("expected to intercept: %T, intercepted: %T", testCase.interceptedError, interceptedError) + } + } +}