Skip to content

Commit 40fae87

Browse files
committed
added support backward compatibility
1 parent 252ec74 commit 40fae87

File tree

2 files changed

+115
-40
lines changed

2 files changed

+115
-40
lines changed

prometheus/graphite/bridge.go

+67-10
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,22 @@ const (
3838
millisecondsPerSecond = 1000
3939
)
4040

41-
// ErrorHandler is a function that handles errors
42-
type ErrorHandler func(err error)
41+
// HandlerErrorHandling defines how a Handler serving metrics will handle
42+
// errors.
43+
type HandlerErrorHandling int
4344

44-
// DefaultErrorHandler skips received errors
45-
var DefaultErrorHandler = func(err error) {}
45+
// These constants cause handlers serving metrics to behave as described if
46+
// errors are encountered.
47+
const (
48+
// Ignore errors and try to push as many metrics to Graphite as possible.
49+
ContinueOnError HandlerErrorHandling = iota
50+
51+
// Abort the push to Graphite upon the first error encountered.
52+
AbortOnError
53+
54+
// Execute callback function on error.
55+
CallbackOnError
56+
)
4657

4758
// Config defines the Graphite bridge config.
4859
type Config struct {
@@ -64,8 +75,16 @@ type Config struct {
6475
// The Gatherer to use for metrics. Defaults to prometheus.DefaultGatherer.
6576
Gatherer prometheus.Gatherer
6677

67-
// ErrorHandler defines how errors are handled.
68-
ErrorHandler ErrorHandler
78+
// The logger that messages are written to. Defaults to no logging.
79+
Logger Logger
80+
81+
// ErrorHandling defines how errors are handled. Note that errors are
82+
// logged regardless of the configured ErrorHandling provided Logger
83+
// is not nil.
84+
ErrorHandling HandlerErrorHandling
85+
86+
// ErrorCallbackFunc is a callback function that can be executed when error is occurred
87+
ErrorCallbackFunc CallbackFunc
6988
}
7089

7190
// Bridge pushes metrics to the configured Graphite server.
@@ -76,11 +95,23 @@ type Bridge struct {
7695
interval time.Duration
7796
timeout time.Duration
7897

79-
errorHandler ErrorHandler
98+
errorHandling HandlerErrorHandling
99+
errorCallbackFunc CallbackFunc
100+
logger Logger
80101

81102
g prometheus.Gatherer
82103
}
83104

105+
// Logger is the minimal interface Bridge needs for logging. Note that
106+
// log.Logger from the standard library implements this interface, and it is
107+
// easy to implement by custom loggers, if they don't do so already anyway.
108+
type Logger interface {
109+
Println(v ...interface{})
110+
}
111+
112+
// CallbackFunc is a special type for callback functions
113+
type CallbackFunc func(error)
114+
84115
// NewBridge returns a pointer to a new Bridge struct.
85116
func NewBridge(c *Config) (*Bridge, error) {
86117
b := &Bridge{}
@@ -98,6 +129,10 @@ func NewBridge(c *Config) (*Bridge, error) {
98129
b.g = c.Gatherer
99130
}
100131

132+
if c.Logger != nil {
133+
b.logger = c.Logger
134+
}
135+
101136
if c.Prefix != "" {
102137
b.prefix = c.Prefix
103138
}
@@ -115,7 +150,11 @@ func NewBridge(c *Config) (*Bridge, error) {
115150
b.timeout = c.Timeout
116151
}
117152

118-
b.errorHandler = c.ErrorHandler
153+
b.errorHandling = c.ErrorHandling
154+
155+
if c.ErrorCallbackFunc != nil {
156+
b.errorCallbackFunc = c.ErrorCallbackFunc
157+
}
119158

120159
return b, nil
121160
}
@@ -128,7 +167,9 @@ func (b *Bridge) Run(ctx context.Context) {
128167
for {
129168
select {
130169
case <-ticker.C:
131-
b.errorHandler(b.Push())
170+
if err := b.Push(); err != nil && b.logger != nil {
171+
b.logger.Println("error pushing to Graphite:", err)
172+
}
132173
case <-ctx.Done():
133174
return
134175
}
@@ -137,11 +178,27 @@ func (b *Bridge) Run(ctx context.Context) {
137178

138179
// Push pushes Prometheus metrics to the configured Graphite server.
139180
func (b *Bridge) Push() error {
181+
err := b.push()
182+
switch b.errorHandling {
183+
case AbortOnError:
184+
return err
185+
case ContinueOnError:
186+
if b.logger != nil {
187+
b.logger.Println("continue on error:", err)
188+
}
189+
case CallbackOnError:
190+
if b.errorCallbackFunc != nil {
191+
b.errorCallbackFunc(err)
192+
}
193+
}
194+
return nil
195+
}
196+
197+
func (b *Bridge) push() error {
140198
mfs, err := b.g.Gather()
141199
if err != nil {
142200
return err
143201
}
144-
145202
if len(mfs) == 0 {
146203
return nil
147204
}

prometheus/graphite/bridge_test.go

+48-30
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ import (
1919
"context"
2020
"fmt"
2121
"io"
22+
"log"
2223
"net"
24+
"os"
2325
"reflect"
2426
"regexp"
2527
"sort"
@@ -436,12 +438,13 @@ type mockGraphite struct {
436438

437439
func ExampleBridge() {
438440
b, err := NewBridge(&Config{
439-
URL: "graphite.example.org:3099",
440-
Gatherer: prometheus.DefaultGatherer,
441-
Prefix: "prefix",
442-
Interval: 15 * time.Second,
443-
Timeout: 10 * time.Second,
444-
ErrorHandler: func(err error) {},
441+
URL: "graphite.example.org:3099",
442+
Gatherer: prometheus.DefaultGatherer,
443+
Prefix: "prefix",
444+
Interval: 15 * time.Second,
445+
Timeout: 10 * time.Second,
446+
ErrorHandling: AbortOnError,
447+
Logger: log.New(os.Stdout, "graphite bridge: ", log.Lshortfile),
445448
})
446449
if err != nil {
447450
panic(err)
@@ -465,32 +468,47 @@ func ExampleBridge() {
465468
b.Run(ctx)
466469
}
467470

468-
func TestErrorHandler(t *testing.T) {
469-
var internalError error
470-
c := &Config{
471-
URL: "localhost",
472-
Gatherer: prometheus.DefaultGatherer,
473-
Prefix: "prefix",
474-
Interval: 5 * time.Second,
475-
Timeout: 2 * time.Second,
476-
ErrorHandler: func(err error) { internalError = err },
477-
}
478-
b, err := NewBridge(c)
479-
if err != nil {
480-
panic(err)
471+
func TestErrorHandling(t *testing.T) {
472+
var testCases = []struct {
473+
errorHandling HandlerErrorHandling
474+
receivedError error
475+
interceptedError error
476+
}{
477+
{
478+
errorHandling: ContinueOnError,
479+
receivedError: nil,
480+
interceptedError: nil,
481+
},
482+
{
483+
errorHandling: AbortOnError,
484+
receivedError: &net.OpError{},
485+
interceptedError: nil,
486+
},
487+
{
488+
errorHandling: CallbackOnError,
489+
receivedError: nil,
490+
interceptedError: &net.OpError{},
491+
},
481492
}
482493

483-
// Create a Context to control stopping the Run() loop that pushes
484-
// metrics to Graphite. Multiplied by 2, because we need Run to be executed at least one time.
485-
ctx, cancel := context.WithTimeout(context.Background(), c.Interval*2)
486-
defer cancel()
487-
488-
// Start pushing metrics to Graphite in the Run() loop.
489-
b.Run(ctx)
494+
for _, testCase := range testCases {
495+
var interceptedError error
496+
c := &Config{
497+
URL: "localhost",
498+
ErrorHandling: testCase.errorHandling,
499+
ErrorCallbackFunc: func(err error) { interceptedError = err },
500+
}
501+
b, err := NewBridge(c)
502+
if err != nil {
503+
t.Fatal(err)
504+
}
490505

491-
// We haven't specified port
492-
expError := fmt.Errorf("dial tcp: address localhost: missing port in address")
493-
if internalError.Error() != expError.Error() {
494-
t.Fatalf("Expected: '%s', actual: '%s'", expError, internalError)
506+
receivedError := b.Push()
507+
if reflect.TypeOf(receivedError) != reflect.TypeOf(testCase.receivedError) {
508+
t.Errorf("expected to receive: %T, received: %T", testCase.receivedError, receivedError)
509+
}
510+
if reflect.TypeOf(interceptedError) != reflect.TypeOf(testCase.interceptedError) {
511+
t.Errorf("expected to intercept: %T, intercepted: %T", testCase.interceptedError, interceptedError)
512+
}
495513
}
496514
}

0 commit comments

Comments
 (0)