Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.
 
 

518 řádky
16 KiB

  1. // Copyright 2017 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
  14. import (
  15. "errors"
  16. "net/http"
  17. "strconv"
  18. "strings"
  19. "time"
  20. dto "github.com/prometheus/client_model/go"
  21. "github.com/prometheus/client_golang/prometheus"
  22. )
  23. // magicString is used for the hacky label test in checkLabels. Remove once fixed.
  24. const magicString = "zZgWfBxLqvG8kc8IMv3POi2Bb0tZI3vAnBx+gBaFi9FyPzB/CzKUer1yufDa"
  25. // InstrumentHandlerInFlight is a middleware that wraps the provided
  26. // http.Handler. It sets the provided prometheus.Gauge to the number of
  27. // requests currently handled by the wrapped http.Handler.
  28. //
  29. // See the example for InstrumentHandlerDuration for example usage.
  30. func InstrumentHandlerInFlight(g prometheus.Gauge, next http.Handler) http.Handler {
  31. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  32. g.Inc()
  33. defer g.Dec()
  34. next.ServeHTTP(w, r)
  35. })
  36. }
  37. // InstrumentHandlerDuration is a middleware that wraps the provided
  38. // http.Handler to observe the request duration with the provided ObserverVec.
  39. // The ObserverVec must have valid metric and label names and must have zero,
  40. // one, or two non-const non-curried labels. For those, the only allowed label
  41. // names are "code" and "method". The function panics otherwise. For the "method"
  42. // label a predefined default label value set is used to filter given values.
  43. // Values besides predefined values will count as `unknown` method.
  44. //`WithExtraMethods` can be used to add more methods to the set. The Observe
  45. // method of the Observer in the ObserverVec is called with the request duration
  46. // in seconds. Partitioning happens by HTTP status code and/or HTTP method if
  47. // the respective instance label names are present in the ObserverVec. For
  48. // unpartitioned observations, use an ObserverVec with zero labels. Note that
  49. // partitioning of Histograms is expensive and should be used judiciously.
  50. //
  51. // If the wrapped Handler does not set a status code, a status code of 200 is assumed.
  52. //
  53. // If the wrapped Handler panics, no values are reported.
  54. //
  55. // Note that this method is only guaranteed to never observe negative durations
  56. // if used with Go1.9+.
  57. func InstrumentHandlerDuration(obs prometheus.ObserverVec, next http.Handler, opts ...Option) http.HandlerFunc {
  58. mwOpts := &option{}
  59. for _, o := range opts {
  60. o(mwOpts)
  61. }
  62. code, method := checkLabels(obs)
  63. if code {
  64. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  65. now := time.Now()
  66. d := newDelegator(w, nil)
  67. next.ServeHTTP(d, r)
  68. obs.With(labels(code, method, r.Method, d.Status(), mwOpts.extraMethods...)).Observe(time.Since(now).Seconds())
  69. })
  70. }
  71. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  72. now := time.Now()
  73. next.ServeHTTP(w, r)
  74. obs.With(labels(code, method, r.Method, 0, mwOpts.extraMethods...)).Observe(time.Since(now).Seconds())
  75. })
  76. }
  77. // InstrumentHandlerCounter is a middleware that wraps the provided http.Handler
  78. // to observe the request result with the provided CounterVec. The CounterVec
  79. // must have valid metric and label names and must have zero, one, or two
  80. // non-const non-curried labels. For those, the only allowed label names are
  81. // "code" and "method". The function panics otherwise. For the "method"
  82. // label a predefined default label value set is used to filter given values.
  83. // Values besides predefined values will count as `unknown` method.
  84. // `WithExtraMethods` can be used to add more methods to the set. Partitioning of the
  85. // CounterVec happens by HTTP status code and/or HTTP method if the respective
  86. // instance label names are present in the CounterVec. For unpartitioned
  87. // counting, use a CounterVec with zero labels.
  88. //
  89. // If the wrapped Handler does not set a status code, a status code of 200 is assumed.
  90. //
  91. // If the wrapped Handler panics, the Counter is not incremented.
  92. //
  93. // See the example for InstrumentHandlerDuration for example usage.
  94. func InstrumentHandlerCounter(counter *prometheus.CounterVec, next http.Handler, opts ...Option) http.HandlerFunc {
  95. mwOpts := &option{}
  96. for _, o := range opts {
  97. o(mwOpts)
  98. }
  99. code, method := checkLabels(counter)
  100. if code {
  101. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  102. d := newDelegator(w, nil)
  103. next.ServeHTTP(d, r)
  104. counter.With(labels(code, method, r.Method, d.Status(), mwOpts.extraMethods...)).Inc()
  105. })
  106. }
  107. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  108. next.ServeHTTP(w, r)
  109. counter.With(labels(code, method, r.Method, 0, mwOpts.extraMethods...)).Inc()
  110. })
  111. }
  112. // InstrumentHandlerTimeToWriteHeader is a middleware that wraps the provided
  113. // http.Handler to observe with the provided ObserverVec the request duration
  114. // until the response headers are written. The ObserverVec must have valid
  115. // metric and label names and must have zero, one, or two non-const non-curried
  116. // labels. For those, the only allowed label names are "code" and "method". The
  117. // function panics otherwise. For the "method" label a predefined default label
  118. // value set is used to filter given values. Values besides predefined values
  119. // will count as `unknown` method.`WithExtraMethods` can be used to add more
  120. // methods to the set. The Observe method of the Observer in the
  121. // ObserverVec is called with the request duration in seconds. Partitioning
  122. // happens by HTTP status code and/or HTTP method if the respective instance
  123. // label names are present in the ObserverVec. For unpartitioned observations,
  124. // use an ObserverVec with zero labels. Note that partitioning of Histograms is
  125. // expensive and should be used judiciously.
  126. //
  127. // If the wrapped Handler panics before calling WriteHeader, no value is
  128. // reported.
  129. //
  130. // Note that this method is only guaranteed to never observe negative durations
  131. // if used with Go1.9+.
  132. //
  133. // See the example for InstrumentHandlerDuration for example usage.
  134. func InstrumentHandlerTimeToWriteHeader(obs prometheus.ObserverVec, next http.Handler, opts ...Option) http.HandlerFunc {
  135. mwOpts := &option{}
  136. for _, o := range opts {
  137. o(mwOpts)
  138. }
  139. code, method := checkLabels(obs)
  140. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  141. now := time.Now()
  142. d := newDelegator(w, func(status int) {
  143. obs.With(labels(code, method, r.Method, status, mwOpts.extraMethods...)).Observe(time.Since(now).Seconds())
  144. })
  145. next.ServeHTTP(d, r)
  146. })
  147. }
  148. // InstrumentHandlerRequestSize is a middleware that wraps the provided
  149. // http.Handler to observe the request size with the provided ObserverVec. The
  150. // ObserverVec must have valid metric and label names and must have zero, one,
  151. // or two non-const non-curried labels. For those, the only allowed label names
  152. // are "code" and "method". The function panics otherwise. For the "method"
  153. // label a predefined default label value set is used to filter given values.
  154. // Values besides predefined values will count as `unknown` method.
  155. // `WithExtraMethods` can be used to add more methods to the set. The Observe
  156. // method of the Observer in the ObserverVec is called with the request size in
  157. // bytes. Partitioning happens by HTTP status code and/or HTTP method if the
  158. // respective instance label names are present in the ObserverVec. For
  159. // unpartitioned observations, use an ObserverVec with zero labels. Note that
  160. // partitioning of Histograms is expensive and should be used judiciously.
  161. //
  162. // If the wrapped Handler does not set a status code, a status code of 200 is assumed.
  163. //
  164. // If the wrapped Handler panics, no values are reported.
  165. //
  166. // See the example for InstrumentHandlerDuration for example usage.
  167. func InstrumentHandlerRequestSize(obs prometheus.ObserverVec, next http.Handler, opts ...Option) http.HandlerFunc {
  168. mwOpts := &option{}
  169. for _, o := range opts {
  170. o(mwOpts)
  171. }
  172. code, method := checkLabels(obs)
  173. if code {
  174. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  175. d := newDelegator(w, nil)
  176. next.ServeHTTP(d, r)
  177. size := computeApproximateRequestSize(r)
  178. obs.With(labels(code, method, r.Method, d.Status(), mwOpts.extraMethods...)).Observe(float64(size))
  179. })
  180. }
  181. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  182. next.ServeHTTP(w, r)
  183. size := computeApproximateRequestSize(r)
  184. obs.With(labels(code, method, r.Method, 0, mwOpts.extraMethods...)).Observe(float64(size))
  185. })
  186. }
  187. // InstrumentHandlerResponseSize is a middleware that wraps the provided
  188. // http.Handler to observe the response size with the provided ObserverVec. The
  189. // ObserverVec must have valid metric and label names and must have zero, one,
  190. // or two non-const non-curried labels. For those, the only allowed label names
  191. // are "code" and "method". The function panics otherwise. For the "method"
  192. // label a predefined default label value set is used to filter given values.
  193. // Values besides predefined values will count as `unknown` method.
  194. // `WithExtraMethods` can be used to add more methods to the set. The Observe
  195. // method of the Observer in the ObserverVec is called with the response size in
  196. // bytes. Partitioning happens by HTTP status code and/or HTTP method if the
  197. // respective instance label names are present in the ObserverVec. For
  198. // unpartitioned observations, use an ObserverVec with zero labels. Note that
  199. // partitioning of Histograms is expensive and should be used judiciously.
  200. //
  201. // If the wrapped Handler does not set a status code, a status code of 200 is assumed.
  202. //
  203. // If the wrapped Handler panics, no values are reported.
  204. //
  205. // See the example for InstrumentHandlerDuration for example usage.
  206. func InstrumentHandlerResponseSize(obs prometheus.ObserverVec, next http.Handler, opts ...Option) http.Handler {
  207. mwOpts := &option{}
  208. for _, o := range opts {
  209. o(mwOpts)
  210. }
  211. code, method := checkLabels(obs)
  212. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  213. d := newDelegator(w, nil)
  214. next.ServeHTTP(d, r)
  215. obs.With(labels(code, method, r.Method, d.Status(), mwOpts.extraMethods...)).Observe(float64(d.Written()))
  216. })
  217. }
  218. // checkLabels returns whether the provided Collector has a non-const,
  219. // non-curried label named "code" and/or "method". It panics if the provided
  220. // Collector does not have a Desc or has more than one Desc or its Desc is
  221. // invalid. It also panics if the Collector has any non-const, non-curried
  222. // labels that are not named "code" or "method".
  223. func checkLabels(c prometheus.Collector) (code bool, method bool) {
  224. // TODO(beorn7): Remove this hacky way to check for instance labels
  225. // once Descriptors can have their dimensionality queried.
  226. var (
  227. desc *prometheus.Desc
  228. m prometheus.Metric
  229. pm dto.Metric
  230. lvs []string
  231. )
  232. // Get the Desc from the Collector.
  233. descc := make(chan *prometheus.Desc, 1)
  234. c.Describe(descc)
  235. select {
  236. case desc = <-descc:
  237. default:
  238. panic("no description provided by collector")
  239. }
  240. select {
  241. case <-descc:
  242. panic("more than one description provided by collector")
  243. default:
  244. }
  245. close(descc)
  246. // Make sure the Collector has a valid Desc by registering it with a
  247. // temporary registry.
  248. prometheus.NewRegistry().MustRegister(c)
  249. // Create a ConstMetric with the Desc. Since we don't know how many
  250. // variable labels there are, try for as long as it needs.
  251. for err := errors.New("dummy"); err != nil; lvs = append(lvs, magicString) {
  252. m, err = prometheus.NewConstMetric(desc, prometheus.UntypedValue, 0, lvs...)
  253. }
  254. // Write out the metric into a proto message and look at the labels.
  255. // If the value is not the magicString, it is a constLabel, which doesn't interest us.
  256. // If the label is curried, it doesn't interest us.
  257. // In all other cases, only "code" or "method" is allowed.
  258. if err := m.Write(&pm); err != nil {
  259. panic("error checking metric for labels")
  260. }
  261. for _, label := range pm.Label {
  262. name, value := label.GetName(), label.GetValue()
  263. if value != magicString || isLabelCurried(c, name) {
  264. continue
  265. }
  266. switch name {
  267. case "code":
  268. code = true
  269. case "method":
  270. method = true
  271. default:
  272. panic("metric partitioned with non-supported labels")
  273. }
  274. }
  275. return
  276. }
  277. func isLabelCurried(c prometheus.Collector, label string) bool {
  278. // This is even hackier than the label test above.
  279. // We essentially try to curry again and see if it works.
  280. // But for that, we need to type-convert to the two
  281. // types we use here, ObserverVec or *CounterVec.
  282. switch v := c.(type) {
  283. case *prometheus.CounterVec:
  284. if _, err := v.CurryWith(prometheus.Labels{label: "dummy"}); err == nil {
  285. return false
  286. }
  287. case prometheus.ObserverVec:
  288. if _, err := v.CurryWith(prometheus.Labels{label: "dummy"}); err == nil {
  289. return false
  290. }
  291. default:
  292. panic("unsupported metric vec type")
  293. }
  294. return true
  295. }
  296. // emptyLabels is a one-time allocation for non-partitioned metrics to avoid
  297. // unnecessary allocations on each request.
  298. var emptyLabels = prometheus.Labels{}
  299. func labels(code, method bool, reqMethod string, status int, extraMethods ...string) prometheus.Labels {
  300. if !(code || method) {
  301. return emptyLabels
  302. }
  303. labels := prometheus.Labels{}
  304. if code {
  305. labels["code"] = sanitizeCode(status)
  306. }
  307. if method {
  308. labels["method"] = sanitizeMethod(reqMethod, extraMethods...)
  309. }
  310. return labels
  311. }
  312. func computeApproximateRequestSize(r *http.Request) int {
  313. s := 0
  314. if r.URL != nil {
  315. s += len(r.URL.String())
  316. }
  317. s += len(r.Method)
  318. s += len(r.Proto)
  319. for name, values := range r.Header {
  320. s += len(name)
  321. for _, value := range values {
  322. s += len(value)
  323. }
  324. }
  325. s += len(r.Host)
  326. // N.B. r.Form and r.MultipartForm are assumed to be included in r.URL.
  327. if r.ContentLength != -1 {
  328. s += int(r.ContentLength)
  329. }
  330. return s
  331. }
  332. // If the wrapped http.Handler has a known method, it will be sanitized and returned.
  333. // Otherwise, "unknown" will be returned. The known method list can be extended
  334. // as needed by using extraMethods parameter.
  335. func sanitizeMethod(m string, extraMethods ...string) string {
  336. // See https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods for
  337. // the methods chosen as default.
  338. switch m {
  339. case "GET", "get":
  340. return "get"
  341. case "PUT", "put":
  342. return "put"
  343. case "HEAD", "head":
  344. return "head"
  345. case "POST", "post":
  346. return "post"
  347. case "DELETE", "delete":
  348. return "delete"
  349. case "CONNECT", "connect":
  350. return "connect"
  351. case "OPTIONS", "options":
  352. return "options"
  353. case "NOTIFY", "notify":
  354. return "notify"
  355. case "TRACE", "trace":
  356. return "trace"
  357. case "PATCH", "patch":
  358. return "patch"
  359. default:
  360. for _, method := range extraMethods {
  361. if strings.EqualFold(m, method) {
  362. return strings.ToLower(m)
  363. }
  364. }
  365. return "unknown"
  366. }
  367. }
  368. // If the wrapped http.Handler has not set a status code, i.e. the value is
  369. // currently 0, sanitizeCode will return 200, for consistency with behavior in
  370. // the stdlib.
  371. func sanitizeCode(s int) string {
  372. // See for accepted codes https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
  373. switch s {
  374. case 100:
  375. return "100"
  376. case 101:
  377. return "101"
  378. case 200, 0:
  379. return "200"
  380. case 201:
  381. return "201"
  382. case 202:
  383. return "202"
  384. case 203:
  385. return "203"
  386. case 204:
  387. return "204"
  388. case 205:
  389. return "205"
  390. case 206:
  391. return "206"
  392. case 300:
  393. return "300"
  394. case 301:
  395. return "301"
  396. case 302:
  397. return "302"
  398. case 304:
  399. return "304"
  400. case 305:
  401. return "305"
  402. case 307:
  403. return "307"
  404. case 400:
  405. return "400"
  406. case 401:
  407. return "401"
  408. case 402:
  409. return "402"
  410. case 403:
  411. return "403"
  412. case 404:
  413. return "404"
  414. case 405:
  415. return "405"
  416. case 406:
  417. return "406"
  418. case 407:
  419. return "407"
  420. case 408:
  421. return "408"
  422. case 409:
  423. return "409"
  424. case 410:
  425. return "410"
  426. case 411:
  427. return "411"
  428. case 412:
  429. return "412"
  430. case 413:
  431. return "413"
  432. case 414:
  433. return "414"
  434. case 415:
  435. return "415"
  436. case 416:
  437. return "416"
  438. case 417:
  439. return "417"
  440. case 418:
  441. return "418"
  442. case 500:
  443. return "500"
  444. case 501:
  445. return "501"
  446. case 502:
  447. return "502"
  448. case 503:
  449. return "503"
  450. case 504:
  451. return "504"
  452. case 505:
  453. return "505"
  454. case 428:
  455. return "428"
  456. case 429:
  457. return "429"
  458. case 431:
  459. return "431"
  460. case 511:
  461. return "511"
  462. default:
  463. if s >= 100 && s <= 599 {
  464. return strconv.Itoa(s)
  465. }
  466. return "unknown"
  467. }
  468. }