http.go 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. // Copyright 2016 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
  5. //
  6. // http://www.apache.org/licenses/LICENSE-2.0
  7. //
  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.
  13. // Copyright (c) 2013, The Prometheus Authors
  14. // All rights reserved.
  15. //
  16. // Use of this source code is governed by a BSD-style license that can be found
  17. // in the LICENSE file.
  18. // Package promhttp contains functions to create http.Handler instances to
  19. // expose Prometheus metrics via HTTP. In later versions of this package, it
  20. // will also contain tooling to instrument instances of http.Handler and
  21. // http.RoundTripper.
  22. //
  23. // promhttp.Handler acts on the prometheus.DefaultGatherer. With HandlerFor,
  24. // you can create a handler for a custom registry or anything that implements
  25. // the Gatherer interface. It also allows to create handlers that act
  26. // differently on errors or allow to log errors.
  27. package promhttp
  28. import (
  29. "bytes"
  30. "compress/gzip"
  31. "fmt"
  32. "io"
  33. "net/http"
  34. "strings"
  35. "sync"
  36. "github.com/prometheus/common/expfmt"
  37. "github.com/prometheus/client_golang/prometheus"
  38. )
  39. const (
  40. contentTypeHeader = "Content-Type"
  41. contentLengthHeader = "Content-Length"
  42. contentEncodingHeader = "Content-Encoding"
  43. acceptEncodingHeader = "Accept-Encoding"
  44. )
  45. var bufPool sync.Pool
  46. func getBuf() *bytes.Buffer {
  47. buf := bufPool.Get()
  48. if buf == nil {
  49. return &bytes.Buffer{}
  50. }
  51. return buf.(*bytes.Buffer)
  52. }
  53. func giveBuf(buf *bytes.Buffer) {
  54. buf.Reset()
  55. bufPool.Put(buf)
  56. }
  57. // Handler returns an HTTP handler for the prometheus.DefaultGatherer. The
  58. // Handler uses the default HandlerOpts, i.e. report the first error as an HTTP
  59. // error, no error logging, and compression if requested by the client.
  60. //
  61. // If you want to create a Handler for the DefaultGatherer with different
  62. // HandlerOpts, create it with HandlerFor with prometheus.DefaultGatherer and
  63. // your desired HandlerOpts.
  64. func Handler() http.Handler {
  65. return HandlerFor(prometheus.DefaultGatherer, HandlerOpts{})
  66. }
  67. // HandlerFor returns an http.Handler for the provided Gatherer. The behavior
  68. // of the Handler is defined by the provided HandlerOpts.
  69. func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler {
  70. return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
  71. mfs, err := reg.Gather()
  72. if err != nil {
  73. if opts.ErrorLog != nil {
  74. opts.ErrorLog.Println("error gathering metrics:", err)
  75. }
  76. switch opts.ErrorHandling {
  77. case PanicOnError:
  78. panic(err)
  79. case ContinueOnError:
  80. if len(mfs) == 0 {
  81. http.Error(w, "No metrics gathered, last error:\n\n"+err.Error(), http.StatusInternalServerError)
  82. return
  83. }
  84. case HTTPErrorOnError:
  85. http.Error(w, "An error has occurred during metrics gathering:\n\n"+err.Error(), http.StatusInternalServerError)
  86. return
  87. }
  88. }
  89. contentType := expfmt.Negotiate(req.Header)
  90. buf := getBuf()
  91. defer giveBuf(buf)
  92. writer, encoding := decorateWriter(req, buf, opts.DisableCompression)
  93. enc := expfmt.NewEncoder(writer, contentType)
  94. var lastErr error
  95. for _, mf := range mfs {
  96. if err := enc.Encode(mf); err != nil {
  97. lastErr = err
  98. if opts.ErrorLog != nil {
  99. opts.ErrorLog.Println("error encoding metric family:", err)
  100. }
  101. switch opts.ErrorHandling {
  102. case PanicOnError:
  103. panic(err)
  104. case ContinueOnError:
  105. // Handled later.
  106. case HTTPErrorOnError:
  107. http.Error(w, "An error has occurred during metrics encoding:\n\n"+err.Error(), http.StatusInternalServerError)
  108. return
  109. }
  110. }
  111. }
  112. if closer, ok := writer.(io.Closer); ok {
  113. closer.Close()
  114. }
  115. if lastErr != nil && buf.Len() == 0 {
  116. http.Error(w, "No metrics encoded, last error:\n\n"+err.Error(), http.StatusInternalServerError)
  117. return
  118. }
  119. header := w.Header()
  120. header.Set(contentTypeHeader, string(contentType))
  121. header.Set(contentLengthHeader, fmt.Sprint(buf.Len()))
  122. if encoding != "" {
  123. header.Set(contentEncodingHeader, encoding)
  124. }
  125. w.Write(buf.Bytes())
  126. // TODO(beorn7): Consider streaming serving of metrics.
  127. })
  128. }
  129. // HandlerErrorHandling defines how a Handler serving metrics will handle
  130. // errors.
  131. type HandlerErrorHandling int
  132. // These constants cause handlers serving metrics to behave as described if
  133. // errors are encountered.
  134. const (
  135. // Serve an HTTP status code 500 upon the first error
  136. // encountered. Report the error message in the body.
  137. HTTPErrorOnError HandlerErrorHandling = iota
  138. // Ignore errors and try to serve as many metrics as possible. However,
  139. // if no metrics can be served, serve an HTTP status code 500 and the
  140. // last error message in the body. Only use this in deliberate "best
  141. // effort" metrics collection scenarios. It is recommended to at least
  142. // log errors (by providing an ErrorLog in HandlerOpts) to not mask
  143. // errors completely.
  144. ContinueOnError
  145. // Panic upon the first error encountered (useful for "crash only" apps).
  146. PanicOnError
  147. )
  148. // Logger is the minimal interface HandlerOpts needs for logging. Note that
  149. // log.Logger from the standard library implements this interface, and it is
  150. // easy to implement by custom loggers, if they don't do so already anyway.
  151. type Logger interface {
  152. Println(v ...interface{})
  153. }
  154. // HandlerOpts specifies options how to serve metrics via an http.Handler. The
  155. // zero value of HandlerOpts is a reasonable default.
  156. type HandlerOpts struct {
  157. // ErrorLog specifies an optional logger for errors collecting and
  158. // serving metrics. If nil, errors are not logged at all.
  159. ErrorLog Logger
  160. // ErrorHandling defines how errors are handled. Note that errors are
  161. // logged regardless of the configured ErrorHandling provided ErrorLog
  162. // is not nil.
  163. ErrorHandling HandlerErrorHandling
  164. // If DisableCompression is true, the handler will never compress the
  165. // response, even if requested by the client.
  166. DisableCompression bool
  167. }
  168. // decorateWriter wraps a writer to handle gzip compression if requested. It
  169. // returns the decorated writer and the appropriate "Content-Encoding" header
  170. // (which is empty if no compression is enabled).
  171. func decorateWriter(request *http.Request, writer io.Writer, compressionDisabled bool) (io.Writer, string) {
  172. if compressionDisabled {
  173. return writer, ""
  174. }
  175. header := request.Header.Get(acceptEncodingHeader)
  176. parts := strings.Split(header, ",")
  177. for _, part := range parts {
  178. part := strings.TrimSpace(part)
  179. if part == "gzip" || strings.HasPrefix(part, "gzip;") {
  180. return gzip.NewWriter(writer), "gzip"
  181. }
  182. }
  183. return writer, ""
  184. }