http.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  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. // Package promhttp provides tooling around HTTP servers and clients.
  14. //
  15. // First, the package allows the creation of http.Handler instances to expose
  16. // Prometheus metrics via HTTP. promhttp.Handler acts on the
  17. // prometheus.DefaultGatherer. With HandlerFor, you can create a handler for a
  18. // custom registry or anything that implements the Gatherer interface. It also
  19. // allows the creation of handlers that act differently on errors or allow to
  20. // log errors.
  21. //
  22. // Second, the package provides tooling to instrument instances of http.Handler
  23. // via middleware. Middleware wrappers follow the naming scheme
  24. // InstrumentHandlerX, where X describes the intended use of the middleware.
  25. // See each function's doc comment for specific details.
  26. //
  27. // Finally, the package allows for an http.RoundTripper to be instrumented via
  28. // middleware. Middleware wrappers follow the naming scheme
  29. // InstrumentRoundTripperX, where X describes the intended use of the
  30. // middleware. See each function's doc comment for specific details.
  31. package promhttp
  32. import (
  33. "bytes"
  34. "compress/gzip"
  35. "fmt"
  36. "io"
  37. "net/http"
  38. "strings"
  39. "sync"
  40. "time"
  41. "github.com/prometheus/common/expfmt"
  42. "github.com/prometheus/client_golang/prometheus"
  43. )
  44. const (
  45. contentTypeHeader = "Content-Type"
  46. contentLengthHeader = "Content-Length"
  47. contentEncodingHeader = "Content-Encoding"
  48. acceptEncodingHeader = "Accept-Encoding"
  49. )
  50. var bufPool sync.Pool
  51. func getBuf() *bytes.Buffer {
  52. buf := bufPool.Get()
  53. if buf == nil {
  54. return &bytes.Buffer{}
  55. }
  56. return buf.(*bytes.Buffer)
  57. }
  58. func giveBuf(buf *bytes.Buffer) {
  59. buf.Reset()
  60. bufPool.Put(buf)
  61. }
  62. // Handler returns an http.Handler for the prometheus.DefaultGatherer, using
  63. // default HandlerOpts, i.e. it reports the first error as an HTTP error, it has
  64. // no error logging, and it applies compression if requested by the client.
  65. //
  66. // The returned http.Handler is already instrumented using the
  67. // InstrumentMetricHandler function and the prometheus.DefaultRegisterer. If you
  68. // create multiple http.Handlers by separate calls of the Handler function, the
  69. // metrics used for instrumentation will be shared between them, providing
  70. // global scrape counts.
  71. //
  72. // This function is meant to cover the bulk of basic use cases. If you are doing
  73. // anything that requires more customization (including using a non-default
  74. // Gatherer, different instrumentation, and non-default HandlerOpts), use the
  75. // HandlerFor function. See there for details.
  76. func Handler() http.Handler {
  77. return InstrumentMetricHandler(
  78. prometheus.DefaultRegisterer, HandlerFor(prometheus.DefaultGatherer, HandlerOpts{}),
  79. )
  80. }
  81. // HandlerFor returns an uninstrumented http.Handler for the provided
  82. // Gatherer. The behavior of the Handler is defined by the provided
  83. // HandlerOpts. Thus, HandlerFor is useful to create http.Handlers for custom
  84. // Gatherers, with non-default HandlerOpts, and/or with custom (or no)
  85. // instrumentation. Use the InstrumentMetricHandler function to apply the same
  86. // kind of instrumentation as it is used by the Handler function.
  87. func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler {
  88. var inFlightSem chan struct{}
  89. if opts.MaxRequestsInFlight > 0 {
  90. inFlightSem = make(chan struct{}, opts.MaxRequestsInFlight)
  91. }
  92. h := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
  93. if inFlightSem != nil {
  94. select {
  95. case inFlightSem <- struct{}{}: // All good, carry on.
  96. defer func() { <-inFlightSem }()
  97. default:
  98. http.Error(w, fmt.Sprintf(
  99. "Limit of concurrent requests reached (%d), try again later.", opts.MaxRequestsInFlight,
  100. ), http.StatusServiceUnavailable)
  101. return
  102. }
  103. }
  104. mfs, err := reg.Gather()
  105. if err != nil {
  106. if opts.ErrorLog != nil {
  107. opts.ErrorLog.Println("error gathering metrics:", err)
  108. }
  109. switch opts.ErrorHandling {
  110. case PanicOnError:
  111. panic(err)
  112. case ContinueOnError:
  113. if len(mfs) == 0 {
  114. http.Error(w, "No metrics gathered, last error:\n\n"+err.Error(), http.StatusInternalServerError)
  115. return
  116. }
  117. case HTTPErrorOnError:
  118. http.Error(w, "An error has occurred during metrics gathering:\n\n"+err.Error(), http.StatusInternalServerError)
  119. return
  120. }
  121. }
  122. contentType := expfmt.Negotiate(req.Header)
  123. buf := getBuf()
  124. defer giveBuf(buf)
  125. writer, encoding := decorateWriter(req, buf, opts.DisableCompression)
  126. enc := expfmt.NewEncoder(writer, contentType)
  127. var lastErr error
  128. for _, mf := range mfs {
  129. if err := enc.Encode(mf); err != nil {
  130. lastErr = err
  131. if opts.ErrorLog != nil {
  132. opts.ErrorLog.Println("error encoding metric family:", err)
  133. }
  134. switch opts.ErrorHandling {
  135. case PanicOnError:
  136. panic(err)
  137. case ContinueOnError:
  138. // Handled later.
  139. case HTTPErrorOnError:
  140. http.Error(w, "An error has occurred during metrics encoding:\n\n"+err.Error(), http.StatusInternalServerError)
  141. return
  142. }
  143. }
  144. }
  145. if closer, ok := writer.(io.Closer); ok {
  146. closer.Close()
  147. }
  148. if lastErr != nil && buf.Len() == 0 {
  149. http.Error(w, "No metrics encoded, last error:\n\n"+lastErr.Error(), http.StatusInternalServerError)
  150. return
  151. }
  152. header := w.Header()
  153. header.Set(contentTypeHeader, string(contentType))
  154. header.Set(contentLengthHeader, fmt.Sprint(buf.Len()))
  155. if encoding != "" {
  156. header.Set(contentEncodingHeader, encoding)
  157. }
  158. if _, err := w.Write(buf.Bytes()); err != nil && opts.ErrorLog != nil {
  159. opts.ErrorLog.Println("error while sending encoded metrics:", err)
  160. }
  161. // TODO(beorn7): Consider streaming serving of metrics.
  162. })
  163. if opts.Timeout <= 0 {
  164. return h
  165. }
  166. return http.TimeoutHandler(h, opts.Timeout, fmt.Sprintf(
  167. "Exceeded configured timeout of %v.\n",
  168. opts.Timeout,
  169. ))
  170. }
  171. // InstrumentMetricHandler is usually used with an http.Handler returned by the
  172. // HandlerFor function. It instruments the provided http.Handler with two
  173. // metrics: A counter vector "promhttp_metric_handler_requests_total" to count
  174. // scrapes partitioned by HTTP status code, and a gauge
  175. // "promhttp_metric_handler_requests_in_flight" to track the number of
  176. // simultaneous scrapes. This function idempotently registers collectors for
  177. // both metrics with the provided Registerer. It panics if the registration
  178. // fails. The provided metrics are useful to see how many scrapes hit the
  179. // monitored target (which could be from different Prometheus servers or other
  180. // scrapers), and how often they overlap (which would result in more than one
  181. // scrape in flight at the same time). Note that the scrapes-in-flight gauge
  182. // will contain the scrape by which it is exposed, while the scrape counter will
  183. // only get incremented after the scrape is complete (as only then the status
  184. // code is known). For tracking scrape durations, use the
  185. // "scrape_duration_seconds" gauge created by the Prometheus server upon each
  186. // scrape.
  187. func InstrumentMetricHandler(reg prometheus.Registerer, handler http.Handler) http.Handler {
  188. cnt := prometheus.NewCounterVec(
  189. prometheus.CounterOpts{
  190. Name: "promhttp_metric_handler_requests_total",
  191. Help: "Total number of scrapes by HTTP status code.",
  192. },
  193. []string{"code"},
  194. )
  195. // Initialize the most likely HTTP status codes.
  196. cnt.WithLabelValues("200")
  197. cnt.WithLabelValues("500")
  198. cnt.WithLabelValues("503")
  199. if err := reg.Register(cnt); err != nil {
  200. if are, ok := err.(prometheus.AlreadyRegisteredError); ok {
  201. cnt = are.ExistingCollector.(*prometheus.CounterVec)
  202. } else {
  203. panic(err)
  204. }
  205. }
  206. gge := prometheus.NewGauge(prometheus.GaugeOpts{
  207. Name: "promhttp_metric_handler_requests_in_flight",
  208. Help: "Current number of scrapes being served.",
  209. })
  210. if err := reg.Register(gge); err != nil {
  211. if are, ok := err.(prometheus.AlreadyRegisteredError); ok {
  212. gge = are.ExistingCollector.(prometheus.Gauge)
  213. } else {
  214. panic(err)
  215. }
  216. }
  217. return InstrumentHandlerCounter(cnt, InstrumentHandlerInFlight(gge, handler))
  218. }
  219. // HandlerErrorHandling defines how a Handler serving metrics will handle
  220. // errors.
  221. type HandlerErrorHandling int
  222. // These constants cause handlers serving metrics to behave as described if
  223. // errors are encountered.
  224. const (
  225. // Serve an HTTP status code 500 upon the first error
  226. // encountered. Report the error message in the body.
  227. HTTPErrorOnError HandlerErrorHandling = iota
  228. // Ignore errors and try to serve as many metrics as possible. However,
  229. // if no metrics can be served, serve an HTTP status code 500 and the
  230. // last error message in the body. Only use this in deliberate "best
  231. // effort" metrics collection scenarios. It is recommended to at least
  232. // log errors (by providing an ErrorLog in HandlerOpts) to not mask
  233. // errors completely.
  234. ContinueOnError
  235. // Panic upon the first error encountered (useful for "crash only" apps).
  236. PanicOnError
  237. )
  238. // Logger is the minimal interface HandlerOpts needs for logging. Note that
  239. // log.Logger from the standard library implements this interface, and it is
  240. // easy to implement by custom loggers, if they don't do so already anyway.
  241. type Logger interface {
  242. Println(v ...interface{})
  243. }
  244. // HandlerOpts specifies options how to serve metrics via an http.Handler. The
  245. // zero value of HandlerOpts is a reasonable default.
  246. type HandlerOpts struct {
  247. // ErrorLog specifies an optional logger for errors collecting and
  248. // serving metrics. If nil, errors are not logged at all.
  249. ErrorLog Logger
  250. // ErrorHandling defines how errors are handled. Note that errors are
  251. // logged regardless of the configured ErrorHandling provided ErrorLog
  252. // is not nil.
  253. ErrorHandling HandlerErrorHandling
  254. // If DisableCompression is true, the handler will never compress the
  255. // response, even if requested by the client.
  256. DisableCompression bool
  257. // The number of concurrent HTTP requests is limited to
  258. // MaxRequestsInFlight. Additional requests are responded to with 503
  259. // Service Unavailable and a suitable message in the body. If
  260. // MaxRequestsInFlight is 0 or negative, no limit is applied.
  261. MaxRequestsInFlight int
  262. // If handling a request takes longer than Timeout, it is responded to
  263. // with 503 ServiceUnavailable and a suitable Message. No timeout is
  264. // applied if Timeout is 0 or negative. Note that with the current
  265. // implementation, reaching the timeout simply ends the HTTP requests as
  266. // described above (and even that only if sending of the body hasn't
  267. // started yet), while the bulk work of gathering all the metrics keeps
  268. // running in the background (with the eventual result to be thrown
  269. // away). Until the implementation is improved, it is recommended to
  270. // implement a separate timeout in potentially slow Collectors.
  271. Timeout time.Duration
  272. }
  273. // decorateWriter wraps a writer to handle gzip compression if requested. It
  274. // returns the decorated writer and the appropriate "Content-Encoding" header
  275. // (which is empty if no compression is enabled).
  276. func decorateWriter(request *http.Request, writer io.Writer, compressionDisabled bool) (io.Writer, string) {
  277. if compressionDisabled {
  278. return writer, ""
  279. }
  280. header := request.Header.Get(acceptEncodingHeader)
  281. parts := strings.Split(header, ",")
  282. for _, part := range parts {
  283. part = strings.TrimSpace(part)
  284. if part == "gzip" || strings.HasPrefix(part, "gzip;") {
  285. return gzip.NewWriter(writer), "gzip"
  286. }
  287. }
  288. return writer, ""
  289. }