diff --git a/prometheus/doc.go b/prometheus/doc.go index 08ac233..1f5942b 100644 --- a/prometheus/doc.go +++ b/prometheus/doc.go @@ -12,11 +12,12 @@ // limitations under the License. // Package prometheus provides metrics primitives to instrument code for -// monitoring. It also offers a registry for metrics and ways to expose -// registered metrics via an HTTP endpoint or push them to a Pushgateway. +// monitoring. It also offers a registry for metrics. Sub-packages allow to +// expose the registered metrics via HTTP (package promhttp) or push them to a +// Pushgateway (package push). // // All exported functions and methods are safe to be used concurrently unless -// specified otherwise. +//specified otherwise. // // A Basic Example // @@ -28,6 +29,7 @@ // "net/http" // // "github.com/prometheus/client_golang/prometheus" +// "github.com/prometheus/client_golang/prometheus/promhttp" // ) // // var ( @@ -35,10 +37,13 @@ // Name: "cpu_temperature_celsius", // Help: "Current temperature of the CPU.", // }) -// hdFailures = prometheus.NewCounter(prometheus.CounterOpts{ -// Name: "hd_errors_total", -// Help: "Number of hard-disk errors.", -// }) +// hdFailures = prometheus.NewCounterVec( +// prometheus.CounterOpts{ +// Name: "hd_errors_total", +// Help: "Number of hard-disk errors.", +// }, +// []string{"device"}, +// ) // ) // // func init() { @@ -49,18 +54,17 @@ // // func main() { // cpuTemp.Set(65.3) -// hdFailures.Inc() +// hdFailures.With(prometheus.Labels{"device":"/dev/sda"}).Inc() // // // The Handler function provides a default handler to expose metrics // // via an HTTP server. "/metrics" is the usual endpoint for that. -// http.Handle("/metrics", prometheus.Handler()) +// http.Handle("/metrics", promhttp.Handler()) // http.ListenAndServe(":8080", nil) // } // // -// This is a complete program that exports two metrics, a Gauge and a Counter. -// It also exports some stats about the HTTP usage of the /metrics -// endpoint. (See the Handler function for more detail.) +// This is a complete program that exports two metrics, a Gauge and a Counter, +// the later with a label attached to turn it into a (one-dimensional) vector. // // Metrics // @@ -159,18 +163,19 @@ // // HTTP Exposition // -// The Handler function used so far to get an http.Handler for serving the -// metrics is also acting on the DefaultRegistry. With HandlerFor, you can -// create a handler for a custom registry or anything that implements the -// Deliverer interface. It also allows to create handlers that act differently -// on errors or allow to log errors. Also note that the handler returned by the -// Handler function is already instrumented with some HTTP metrics. You can call -// UninstrumentedHandler to get a handler for the DefaultRegistry that is not -// instrumented, or you can use InstrumentHandler to instrument any -// http.Handlers of your choice. (But note that the way the instrumentation -// happens is partially obsolete. Better ways are being worked on.) +// The Registry implements the Deliverer interface. The caller of the Deliver +// method can then expose the delivered metrics in some way. Usually, the +// metrics are served via HTTP on the /metrics endpoint. That's happening in the +// example above. The tools to expose metrics via HTTP are in the promhttp +// sub-package. (The top-level functions in the prometheus package are +// deprecated.) // // Pushing to the Pushgateway // // Function for pushing to the Pushgateway can be found in the push sub-package. +// +// Other Means of Exposition +// +// More ways of exposing metrics can easily be added. Sending metrics to +// Graphite would be an example that will soon be implemented. package prometheus diff --git a/prometheus/http.go b/prometheus/http.go index 7ffe097..0a617a2 100644 --- a/prometheus/http.go +++ b/prometheus/http.go @@ -15,6 +15,7 @@ package prometheus import ( "bufio" + "bytes" "compress/gzip" "fmt" "io" @@ -22,11 +23,17 @@ import ( "net/http" "strconv" "strings" + "sync" "time" "github.com/prometheus/common/expfmt" ) +// TODO(beorn7): Remove this whole file. It is a partial mirror of +// promhttp/http.go (to avoid circular import chains) where everything HTTP +// related should live. The functions here are just for avoiding +// breakage. Everything is deprecated. + const ( contentTypeHeader = "Content-Type" contentLengthHeader = "Content-Length" @@ -34,82 +41,54 @@ const ( acceptEncodingHeader = "Accept-Encoding" ) -// Handler returns an HTTP handler for the DefaultRegistry. It is +var bufPool sync.Pool + +func getBuf() *bytes.Buffer { + buf := bufPool.Get() + if buf == nil { + return &bytes.Buffer{} + } + return buf.(*bytes.Buffer) +} + +func giveBuf(buf *bytes.Buffer) { + buf.Reset() + bufPool.Put(buf) +} + +// Handler returns an HTTP handler for the DefaultDeliverer. It is // already instrumented with InstrumentHandler (using "prometheus" as handler // name). // -// Please note the issues described in the doc comment of InstrumentHandler. You -// might want to consider using UninstrumentedHandler instead. In fact, the -// instrumentation of the handler is DEPRECATED. In future versions of this -// package, the Handler function will return an uninstrumented handler, and the -// UninstrumentedHandler function will be removed. -// -// The returned Handler is using the same HandlerOpts as the Handler returned by -// UninstrumentedHandler. See there for details. +// Deprecated: Please note the issues described in the doc comment of +// InstrumentHandler. You might want to consider using promhttp.Handler instead +// (which is non instrumented). func Handler() http.Handler { return InstrumentHandler("prometheus", UninstrumentedHandler()) } -// UninstrumentedHandler returns an HTTP handler for the DefaultDeliverer. The -// Handler uses the default HandlerOpts, i.e. report the first error as an HTTP -// error, no error logging, and compression if requested by the client. +// UninstrumentedHandler returns an HTTP handler for the DefaultDeliverer. // -// If you want to create a Handler for the DefaultDeliverer with different -// HandlerOpts, create it with HandlerFor with the DefaultDeliverer and your -// desired HandlerOpts. -// -// Note that in future versions of this package, UninstrumentedHandler will be -// replaced by Handler (which will then return an uninstrumented handler, see -// there for details). +// Deprecated: Use promhttp.Handler instead. See there for further documentation. func UninstrumentedHandler() http.Handler { - return HandlerFor(DefaultDeliverer, HandlerOpts{}) -} - -// HandlerFor returns an http.Handler for the provided Deliverer. The behavior ef -// the Handler is defined by the provided HandlerOpts. The Handler is NOT -// instrumented with InstrumentHandler. -func HandlerFor(reg Deliverer, opts HandlerOpts) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - mfs, err := reg.Deliver() + mfs, err := DefaultDeliverer.Deliver() if err != nil { - if opts.ErrorLog != nil { - opts.ErrorLog.Println("error collecting metrics:", err) - } - switch opts.ErrorHandling { - case PanicOnError: - panic(err) - case ContinueOnError: - if len(mfs) == 0 { - http.Error(w, "No metrics collected, last error:\n\n"+err.Error(), http.StatusInternalServerError) - return - } - case HTTPErrorOnError: - http.Error(w, "An error has occurred during metrics collection:\n\n"+err.Error(), http.StatusInternalServerError) - return - } + http.Error(w, "An error has occurred during metrics collection:\n\n"+err.Error(), http.StatusInternalServerError) + return } contentType := expfmt.Negotiate(req.Header) buf := getBuf() defer giveBuf(buf) - writer, encoding := decorateWriter(req, buf, opts.DisableCompression) + writer, encoding := decorateWriter(req, buf) enc := expfmt.NewEncoder(writer, contentType) var lastErr error for _, mf := range mfs { if err := enc.Encode(mf); err != nil { lastErr = err - if opts.ErrorLog != nil { - opts.ErrorLog.Println("error encoding metric family:", err) - } - switch opts.ErrorHandling { - case PanicOnError: - panic(err) - case ContinueOnError: - // Handled later. - case HTTPErrorOnError: - http.Error(w, "An error has occurred during metrics encoding:\n\n"+err.Error(), http.StatusInternalServerError) - return - } + http.Error(w, "An error has occurred during metrics encoding:\n\n"+err.Error(), http.StatusInternalServerError) + return } } if closer, ok := writer.(io.Closer); ok { @@ -126,60 +105,13 @@ func HandlerFor(reg Deliverer, opts HandlerOpts) http.Handler { header.Set(contentEncodingHeader, encoding) } w.Write(buf.Bytes()) - // TODO(beorn7): Consider streaming serving of metrics. }) } -// HandlerErrorHandling defines how a Handler serving metrics will handle -// errors. -type HandlerErrorHandling int - -// These constants cause handlers serving metrics to behave as described if -// errors are encountered. -const ( - // Serve an HTTP status code 500 upon the first error - // encountered. Report the error message in the body. - HTTPErrorOnError HandlerErrorHandling = iota - // Ignore errors and try to serve as many metrics as possible. However, - // if no metrics can be served, serve an HTTP status code 500 and the - // last error message in the body. Only use this in deliberate "best - // effort" metrics collection scenarios. It is recommended to at least - // log errors (by providing an ErrorLog in HandlerOpts) to not mask - // errors completely. - ContinueOnError - // Panic upon the first error encountered (useful for "crash only" apps). - PanicOnError -) - -// Logger is the minimal interface HandlerOpts needs for logging. Note that -// log.Logger from the standard library implements this interface, and it is -// easy to implement by custom loggers, if they don't do so already anyway. -type Logger interface { - Println(v ...interface{}) -} - -// HandlerOpts specifies options how to serve metrics via an http.Handler. The -// zero value of HandlerOpts is a reasonable default. -type HandlerOpts struct { - // ErrorLog specifies an optional logger for errors collecting and - // serving metrics. If nil, errors are not logged at all. - ErrorLog Logger - // ErrorHandling defines how errors are handled. Note that errors are - // logged regardless of the configured ErrorHandling provided ErrorLog - // is not nil. - ErrorHandling HandlerErrorHandling - // If DisableCompression is true, the handler will never compress the - // response, even if requested by the client. - DisableCompression bool -} - // decorateWriter wraps a writer to handle gzip compression if requested. It // returns the decorated writer and the appropriate "Content-Encoding" header // (which is empty if no compression is enabled). -func decorateWriter(request *http.Request, writer io.Writer, compressionDisabled bool) (io.Writer, string) { - if compressionDisabled { - return writer, "" - } +func decorateWriter(request *http.Request, writer io.Writer) (io.Writer, string) { header := request.Header.Get(acceptEncodingHeader) parts := strings.Split(header, ",") for _, part := range parts { @@ -226,7 +158,7 @@ func nowSeries(t ...time.Time) nower { // value. http_requests_total is a metric vector partitioned by HTTP method // (label name "method") and HTTP status code (label name "code"). // -// Note that InstrumentHandler has several issues: +// Deprecated: InstrumentHandler has several issues: // // - It uses Summaries rather than Histograms. Summaries are not useful if // aggregation across multiple instances is required. @@ -241,8 +173,8 @@ func nowSeries(t ...time.Time) nower { // performing such writes. // // Upcoming versions of this package will provide ways of instrumenting HTTP -// handlers that are more flexible and have fewer issues. Consider this function -// DEPRECATED and prefer direct instrumentation in the meantime. +// handlers that are more flexible and have fewer issues. Please prefer direct +// instrumentation in the meantime. func InstrumentHandler(handlerName string, handler http.Handler) http.HandlerFunc { return InstrumentHandlerFunc(handlerName, handler.ServeHTTP) } @@ -250,6 +182,9 @@ func InstrumentHandler(handlerName string, handler http.Handler) http.HandlerFun // InstrumentHandlerFunc wraps the given function for instrumentation. It // otherwise works in the same way as InstrumentHandler (and shares the same // issues). +// +// Deprecated: InstrumentHandlerFunc is deprecated for the same reasons as +// InstrumentHandler is. func InstrumentHandlerFunc(handlerName string, handlerFunc func(http.ResponseWriter, *http.Request)) http.HandlerFunc { return InstrumentHandlerFuncWithOpts( SummaryOpts{ @@ -285,6 +220,9 @@ func InstrumentHandlerFunc(handlerName string, handlerFunc func(http.ResponseWri // cannot use SummaryOpts. Instead, a CounterOpts struct is created internally, // and all its fields are set to the equally named fields in the provided // SummaryOpts. +// +// Deprecated: InstrumentHandlerWithOpts is deprecated for the same reasons as +// InstrumentHandler is. func InstrumentHandlerWithOpts(opts SummaryOpts, handler http.Handler) http.HandlerFunc { return InstrumentHandlerFuncWithOpts(opts, handler.ServeHTTP) } @@ -293,6 +231,9 @@ func InstrumentHandlerWithOpts(opts SummaryOpts, handler http.Handler) http.Hand // the same issues) but provides more flexibility (at the cost of a more complex // call syntax). See InstrumentHandlerWithOpts for details how the provided // SummaryOpts are used. +// +// Deprecated: InstrumentHandlerFuncWithOpts is deprecated for the same reasons +// as InstrumentHandler is. func InstrumentHandlerFuncWithOpts(opts SummaryOpts, handlerFunc func(http.ResponseWriter, *http.Request)) http.HandlerFunc { reqCnt := NewCounterVec( CounterOpts{ diff --git a/prometheus/http_test.go b/prometheus/http_test.go index d4bfe25..ffe0418 100644 --- a/prometheus/http_test.go +++ b/prometheus/http_test.go @@ -14,9 +14,6 @@ package prometheus import ( - "bytes" - "errors" - "log" "net/http" "net/http/httptest" "testing" @@ -122,113 +119,3 @@ func TestInstrumentHandler(t *testing.T) { t.Errorf("want reqCnt of %f, got %f", want, got) } } - -type errorCollector struct{} - -func (e errorCollector) Describe(ch chan<- *Desc) { - ch <- NewDesc("invalid_metric", "not helpful", nil, nil) -} - -func (e errorCollector) Collect(ch chan<- Metric) { - ch <- NewInvalidMetric( - NewDesc("invalid_metric", "not helpful", nil, nil), - errors.New("collect error"), - ) -} - -func TestHandlerErrorHandling(t *testing.T) { - - // Create a registry that collects a MetricFamily with two elements, - // another with one, and reports an error. - reg := NewRegistry() - - cnt := NewCounter(CounterOpts{ - Name: "the_count", - Help: "Ah-ah-ah! Thunder and lightning!", - }) - reg.MustRegister(cnt) - - cntVec := NewCounterVec( - CounterOpts{ - Name: "name", - Help: "docstring", - ConstLabels: Labels{"constname": "constvalue"}, - }, - []string{"labelname"}, - ) - cntVec.WithLabelValues("val1").Inc() - cntVec.WithLabelValues("val2").Inc() - reg.MustRegister(cntVec) - - reg.MustRegister(errorCollector{}) - - logBuf := &bytes.Buffer{} - logger := log.New(logBuf, "", 0) - - writer := httptest.NewRecorder() - request, _ := http.NewRequest("GET", "/", nil) - request.Header.Add("Accept", "test/plain") - - errorHandler := HandlerFor(reg, HandlerOpts{ - ErrorLog: logger, - ErrorHandling: HTTPErrorOnError, - }) - continueHandler := HandlerFor(reg, HandlerOpts{ - ErrorLog: logger, - ErrorHandling: ContinueOnError, - }) - panicHandler := HandlerFor(reg, HandlerOpts{ - ErrorLog: logger, - ErrorHandling: PanicOnError, - }) - wantMsg := `error collecting metrics: 1 error(s) occurred: - -* error collecting metric Desc{fqName: "invalid_metric", help: "not helpful", constLabels: {}, variableLabels: []}: collect error -` - wantErrorBody := `An error has occurred during metrics collection: - -1 error(s) occurred: - -* error collecting metric Desc{fqName: "invalid_metric", help: "not helpful", constLabels: {}, variableLabels: []}: collect error -` - wantOKBody := `# HELP name docstring -# TYPE name counter -name{constname="constvalue",labelname="val1"} 1 -name{constname="constvalue",labelname="val2"} 1 -# HELP the_count Ah-ah-ah! Thunder and lightning! -# TYPE the_count counter -the_count 0 -` - - errorHandler.ServeHTTP(writer, request) - if got, want := writer.Code, http.StatusInternalServerError; got != want { - t.Errorf("got HTTP status code %d, want %d", got, want) - } - if got := logBuf.String(); got != wantMsg { - t.Errorf("got log message %q, want %q", got, wantMsg) - } - if got := writer.Body.String(); got != wantErrorBody { - t.Errorf("got body %q, want %q", got, wantErrorBody) - } - logBuf.Reset() - writer.Body.Reset() - writer.Code = http.StatusOK - - continueHandler.ServeHTTP(writer, request) - if got, want := writer.Code, http.StatusOK; got != want { - t.Errorf("got HTTP status code %d, want %d", got, want) - } - if got := logBuf.String(); got != wantMsg { - t.Errorf("got log message %q, want %q", got, wantMsg) - } - if got := writer.Body.String(); got != wantOKBody { - t.Errorf("got body %q, want %q", got, wantOKBody) - } - - defer func() { - if err := recover(); err == nil { - t.Error("expected panic from panicHandler") - } - }() - panicHandler.ServeHTTP(writer, request) -} diff --git a/prometheus/pool.go b/prometheus/pool.go deleted file mode 100644 index 7096af0..0000000 --- a/prometheus/pool.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2016 The Prometheus Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package prometheus - -import ( - "bytes" - "sync" -) - -var bufPool sync.Pool - -func getBuf() *bytes.Buffer { - buf := bufPool.Get() - if buf == nil { - return &bytes.Buffer{} - } - return buf.(*bytes.Buffer) -} - -func giveBuf(buf *bytes.Buffer) { - buf.Reset() - bufPool.Put(buf) -} diff --git a/prometheus/promhttp/http.go b/prometheus/promhttp/http.go new file mode 100644 index 0000000..79bff2b --- /dev/null +++ b/prometheus/promhttp/http.go @@ -0,0 +1,201 @@ +// Copyright 2016 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Copyright (c) 2013, The Prometheus Authors +// All rights reserved. +// +// Use of this source code is governed by a BSD-style license that can be found +// in the LICENSE file. + +// Package promhttp contains functions to create http.Handler instances to +// expose Prometheus metrics via HTTP. In later versions of this package, it +// will also contain tooling to instrument instances of http.Handler and +// http.RoundTripper. +// +// promhttp.Handler acts on the prometheus.DefaultDeliverer. With HandlerFor, +// you can create a handler for a custom registry or anything that implements +// the Deliverer interface. It also allows to create handlers that act +// differently on errors or allow to log errors. +package promhttp + +import ( + "bytes" + "compress/gzip" + "fmt" + "io" + "net/http" + "strings" + "sync" + + "github.com/prometheus/common/expfmt" + + "github.com/prometheus/client_golang/prometheus" +) + +const ( + contentTypeHeader = "Content-Type" + contentLengthHeader = "Content-Length" + contentEncodingHeader = "Content-Encoding" + acceptEncodingHeader = "Accept-Encoding" +) + +var bufPool sync.Pool + +func getBuf() *bytes.Buffer { + buf := bufPool.Get() + if buf == nil { + return &bytes.Buffer{} + } + return buf.(*bytes.Buffer) +} + +func giveBuf(buf *bytes.Buffer) { + buf.Reset() + bufPool.Put(buf) +} + +// Handler returns an HTTP handler for the prometheus.DefaultDeliverer. The +// Handler uses the default HandlerOpts, i.e. report the first error as an HTTP +// error, no error logging, and compression if requested by the client. +// +// If you want to create a Handler for the DefaultDeliverer with different +// HandlerOpts, create it with HandlerFor with prometheus.DefaultDeliverer and +// your desired HandlerOpts. +func Handler() http.Handler { + return HandlerFor(prometheus.DefaultDeliverer, HandlerOpts{}) +} + +// HandlerFor returns an http.Handler for the provided Deliverer. The behavior +// ef the Handler is defined by the provided HandlerOpts. +func HandlerFor(reg prometheus.Deliverer, opts HandlerOpts) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + mfs, err := reg.Deliver() + if err != nil { + if opts.ErrorLog != nil { + opts.ErrorLog.Println("error collecting metrics:", err) + } + switch opts.ErrorHandling { + case PanicOnError: + panic(err) + case ContinueOnError: + if len(mfs) == 0 { + http.Error(w, "No metrics collected, last error:\n\n"+err.Error(), http.StatusInternalServerError) + return + } + case HTTPErrorOnError: + http.Error(w, "An error has occurred during metrics collection:\n\n"+err.Error(), http.StatusInternalServerError) + return + } + } + + contentType := expfmt.Negotiate(req.Header) + buf := getBuf() + defer giveBuf(buf) + writer, encoding := decorateWriter(req, buf, opts.DisableCompression) + enc := expfmt.NewEncoder(writer, contentType) + var lastErr error + for _, mf := range mfs { + if err := enc.Encode(mf); err != nil { + lastErr = err + if opts.ErrorLog != nil { + opts.ErrorLog.Println("error encoding metric family:", err) + } + switch opts.ErrorHandling { + case PanicOnError: + panic(err) + case ContinueOnError: + // Handled later. + case HTTPErrorOnError: + http.Error(w, "An error has occurred during metrics encoding:\n\n"+err.Error(), http.StatusInternalServerError) + return + } + } + } + if closer, ok := writer.(io.Closer); ok { + closer.Close() + } + if lastErr != nil && buf.Len() == 0 { + http.Error(w, "No metrics encoded, last error:\n\n"+err.Error(), http.StatusInternalServerError) + return + } + header := w.Header() + header.Set(contentTypeHeader, string(contentType)) + header.Set(contentLengthHeader, fmt.Sprint(buf.Len())) + if encoding != "" { + header.Set(contentEncodingHeader, encoding) + } + w.Write(buf.Bytes()) + // TODO(beorn7): Consider streaming serving of metrics. + }) +} + +// HandlerErrorHandling defines how a Handler serving metrics will handle +// errors. +type HandlerErrorHandling int + +// These constants cause handlers serving metrics to behave as described if +// errors are encountered. +const ( + // Serve an HTTP status code 500 upon the first error + // encountered. Report the error message in the body. + HTTPErrorOnError HandlerErrorHandling = iota + // Ignore errors and try to serve as many metrics as possible. However, + // if no metrics can be served, serve an HTTP status code 500 and the + // last error message in the body. Only use this in deliberate "best + // effort" metrics collection scenarios. It is recommended to at least + // log errors (by providing an ErrorLog in HandlerOpts) to not mask + // errors completely. + ContinueOnError + // Panic upon the first error encountered (useful for "crash only" apps). + PanicOnError +) + +// Logger is the minimal interface HandlerOpts needs for logging. Note that +// log.Logger from the standard library implements this interface, and it is +// easy to implement by custom loggers, if they don't do so already anyway. +type Logger interface { + Println(v ...interface{}) +} + +// HandlerOpts specifies options how to serve metrics via an http.Handler. The +// zero value of HandlerOpts is a reasonable default. +type HandlerOpts struct { + // ErrorLog specifies an optional logger for errors collecting and + // serving metrics. If nil, errors are not logged at all. + ErrorLog Logger + // ErrorHandling defines how errors are handled. Note that errors are + // logged regardless of the configured ErrorHandling provided ErrorLog + // is not nil. + ErrorHandling HandlerErrorHandling + // If DisableCompression is true, the handler will never compress the + // response, even if requested by the client. + DisableCompression bool +} + +// decorateWriter wraps a writer to handle gzip compression if requested. It +// returns the decorated writer and the appropriate "Content-Encoding" header +// (which is empty if no compression is enabled). +func decorateWriter(request *http.Request, writer io.Writer, compressionDisabled bool) (io.Writer, string) { + if compressionDisabled { + return writer, "" + } + header := request.Header.Get(acceptEncodingHeader) + parts := strings.Split(header, ",") + for _, part := range parts { + part := strings.TrimSpace(part) + if part == "gzip" || strings.HasPrefix(part, "gzip;") { + return gzip.NewWriter(writer), "gzip" + } + } + return writer, "" +} diff --git a/prometheus/promhttp/http_test.go b/prometheus/promhttp/http_test.go new file mode 100644 index 0000000..4d0370e --- /dev/null +++ b/prometheus/promhttp/http_test.go @@ -0,0 +1,141 @@ +// Copyright 2016 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Copyright (c) 2013, The Prometheus Authors +// All rights reserved. +// +// Use of this source code is governed by a BSD-style license that can be found +// in the LICENSE file. + +package promhttp + +import ( + "bytes" + "errors" + "log" + "net/http" + "net/http/httptest" + "testing" + + "github.com/prometheus/client_golang/prometheus" +) + +type errorCollector struct{} + +func (e errorCollector) Describe(ch chan<- *prometheus.Desc) { + ch <- prometheus.NewDesc("invalid_metric", "not helpful", nil, nil) +} + +func (e errorCollector) Collect(ch chan<- prometheus.Metric) { + ch <- prometheus.NewInvalidMetric( + prometheus.NewDesc("invalid_metric", "not helpful", nil, nil), + errors.New("collect error"), + ) +} + +func TestHandlerErrorHandling(t *testing.T) { + + // Create a registry that collects a MetricFamily with two elements, + // another with one, and reports an error. + reg := prometheus.NewRegistry() + + cnt := prometheus.NewCounter(prometheus.CounterOpts{ + Name: "the_count", + Help: "Ah-ah-ah! Thunder and lightning!", + }) + reg.MustRegister(cnt) + + cntVec := prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "name", + Help: "docstring", + ConstLabels: prometheus.Labels{"constname": "constvalue"}, + }, + []string{"labelname"}, + ) + cntVec.WithLabelValues("val1").Inc() + cntVec.WithLabelValues("val2").Inc() + reg.MustRegister(cntVec) + + reg.MustRegister(errorCollector{}) + + logBuf := &bytes.Buffer{} + logger := log.New(logBuf, "", 0) + + writer := httptest.NewRecorder() + request, _ := http.NewRequest("GET", "/", nil) + request.Header.Add("Accept", "test/plain") + + errorHandler := HandlerFor(reg, HandlerOpts{ + ErrorLog: logger, + ErrorHandling: HTTPErrorOnError, + }) + continueHandler := HandlerFor(reg, HandlerOpts{ + ErrorLog: logger, + ErrorHandling: ContinueOnError, + }) + panicHandler := HandlerFor(reg, HandlerOpts{ + ErrorLog: logger, + ErrorHandling: PanicOnError, + }) + wantMsg := `error collecting metrics: 1 error(s) occurred: + +* error collecting metric Desc{fqName: "invalid_metric", help: "not helpful", constLabels: {}, variableLabels: []}: collect error +` + wantErrorBody := `An error has occurred during metrics collection: + +1 error(s) occurred: + +* error collecting metric Desc{fqName: "invalid_metric", help: "not helpful", constLabels: {}, variableLabels: []}: collect error +` + wantOKBody := `# HELP name docstring +# TYPE name counter +name{constname="constvalue",labelname="val1"} 1 +name{constname="constvalue",labelname="val2"} 1 +# HELP the_count Ah-ah-ah! Thunder and lightning! +# TYPE the_count counter +the_count 0 +` + + errorHandler.ServeHTTP(writer, request) + if got, want := writer.Code, http.StatusInternalServerError; got != want { + t.Errorf("got HTTP status code %d, want %d", got, want) + } + if got := logBuf.String(); got != wantMsg { + t.Errorf("got log message %q, want %q", got, wantMsg) + } + if got := writer.Body.String(); got != wantErrorBody { + t.Errorf("got body %q, want %q", got, wantErrorBody) + } + logBuf.Reset() + writer.Body.Reset() + writer.Code = http.StatusOK + + continueHandler.ServeHTTP(writer, request) + if got, want := writer.Code, http.StatusOK; got != want { + t.Errorf("got HTTP status code %d, want %d", got, want) + } + if got := logBuf.String(); got != wantMsg { + t.Errorf("got log message %q, want %q", got, wantMsg) + } + if got := writer.Body.String(); got != wantOKBody { + t.Errorf("got body %q, want %q", got, wantOKBody) + } + + defer func() { + if err := recover(); err == nil { + t.Error("expected panic from panicHandler") + } + }() + panicHandler.ServeHTTP(writer, request) +} diff --git a/prometheus/registry_test.go b/prometheus/registry_test.go index 35209dd..5df66ad 100644 --- a/prometheus/registry_test.go +++ b/prometheus/registry_test.go @@ -17,7 +17,7 @@ // Use of this source code is governed by a BSD-style license that can be found // in the LICENSE file. -package prometheus +package prometheus_test import ( "bytes" @@ -25,18 +25,22 @@ import ( "net/http/httptest" "testing" - "github.com/golang/protobuf/proto" dto "github.com/prometheus/client_model/go" + + "github.com/golang/protobuf/proto" "github.com/prometheus/common/expfmt" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" ) func testHandler(t testing.TB) { - metricVec := NewCounterVec( - CounterOpts{ + metricVec := prometheus.NewCounterVec( + prometheus.CounterOpts{ Name: "name", Help: "docstring", - ConstLabels: Labels{"constname": "constvalue"}, + ConstLabels: prometheus.Labels{"constname": "constvalue"}, }, []string{"labelname"}, ) @@ -213,7 +217,7 @@ metric: < var scenarios = []struct { headers map[string]string out output - collector Collector + collector prometheus.Collector externalMF []*dto.MetricFamily }{ { // 0 @@ -450,7 +454,7 @@ metric: < }, } for i, scenario := range scenarios { - registry := NewPedanticRegistry() + registry := prometheus.NewPedanticRegistry() if scenario.externalMF != nil { registry.SetInjectionHook(func() []*dto.MetricFamily { return scenario.externalMF @@ -461,7 +465,7 @@ metric: < registry.Register(scenario.collector) } writer := httptest.NewRecorder() - handler := InstrumentHandler("prometheus", HandlerFor(registry, HandlerOpts{})) + handler := prometheus.InstrumentHandler("prometheus", promhttp.HandlerFor(registry, promhttp.HandlerOpts{})) request, _ := http.NewRequest("GET", "/", nil) for key, value := range scenario.headers { request.Header.Add(key, value) @@ -497,30 +501,34 @@ func BenchmarkHandler(b *testing.B) { } func TestRegisterWithOrGet(t *testing.T) { - // Clean the default registry just to be sure. This is bad, but this + // Replace the default registerer just to be sure. This is bad, but this // whole test will go away once RegisterOrGet is removed. - DefaultRegistry = NewRegistry() - original := NewCounterVec( - CounterOpts{ + oldRegisterer := prometheus.DefaultRegisterer + defer func() { + prometheus.DefaultRegisterer = oldRegisterer + }() + prometheus.DefaultRegisterer = prometheus.NewRegistry() + original := prometheus.NewCounterVec( + prometheus.CounterOpts{ Name: "test", Help: "help", }, []string{"foo", "bar"}, ) - equalButNotSame := NewCounterVec( - CounterOpts{ + equalButNotSame := prometheus.NewCounterVec( + prometheus.CounterOpts{ Name: "test", Help: "help", }, []string{"foo", "bar"}, ) - if err := Register(original); err != nil { + if err := prometheus.Register(original); err != nil { t.Fatal(err) } - if err := Register(equalButNotSame); err == nil { + if err := prometheus.Register(equalButNotSame); err == nil { t.Fatal("expected error when registringe equal collector") } - existing, err := RegisterOrGet(equalButNotSame) + existing, err := prometheus.RegisterOrGet(equalButNotSame) if err != nil { t.Fatal(err) }