25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

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