Skip to content

Commit 92c1ac7

Browse files
committed
Add a "factory" to make promauto work for custom registries
Also, update the package documentation. The concerns about the global registry aren't really valid anymore because promauto now also works with custom registries. The musings about the http.DefaultMux are more a digression and shouldn't be in a doc comment. Signed-off-by: beorn7 <[email protected]>
1 parent 87f9434 commit 92c1ac7

File tree

1 file changed

+209
-77
lines changed

1 file changed

+209
-77
lines changed

prometheus/promauto/auto.go

Lines changed: 209 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,16 @@
1111
// See the License for the specific language governing permissions and
1212
// limitations under the License.
1313

14-
// Package promauto provides constructors for the usual Prometheus metrics that
15-
// return them already registered with the global registry
16-
// (prometheus.DefaultRegisterer). This allows very compact code, avoiding any
17-
// references to the registry altogether, but all the constructors in this
18-
// package will panic if the registration fails.
14+
// Package promauto provides alternative constructors for the fundamental
15+
// Prometheus metric types and their …Vec and …Func variants. The difference to
16+
// their counterparts in the prometheus package is that the promauto
17+
// constructors return Collectors that are already registered with a
18+
// registry. There are two sets of constructors. The constructors in the first
19+
// set are top-level functions, while the constructors in the other set are
20+
// methods of the Factory type. The top-level function return Collectors
21+
// registered with the global registry (prometheus.DefaultRegisterer), while the
22+
// methods return Collectors registered with the registry the Factory was
23+
// constructed with. All constructors panic if the registration fails.
1924
//
2025
// The following example is a complete program to create a histogram of normally
2126
// distributed random numbers from the math/rand package:
@@ -79,51 +84,78 @@
7984
// http.ListenAndServe(":1971", nil)
8085
// }
8186
//
87+
// A Factory is created with the With(prometheus.Registerer) function, which
88+
// enables two usage pattern. With(prometheus.Registerer) can be called once per
89+
// line:
90+
//
91+
// var (
92+
// reg = prometheus.NewRegistry()
93+
// randomNumbers = promauto.With(reg).NewHistogram(prometheus.HistogramOpts{
94+
// Name: "random_numbers",
95+
// Help: "A histogram of normally distributed random numbers.",
96+
// Buckets: prometheus.LinearBuckets(-3, .1, 61),
97+
// })
98+
// requestCount = promauto.With(reg).NewCounterVec(
99+
// prometheus.CounterOpts{
100+
// Name: "http_requests_total",
101+
// Help: "Total number of HTTP requests by status code end method.",
102+
// },
103+
// []string{"code", "method"},
104+
// )
105+
// )
106+
//
107+
// Or it can be used to create a Factory once to be used multiple times:
108+
//
109+
// var (
110+
// reg = prometheus.NewRegistry()
111+
// factory = promauto.With(reg)
112+
// randomNumbers = factory.NewHistogram(prometheus.HistogramOpts{
113+
// Name: "random_numbers",
114+
// Help: "A histogram of normally distributed random numbers.",
115+
// Buckets: prometheus.LinearBuckets(-3, .1, 61),
116+
// })
117+
// requestCount = factory.NewCounterVec(
118+
// prometheus.CounterOpts{
119+
// Name: "http_requests_total",
120+
// Help: "Total number of HTTP requests by status code end method.",
121+
// },
122+
// []string{"code", "method"},
123+
// )
124+
// )
125+
//
82126
// This appears very handy. So why are these constructors locked away in a
83-
// separate package? There are two caveats:
84-
//
85-
// First, in more complex programs, global state is often quite problematic.
86-
// That's the reason why the metrics constructors in the prometheus package do
87-
// not interact with the global prometheus.DefaultRegisterer on their own. You
88-
// are free to use the Register or MustRegister functions to register them with
89-
// the global prometheus.DefaultRegisterer, but you could as well choose a local
90-
// Registerer (usually created with prometheus.NewRegistry, but there are other
91-
// scenarios, e.g. testing).
92-
//
93-
// The second issue is that registration may fail, e.g. if a metric inconsistent
94-
// with the newly to be registered one is already registered. But how to signal
95-
// and handle a panic in the automatic registration with the default registry?
96-
// The only way is panicking. While panicking on invalid input provided by the
97-
// programmer is certainly fine, things are a bit more subtle in this case: You
98-
// might just add another package to the program, and that package (in its init
99-
// function) happens to register a metric with the same name as your code. Now,
100-
// all of a sudden, either your code or the code of the newly imported package
101-
// panics, depending on initialization order, without any opportunity to handle
102-
// the case gracefully. Even worse is a scenario where registration happens
103-
// later during the runtime (e.g. upon loading some kind of plugin), where the
104-
// panic could be triggered long after the code has been deployed to
105-
// production. A possibility to panic should be explicitly called out by the
106-
// Must… idiom, cf. prometheus.MustRegister. But adding a separate set of
107-
// constructors in the prometheus package called MustRegisterNewCounterVec or
108-
// similar would be quite unwieldy. Adding an extra MustRegister method to each
109-
// metric, returning the registered metric, would result in nice code for those
110-
// using the method, but would pollute every single metric interface for
111-
// everybody avoiding the global registry.
112-
//
113-
// To address both issues, the problematic auto-registering and possibly
114-
// panicking constructors are all in this package with a clear warning
115-
// ahead. And whoever cares about avoiding global state and possibly panicking
116-
// function calls can simply ignore the existence of the promauto package
117-
// altogether.
118-
//
119-
// A final note: There is a similar case in the net/http package of the standard
120-
// library. It has DefaultServeMux as a global instance of ServeMux, and the
121-
// Handle function acts on it, panicking if a handler for the same pattern has
122-
// already been registered. However, one might argue that the whole HTTP routing
123-
// is usually set up closely together in the same package or file, while
124-
// Prometheus metrics tend to be spread widely over the codebase, increasing the
125-
// chance of surprising registration failures. Furthermore, the use of global
126-
// state in net/http has been criticized widely, and some avoid it altogether.
127+
// separate package?
128+
//
129+
// The main problem is that registration may fail, e.g. if a metric inconsistent
130+
// with the newly to be registered one is already registered. Therefore, the
131+
// Register method in the prometheus.Registerer interface returns an error, and
132+
// the same is the case for the top-level prometheus.Register function that
133+
// registers with the global registry. The prometheus package also provides
134+
// MustRegister versions for both. They panic if the registration fails, and
135+
// they clearly call this out by using the Must… idiom. Panicking is a bit
136+
// problematic here because it doesn't just happen on input provided by the
137+
// caller that is invalid on its own. Things are a bit more subtle here: Metric
138+
// creation and registration tend to be spread widely over the codebase. It can
139+
// easily happen that an incompatible metric is added to an unrelated part of
140+
// the code, and suddenly code that used to work perfectly fine starts to panic
141+
// (provided that the registration of the newly added metric happens before the
142+
// registration of the previously existing metric). This may come as an even
143+
// bigger surprise with the global registry, where simply importing another
144+
// package can trigger a panic (if the newly imported package registers metrics
145+
// in its init function). At least, in the prometheus package, creation of
146+
// metrics and other collectors is separate from registration. You first create
147+
// the metric, and then you decide explicitly if you want to register it with a
148+
// local or the global registry, and if you want to handle the error or risk a
149+
// panic. With the constructors in the promauto package, registration is
150+
// automatic, and if it fails, it will always panic. Furthermore, the
151+
// constructors will often be called in the var section of a file, which means
152+
// that panicking will happen as a side effect of merely importing a package.
153+
//
154+
// A separate package allows conservative users to entirely ignore it. And
155+
// whoever wants to use it, will do so explicitly, with an opportunity to read
156+
// this warning.
157+
//
158+
// Enjoy promauto responsibly!
127159
package promauto
128160

129161
import "github.com/prometheus/client_golang/prometheus"
@@ -132,92 +164,192 @@ import "github.com/prometheus/client_golang/prometheus"
132164
// but it automatically registers the Counter with the
133165
// prometheus.DefaultRegisterer. If the registration fails, NewCounter panics.
134166
func NewCounter(opts prometheus.CounterOpts) prometheus.Counter {
135-
c := prometheus.NewCounter(opts)
136-
prometheus.MustRegister(c)
137-
return c
167+
return With(prometheus.DefaultRegisterer).NewCounter(opts)
138168
}
139169

140170
// NewCounterVec works like the function of the same name in the prometheus
141171
// package but it automatically registers the CounterVec with the
142172
// prometheus.DefaultRegisterer. If the registration fails, NewCounterVec
143173
// panics.
144174
func NewCounterVec(opts prometheus.CounterOpts, labelNames []string) *prometheus.CounterVec {
145-
c := prometheus.NewCounterVec(opts, labelNames)
146-
prometheus.MustRegister(c)
147-
return c
175+
return With(prometheus.DefaultRegisterer).NewCounterVec(opts, labelNames)
148176
}
149177

150178
// NewCounterFunc works like the function of the same name in the prometheus
151179
// package but it automatically registers the CounterFunc with the
152180
// prometheus.DefaultRegisterer. If the registration fails, NewCounterFunc
153181
// panics.
154182
func NewCounterFunc(opts prometheus.CounterOpts, function func() float64) prometheus.CounterFunc {
155-
g := prometheus.NewCounterFunc(opts, function)
156-
prometheus.MustRegister(g)
157-
return g
183+
return With(prometheus.DefaultRegisterer).NewCounterFunc(opts, function)
158184
}
159185

160186
// NewGauge works like the function of the same name in the prometheus package
161187
// but it automatically registers the Gauge with the
162188
// prometheus.DefaultRegisterer. If the registration fails, NewGauge panics.
163189
func NewGauge(opts prometheus.GaugeOpts) prometheus.Gauge {
164-
g := prometheus.NewGauge(opts)
165-
prometheus.MustRegister(g)
166-
return g
190+
return With(prometheus.DefaultRegisterer).NewGauge(opts)
167191
}
168192

169193
// NewGaugeVec works like the function of the same name in the prometheus
170194
// package but it automatically registers the GaugeVec with the
171195
// prometheus.DefaultRegisterer. If the registration fails, NewGaugeVec panics.
172196
func NewGaugeVec(opts prometheus.GaugeOpts, labelNames []string) *prometheus.GaugeVec {
173-
g := prometheus.NewGaugeVec(opts, labelNames)
174-
prometheus.MustRegister(g)
175-
return g
197+
return With(prometheus.DefaultRegisterer).NewGaugeVec(opts, labelNames)
176198
}
177199

178200
// NewGaugeFunc works like the function of the same name in the prometheus
179201
// package but it automatically registers the GaugeFunc with the
180202
// prometheus.DefaultRegisterer. If the registration fails, NewGaugeFunc panics.
181203
func NewGaugeFunc(opts prometheus.GaugeOpts, function func() float64) prometheus.GaugeFunc {
182-
g := prometheus.NewGaugeFunc(opts, function)
183-
prometheus.MustRegister(g)
184-
return g
204+
return With(prometheus.DefaultRegisterer).NewGaugeFunc(opts, function)
185205
}
186206

187207
// NewSummary works like the function of the same name in the prometheus package
188208
// but it automatically registers the Summary with the
189209
// prometheus.DefaultRegisterer. If the registration fails, NewSummary panics.
190210
func NewSummary(opts prometheus.SummaryOpts) prometheus.Summary {
191-
s := prometheus.NewSummary(opts)
192-
prometheus.MustRegister(s)
193-
return s
211+
return With(prometheus.DefaultRegisterer).NewSummary(opts)
194212
}
195213

196214
// NewSummaryVec works like the function of the same name in the prometheus
197215
// package but it automatically registers the SummaryVec with the
198216
// prometheus.DefaultRegisterer. If the registration fails, NewSummaryVec
199217
// panics.
200218
func NewSummaryVec(opts prometheus.SummaryOpts, labelNames []string) *prometheus.SummaryVec {
201-
s := prometheus.NewSummaryVec(opts, labelNames)
202-
prometheus.MustRegister(s)
203-
return s
219+
return With(prometheus.DefaultRegisterer).NewSummaryVec(opts, labelNames)
204220
}
205221

206222
// NewHistogram works like the function of the same name in the prometheus
207223
// package but it automatically registers the Histogram with the
208224
// prometheus.DefaultRegisterer. If the registration fails, NewHistogram panics.
209225
func NewHistogram(opts prometheus.HistogramOpts) prometheus.Histogram {
210-
h := prometheus.NewHistogram(opts)
211-
prometheus.MustRegister(h)
212-
return h
226+
return With(prometheus.DefaultRegisterer).NewHistogram(opts)
213227
}
214228

215229
// NewHistogramVec works like the function of the same name in the prometheus
216230
// package but it automatically registers the HistogramVec with the
217231
// prometheus.DefaultRegisterer. If the registration fails, NewHistogramVec
218232
// panics.
219233
func NewHistogramVec(opts prometheus.HistogramOpts, labelNames []string) *prometheus.HistogramVec {
234+
return With(prometheus.DefaultRegisterer).NewHistogramVec(opts, labelNames)
235+
}
236+
237+
// Factory provides factory methods to create Collectors that are automatically
238+
// registered with a Registerer. Create a Factory with the With function,
239+
// providing a Registerer to auto-register created Collectors with. The zero
240+
// value of a Factory creates Collectors that are not registered with any
241+
// Registerer. All methods of the Factory panic if the registration fails.
242+
type Factory struct {
243+
r prometheus.Registerer
244+
}
245+
246+
// With creates a Factory using the provided Registerer for registration of the
247+
// created Collectors.
248+
func With(r prometheus.Registerer) Factory { return Factory{r} }
249+
250+
// NewCounter works like the function of the same name in the prometheus package
251+
// but it automatically registers the Counter with the Factory's Registerer.
252+
func (f Factory) NewCounter(opts prometheus.CounterOpts) prometheus.Counter {
253+
c := prometheus.NewCounter(opts)
254+
if f.r != nil {
255+
f.r.MustRegister(c)
256+
}
257+
return c
258+
}
259+
260+
// NewCounterVec works like the function of the same name in the prometheus
261+
// package but it automatically registers the CounterVec with the Factory's
262+
// Registerer.
263+
func (f Factory) NewCounterVec(opts prometheus.CounterOpts, labelNames []string) *prometheus.CounterVec {
264+
c := prometheus.NewCounterVec(opts, labelNames)
265+
if f.r != nil {
266+
f.r.MustRegister(c)
267+
}
268+
return c
269+
}
270+
271+
// NewCounterFunc works like the function of the same name in the prometheus
272+
// package but it automatically registers the CounterFunc with the Factory's
273+
// Registerer.
274+
func (f Factory) NewCounterFunc(opts prometheus.CounterOpts, function func() float64) prometheus.CounterFunc {
275+
c := prometheus.NewCounterFunc(opts, function)
276+
if f.r != nil {
277+
f.r.MustRegister(c)
278+
}
279+
return c
280+
}
281+
282+
// NewGauge works like the function of the same name in the prometheus package
283+
// but it automatically registers the Gauge with the Factory's Registerer.
284+
func (f Factory) NewGauge(opts prometheus.GaugeOpts) prometheus.Gauge {
285+
g := prometheus.NewGauge(opts)
286+
if f.r != nil {
287+
f.r.MustRegister(g)
288+
}
289+
return g
290+
}
291+
292+
// NewGaugeVec works like the function of the same name in the prometheus
293+
// package but it automatically registers the GaugeVec with the Factory's
294+
// Registerer.
295+
func (f Factory) NewGaugeVec(opts prometheus.GaugeOpts, labelNames []string) *prometheus.GaugeVec {
296+
g := prometheus.NewGaugeVec(opts, labelNames)
297+
if f.r != nil {
298+
f.r.MustRegister(g)
299+
}
300+
return g
301+
}
302+
303+
// NewGaugeFunc works like the function of the same name in the prometheus
304+
// package but it automatically registers the GaugeFunc with the Factory's
305+
// Registerer.
306+
func (f Factory) NewGaugeFunc(opts prometheus.GaugeOpts, function func() float64) prometheus.GaugeFunc {
307+
g := prometheus.NewGaugeFunc(opts, function)
308+
if f.r != nil {
309+
f.r.MustRegister(g)
310+
}
311+
return g
312+
}
313+
314+
// NewSummary works like the function of the same name in the prometheus package
315+
// but it automatically registers the Summary with the Factory's Registerer.
316+
func (f Factory) NewSummary(opts prometheus.SummaryOpts) prometheus.Summary {
317+
s := prometheus.NewSummary(opts)
318+
if f.r != nil {
319+
f.r.MustRegister(s)
320+
}
321+
return s
322+
}
323+
324+
// NewSummaryVec works like the function of the same name in the prometheus
325+
// package but it automatically registers the SummaryVec with the Factory's
326+
// Registerer.
327+
func (f Factory) NewSummaryVec(opts prometheus.SummaryOpts, labelNames []string) *prometheus.SummaryVec {
328+
s := prometheus.NewSummaryVec(opts, labelNames)
329+
if f.r != nil {
330+
f.r.MustRegister(s)
331+
}
332+
return s
333+
}
334+
335+
// NewHistogram works like the function of the same name in the prometheus
336+
// package but it automatically registers the Histogram with the Factory's
337+
// Registerer.
338+
func (f Factory) NewHistogram(opts prometheus.HistogramOpts) prometheus.Histogram {
339+
h := prometheus.NewHistogram(opts)
340+
if f.r != nil {
341+
f.r.MustRegister(h)
342+
}
343+
return h
344+
}
345+
346+
// NewHistogramVec works like the function of the same name in the prometheus
347+
// package but it automatically registers the HistogramVec with the Factory's
348+
// Registerer.
349+
func (f Factory) NewHistogramVec(opts prometheus.HistogramOpts, labelNames []string) *prometheus.HistogramVec {
220350
h := prometheus.NewHistogramVec(opts, labelNames)
221-
prometheus.MustRegister(h)
351+
if f.r != nil {
352+
f.r.MustRegister(h)
353+
}
222354
return h
223355
}

0 commit comments

Comments
 (0)