Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

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