/
exporter.go
141 lines (118 loc) · 2.79 KB
/
exporter.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
package exporter
import (
"context"
"fmt"
"net"
"net/http"
"github.com/go-logr/logr"
"github.com/go-logr/zapr"
"github.com/prometheus/client_golang/prometheus/promhttp"
"go.uber.org/zap"
)
const (
defaultBindAddress = ":9000"
defaultTelemetryPath = "/metrics"
)
// Exporter is responsible for bringing up a web server that collects metrics
// that have been globally registered via prometheus collectors (e.g., see
// `pkg/collector`).
//
type Exporter struct {
bindAddress string
telemetryPath string
listener net.Listener
log logr.Logger
}
// WithTelemetryPath overrides the default path under which the prometheus
// metrics are reported.
//
// For instance:
// - /
// - /metrics
// - /telemetry
//
func WithTelemetryPath(v string) Option {
return func(e *Exporter) {
e.telemetryPath = v
}
}
// WithBindAddress overrides the default address at which the prometheus
// metrics HTTP server would bind to.
//
// Examples:
// - :8080
// - 127.0.0.2:1313
//
func WithBindAddress(v string) Option {
return func(e *Exporter) {
e.bindAddress = v
}
}
// Option allows overriding the exporter's defaults
//
type Option func(e *Exporter)
// New instantiates a new exporter with defaults, unless options are passed.
//
func New(opts ...Option) (*Exporter, error) {
defaultLogger, err := zap.NewDevelopment()
if err != nil {
return nil, fmt.Errorf("zap new development: %w", err)
}
e := &Exporter{
bindAddress: defaultBindAddress,
telemetryPath: defaultTelemetryPath,
log: zapr.NewLogger(defaultLogger.Named("exporter")),
}
for _, opt := range opts {
opt(e)
}
return e, nil
}
// Run initiates the HTTP server to serve the metrics.
//
// ps.: this is a BLOCKING method - make sure you either make use of goroutines
// to not block if needed.
//
func (e *Exporter) Run(ctx context.Context) error {
var err error
e.listener, err = net.Listen("tcp", e.bindAddress)
if err != nil {
return fmt.Errorf("listen on '%s': %w", e.bindAddress, err)
}
doneChan := make(chan error, 1)
go func() {
defer close(doneChan)
e.log.WithValues(
"addr", e.bindAddress,
"path", e.telemetryPath,
).Info("listening")
http.Handle(e.telemetryPath, promhttp.Handler())
if err := http.Serve(e.listener, nil); err != nil {
doneChan <- fmt.Errorf(
"failed listening on address %s: %w",
e.bindAddress, err,
)
}
}()
select {
case err = <-doneChan:
if err != nil {
return fmt.Errorf("donechan err: %w", err)
}
case <-ctx.Done():
return fmt.Errorf("ctx err: %w", ctx.Err())
}
return nil
}
// Close gracefully closes the tcp listener associated with it.
//
func (e *Exporter) Close() (err error) {
if e.listener == nil {
return nil
}
e.log.Info("closing")
if err := e.listener.Close(); err != nil {
return fmt.Errorf("close: %w", err)
}
return nil
}