1 // Copyright 2014 The Prometheus Authors
2 // Licensed under the Apache License, Version 2.0 (the "License");
3 // you may not use this file except in compliance with the License.
4 // You may obtain a copy of the License at
6 // http://www.apache.org/licenses/LICENSE-2.0
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
27 "github.com/prometheus/common/expfmt"
30 // TODO(beorn7): Remove this whole file. It is a partial mirror of
31 // promhttp/http.go (to avoid circular import chains) where everything HTTP
32 // related should live. The functions here are just for avoiding
33 // breakage. Everything is deprecated.
36 contentTypeHeader = "Content-Type"
37 contentEncodingHeader = "Content-Encoding"
38 acceptEncodingHeader = "Accept-Encoding"
41 var gzipPool = sync.Pool{
42 New: func() interface{} {
43 return gzip.NewWriter(nil)
47 // Handler returns an HTTP handler for the DefaultGatherer. It is
48 // already instrumented with InstrumentHandler (using "prometheus" as handler
51 // Deprecated: Please note the issues described in the doc comment of
52 // InstrumentHandler. You might want to consider using promhttp.Handler instead.
53 func Handler() http.Handler {
54 return InstrumentHandler("prometheus", UninstrumentedHandler())
57 // UninstrumentedHandler returns an HTTP handler for the DefaultGatherer.
59 // Deprecated: Use promhttp.HandlerFor(DefaultGatherer, promhttp.HandlerOpts{})
60 // instead. See there for further documentation.
61 func UninstrumentedHandler() http.Handler {
62 return http.HandlerFunc(func(rsp http.ResponseWriter, req *http.Request) {
63 mfs, err := DefaultGatherer.Gather()
69 contentType := expfmt.Negotiate(req.Header)
70 header := rsp.Header()
71 header.Set(contentTypeHeader, string(contentType))
74 if gzipAccepted(req.Header) {
75 header.Set(contentEncodingHeader, "gzip")
76 gz := gzipPool.Get().(*gzip.Writer)
77 defer gzipPool.Put(gz)
85 enc := expfmt.NewEncoder(w, contentType)
87 for _, mf := range mfs {
88 if err := enc.Encode(mf); err != nil {
96 var instLabels = []string{"method", "code"}
98 type nower interface {
102 type nowFunc func() time.Time
104 func (n nowFunc) Now() time.Time {
108 var now nower = nowFunc(func() time.Time {
112 // InstrumentHandler wraps the given HTTP handler for instrumentation. It
113 // registers four metric collectors (if not already done) and reports HTTP
114 // metrics to the (newly or already) registered collectors: http_requests_total
115 // (CounterVec), http_request_duration_microseconds (Summary),
116 // http_request_size_bytes (Summary), http_response_size_bytes (Summary). Each
117 // has a constant label named "handler" with the provided handlerName as
118 // value. http_requests_total is a metric vector partitioned by HTTP method
119 // (label name "method") and HTTP status code (label name "code").
121 // Deprecated: InstrumentHandler has several issues. Use the tooling provided in
122 // package promhttp instead. The issues are the following: (1) It uses Summaries
123 // rather than Histograms. Summaries are not useful if aggregation across
124 // multiple instances is required. (2) It uses microseconds as unit, which is
125 // deprecated and should be replaced by seconds. (3) The size of the request is
126 // calculated in a separate goroutine. Since this calculator requires access to
127 // the request header, it creates a race with any writes to the header performed
128 // during request handling. httputil.ReverseProxy is a prominent example for a
129 // handler performing such writes. (4) It has additional issues with HTTP/2, cf.
130 // https://github.com/prometheus/client_golang/issues/272.
131 func InstrumentHandler(handlerName string, handler http.Handler) http.HandlerFunc {
132 return InstrumentHandlerFunc(handlerName, handler.ServeHTTP)
135 // InstrumentHandlerFunc wraps the given function for instrumentation. It
136 // otherwise works in the same way as InstrumentHandler (and shares the same
139 // Deprecated: InstrumentHandlerFunc is deprecated for the same reasons as
140 // InstrumentHandler is. Use the tooling provided in package promhttp instead.
141 func InstrumentHandlerFunc(handlerName string, handlerFunc func(http.ResponseWriter, *http.Request)) http.HandlerFunc {
142 return InstrumentHandlerFuncWithOpts(
145 ConstLabels: Labels{"handler": handlerName},
146 Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
152 // InstrumentHandlerWithOpts works like InstrumentHandler (and shares the same
153 // issues) but provides more flexibility (at the cost of a more complex call
154 // syntax). As InstrumentHandler, this function registers four metric
155 // collectors, but it uses the provided SummaryOpts to create them. However, the
156 // fields "Name" and "Help" in the SummaryOpts are ignored. "Name" is replaced
157 // by "requests_total", "request_duration_microseconds", "request_size_bytes",
158 // and "response_size_bytes", respectively. "Help" is replaced by an appropriate
159 // help string. The names of the variable labels of the http_requests_total
160 // CounterVec are "method" (get, post, etc.), and "code" (HTTP status code).
162 // If InstrumentHandlerWithOpts is called as follows, it mimics exactly the
163 // behavior of InstrumentHandler:
165 // prometheus.InstrumentHandlerWithOpts(
166 // prometheus.SummaryOpts{
167 // Subsystem: "http",
168 // ConstLabels: prometheus.Labels{"handler": handlerName},
173 // Technical detail: "requests_total" is a CounterVec, not a SummaryVec, so it
174 // cannot use SummaryOpts. Instead, a CounterOpts struct is created internally,
175 // and all its fields are set to the equally named fields in the provided
178 // Deprecated: InstrumentHandlerWithOpts is deprecated for the same reasons as
179 // InstrumentHandler is. Use the tooling provided in package promhttp instead.
180 func InstrumentHandlerWithOpts(opts SummaryOpts, handler http.Handler) http.HandlerFunc {
181 return InstrumentHandlerFuncWithOpts(opts, handler.ServeHTTP)
184 // InstrumentHandlerFuncWithOpts works like InstrumentHandlerFunc (and shares
185 // the same issues) but provides more flexibility (at the cost of a more complex
186 // call syntax). See InstrumentHandlerWithOpts for details how the provided
187 // SummaryOpts are used.
189 // Deprecated: InstrumentHandlerFuncWithOpts is deprecated for the same reasons
190 // as InstrumentHandler is. Use the tooling provided in package promhttp instead.
191 func InstrumentHandlerFuncWithOpts(opts SummaryOpts, handlerFunc func(http.ResponseWriter, *http.Request)) http.HandlerFunc {
192 reqCnt := NewCounterVec(
194 Namespace: opts.Namespace,
195 Subsystem: opts.Subsystem,
196 Name: "requests_total",
197 Help: "Total number of HTTP requests made.",
198 ConstLabels: opts.ConstLabels,
202 if err := Register(reqCnt); err != nil {
203 if are, ok := err.(AlreadyRegisteredError); ok {
204 reqCnt = are.ExistingCollector.(*CounterVec)
210 opts.Name = "request_duration_microseconds"
211 opts.Help = "The HTTP request latencies in microseconds."
212 reqDur := NewSummary(opts)
213 if err := Register(reqDur); err != nil {
214 if are, ok := err.(AlreadyRegisteredError); ok {
215 reqDur = are.ExistingCollector.(Summary)
221 opts.Name = "request_size_bytes"
222 opts.Help = "The HTTP request sizes in bytes."
223 reqSz := NewSummary(opts)
224 if err := Register(reqSz); err != nil {
225 if are, ok := err.(AlreadyRegisteredError); ok {
226 reqSz = are.ExistingCollector.(Summary)
232 opts.Name = "response_size_bytes"
233 opts.Help = "The HTTP response sizes in bytes."
234 resSz := NewSummary(opts)
235 if err := Register(resSz); err != nil {
236 if are, ok := err.(AlreadyRegisteredError); ok {
237 resSz = are.ExistingCollector.(Summary)
243 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
246 delegate := &responseWriterDelegator{ResponseWriter: w}
247 out := computeApproximateRequestSize(r)
249 _, cn := w.(http.CloseNotifier)
250 _, fl := w.(http.Flusher)
251 _, hj := w.(http.Hijacker)
252 _, rf := w.(io.ReaderFrom)
253 var rw http.ResponseWriter
254 if cn && fl && hj && rf {
255 rw = &fancyResponseWriterDelegator{delegate}
261 elapsed := float64(time.Since(now)) / float64(time.Microsecond)
263 method := sanitizeMethod(r.Method)
264 code := sanitizeCode(delegate.status)
265 reqCnt.WithLabelValues(method, code).Inc()
266 reqDur.Observe(elapsed)
267 resSz.Observe(float64(delegate.written))
268 reqSz.Observe(float64(<-out))
272 func computeApproximateRequestSize(r *http.Request) <-chan int {
273 // Get URL length in current goroutine for avoiding a race condition.
274 // HandlerFunc that runs in parallel may modify the URL.
277 s += len(r.URL.String())
280 out := make(chan int, 1)
285 for name, values := range r.Header {
287 for _, value := range values {
293 // N.B. r.Form and r.MultipartForm are assumed to be included in r.URL.
295 if r.ContentLength != -1 {
296 s += int(r.ContentLength)
305 type responseWriterDelegator struct {
313 func (r *responseWriterDelegator) WriteHeader(code int) {
316 r.ResponseWriter.WriteHeader(code)
319 func (r *responseWriterDelegator) Write(b []byte) (int, error) {
321 r.WriteHeader(http.StatusOK)
323 n, err := r.ResponseWriter.Write(b)
324 r.written += int64(n)
328 type fancyResponseWriterDelegator struct {
329 *responseWriterDelegator
332 func (f *fancyResponseWriterDelegator) CloseNotify() <-chan bool {
333 return f.ResponseWriter.(http.CloseNotifier).CloseNotify()
336 func (f *fancyResponseWriterDelegator) Flush() {
337 f.ResponseWriter.(http.Flusher).Flush()
340 func (f *fancyResponseWriterDelegator) Hijack() (net.Conn, *bufio.ReadWriter, error) {
341 return f.ResponseWriter.(http.Hijacker).Hijack()
344 func (f *fancyResponseWriterDelegator) ReadFrom(r io.Reader) (int64, error) {
346 f.WriteHeader(http.StatusOK)
348 n, err := f.ResponseWriter.(io.ReaderFrom).ReadFrom(r)
353 func sanitizeMethod(m string) string {
363 case "DELETE", "delete":
365 case "CONNECT", "connect":
367 case "OPTIONS", "options":
369 case "NOTIFY", "notify":
372 return strings.ToLower(m)
376 func sanitizeCode(s int) string {
473 return strconv.Itoa(s)
477 // gzipAccepted returns whether the client will accept gzip-encoded content.
478 func gzipAccepted(header http.Header) bool {
479 a := header.Get(acceptEncodingHeader)
480 parts := strings.Split(a, ",")
481 for _, part := range parts {
482 part = strings.TrimSpace(part)
483 if part == "gzip" || strings.HasPrefix(part, "gzip;") {
490 // httpError removes any content-encoding header and then calls http.Error with
491 // the provided error and http.StatusInternalServerErrer. Error contents is
492 // supposed to be uncompressed plain text. However, same as with a plain
493 // http.Error, any header settings will be void if the header has already been
494 // sent. The error message will still be written to the writer, but it will
495 // probably be of limited use.
496 func httpError(rsp http.ResponseWriter, err error) {
497 rsp.Header().Del(contentEncodingHeader)
500 "An error has occurred while serving metrics:\n\n"+err.Error(),
501 http.StatusInternalServerError,