|
| 1 | +From 253029f7ffbade99588df59a8b89a35d99197fe0 Mon Sep 17 00:00:00 2001 |
| 2 | +From: Tobias Brick <tobiasb@microsoft.com> |
| 3 | +Date: Tue, 18 Jan 2022 10:19:28 +0100 |
| 4 | +Subject: [PATCH] Port upstream patch |
| 5 | + https://github.com/prometheus/client_golang/commit/9075cdf61646b5adf54d3ba77a0e4f6c65cb4fd7 |
| 6 | + |
| 7 | +Differences: |
| 8 | +- Removed tests |
| 9 | +- Removed some comments that don't merge |
| 10 | +- Line numbers and such |
| 11 | + |
| 12 | +Based on: |
| 13 | + |
| 14 | +From 9075cdf61646b5adf54d3ba77a0e4f6c65cb4fd7 Mon Sep 17 00:00:00 2001 |
| 15 | +From: Kemal Akkoyun <kakkoyun@users.noreply.github.com> |
| 16 | +Date: Tue, 18 Jan 2022 10:19:28 +0100 |
| 17 | +Subject: [PATCH] promhttp: Check validity of method and code label values |
| 18 | + (#962) |
| 19 | + |
| 20 | +* Check validity of method and code label values |
| 21 | + |
| 22 | +Signed-off-by: Kemal Akkoyun <kakkoyun@gmail.com> |
| 23 | + |
| 24 | +* Use more flexibly functional option pattern for configuration |
| 25 | + |
| 26 | +Signed-off-by: Kemal Akkoyun <kakkoyun@gmail.com> |
| 27 | + |
| 28 | +* Update documentation |
| 29 | + |
| 30 | +Signed-off-by: Kemal Akkoyun <kakkoyun@gmail.com> |
| 31 | + |
| 32 | +* Simplify |
| 33 | + |
| 34 | +Signed-off-by: Kemal Akkoyun <kakkoyun@gmail.com> |
| 35 | + |
| 36 | +* Fix inconsistent method naming |
| 37 | + |
| 38 | +Signed-off-by: Kemal Akkoyun <kakkoyun@gmail.com> |
| 39 | +--- |
| 40 | + prometheus/promhttp/instrument_client.go | 28 ++++++-- |
| 41 | + prometheus/promhttp/instrument_server.go | 82 ++++++++++++++++++------ |
| 42 | + prometheus/promhttp/option.go | 31 +++++++++ |
| 43 | + 3 files changed, 116 insertions(+), 25 deletions(-) |
| 44 | + create mode 100644 prometheus/promhttp/option.go |
| 45 | + |
| 46 | +diff --git a/prometheus/promhttp/instrument_client.go b/prometheus/promhttp/instrument_client.go |
| 47 | +index 83c49b6..861b4d2 100644 |
| 48 | +--- a/prometheus/promhttp/instrument_client.go |
| 49 | ++++ b/prometheus/promhttp/instrument_client.go |
| 50 | +@@ -49,7 +49,10 @@ func InstrumentRoundTripperInFlight(gauge prometheus.Gauge, next http.RoundTripp |
| 51 | + // http.RoundTripper to observe the request result with the provided CounterVec. |
| 52 | + // The CounterVec must have zero, one, or two non-const non-curried labels. For |
| 53 | + // those, the only allowed label names are "code" and "method". The function |
| 54 | +-// panics otherwise. Partitioning of the CounterVec happens by HTTP status code |
| 55 | ++// panics otherwise. For the "method" label a predefined default label value set |
| 56 | ++// is used to filter given values. Values besides predefined values will count |
| 57 | ++// as `unknown` method.`WithExtraMethods` can be used to add more |
| 58 | ++// methods to the set. Partitioning of the CounterVec happens by HTTP status code |
| 59 | + // and/or HTTP method if the respective instance label names are present in the |
| 60 | + // CounterVec. For unpartitioned counting, use a CounterVec with zero labels. |
| 61 | + // |
| 62 | +@@ -57,13 +60,18 @@ func InstrumentRoundTripperInFlight(gauge prometheus.Gauge, next http.RoundTripp |
| 63 | + // is not incremented. |
| 64 | + // |
| 65 | + // See the example for ExampleInstrumentRoundTripperDuration for example usage. |
| 66 | +-func InstrumentRoundTripperCounter(counter *prometheus.CounterVec, next http.RoundTripper) RoundTripperFunc { |
| 67 | ++func InstrumentRoundTripperCounter(counter *prometheus.CounterVec, next http.RoundTripper, opts ...Option) RoundTripperFunc { |
| 68 | ++ rtOpts := &option{} |
| 69 | ++ for _, o := range opts { |
| 70 | ++ o(rtOpts) |
| 71 | ++ } |
| 72 | ++ |
| 73 | + code, method := checkLabels(counter) |
| 74 | + |
| 75 | + return RoundTripperFunc(func(r *http.Request) (*http.Response, error) { |
| 76 | + resp, err := next.RoundTrip(r) |
| 77 | + if err == nil { |
| 78 | +- counter.With(labels(code, method, r.Method, resp.StatusCode)).Inc() |
| 79 | ++ counter.With(labels(code, method, r.Method, resp.StatusCode, rtOpts.extraMethods...)).Inc() |
| 80 | + } |
| 81 | + return resp, err |
| 82 | + }) |
| 83 | +@@ -73,7 +81,10 @@ func InstrumentRoundTripperCounter(counter *prometheus.CounterVec, next http.Rou |
| 84 | + // http.RoundTripper to observe the request duration with the provided |
| 85 | + // ObserverVec. The ObserverVec must have zero, one, or two non-const |
| 86 | + // non-curried labels. For those, the only allowed label names are "code" and |
| 87 | +-// "method". The function panics otherwise. The Observe method of the Observer |
| 88 | ++// "method". The function panics otherwise. For the "method" label a predefined |
| 89 | ++// default label value set is used to filter given values. Values besides |
| 90 | ++// predefined values will count as `unknown` method. `WithExtraMethods` |
| 91 | ++// can be used to add more methods to the set. The Observe method of the Observer |
| 92 | + // in the ObserverVec is called with the request duration in |
| 93 | + // seconds. Partitioning happens by HTTP status code and/or HTTP method if the |
| 94 | + // respective instance label names are present in the ObserverVec. For |
| 95 | +@@ -85,14 +96,19 @@ func InstrumentRoundTripperCounter(counter *prometheus.CounterVec, next http.Rou |
| 96 | + // |
| 97 | + // Note that this method is only guaranteed to never observe negative durations |
| 98 | + // if used with Go1.9+. |
| 99 | +-func InstrumentRoundTripperDuration(obs prometheus.ObserverVec, next http.RoundTripper) RoundTripperFunc { |
| 100 | ++func InstrumentRoundTripperDuration(obs prometheus.ObserverVec, next http.RoundTripper, opts ...Option) RoundTripperFunc { |
| 101 | ++ rtOpts := &option{} |
| 102 | ++ for _, o := range opts { |
| 103 | ++ o(rtOpts) |
| 104 | ++ } |
| 105 | ++ |
| 106 | + code, method := checkLabels(obs) |
| 107 | + |
| 108 | + return RoundTripperFunc(func(r *http.Request) (*http.Response, error) { |
| 109 | + start := time.Now() |
| 110 | + resp, err := next.RoundTrip(r) |
| 111 | + if err == nil { |
| 112 | +- obs.With(labels(code, method, r.Method, resp.StatusCode)).Observe(time.Since(start).Seconds()) |
| 113 | ++ obs.With(labels(code, method, r.Method, resp.StatusCode, rtOpts.extraMethods...)).Observe(time.Since(start).Seconds()) |
| 114 | + } |
| 115 | + return resp, err |
| 116 | + }) |
| 117 | +diff --git a/prometheus/promhttp/instrument_server.go b/prometheus/promhttp/instrument_server.go |
| 118 | +index 9db2438..91802f8 100644 |
| 119 | +--- a/prometheus/promhttp/instrument_server.go |
| 120 | ++++ b/prometheus/promhttp/instrument_server.go |
| 121 | +@@ -58,7 +58,12 @@ func InstrumentHandlerInFlight(g prometheus.Gauge, next http.Handler) http.Handl |
| 122 | + // |
| 123 | + // Note that this method is only guaranteed to never observe negative durations |
| 124 | + // if used with Go1.9+. |
| 125 | +-func InstrumentHandlerDuration(obs prometheus.ObserverVec, next http.Handler) http.HandlerFunc { |
| 126 | ++func InstrumentHandlerDuration(obs prometheus.ObserverVec, next http.Handler, opts ...Option) http.HandlerFunc { |
| 127 | ++ mwOpts := &option{} |
| 128 | ++ for _, o := range opts { |
| 129 | ++ o(mwOpts) |
| 130 | ++ } |
| 131 | ++ |
| 132 | + code, method := checkLabels(obs) |
| 133 | + |
| 134 | + if code { |
| 135 | +@@ -67,14 +72,14 @@ func InstrumentHandlerDuration(obs prometheus.ObserverVec, next http.Handler) ht |
| 136 | + d := newDelegator(w, nil) |
| 137 | + next.ServeHTTP(d, r) |
| 138 | + |
| 139 | +- obs.With(labels(code, method, r.Method, d.Status())).Observe(time.Since(now).Seconds()) |
| 140 | ++ obs.With(labels(code, method, r.Method, d.Status(), mwOpts.extraMethods...)).Observe(time.Since(now).Seconds()) |
| 141 | + }) |
| 142 | + } |
| 143 | + |
| 144 | + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| 145 | + now := time.Now() |
| 146 | + next.ServeHTTP(w, r) |
| 147 | +- obs.With(labels(code, method, r.Method, 0)).Observe(time.Since(now).Seconds()) |
| 148 | ++ obs.With(labels(code, method, r.Method, 0, mwOpts.extraMethods...)).Observe(time.Since(now).Seconds()) |
| 149 | + }) |
| 150 | + } |
| 151 | + |
| 152 | +@@ -91,20 +96,25 @@ func InstrumentHandlerDuration(obs prometheus.ObserverVec, next http.Handler) ht |
| 153 | + // If the wrapped Handler panics, the Counter is not incremented. |
| 154 | + // |
| 155 | + // See the example for InstrumentHandlerDuration for example usage. |
| 156 | +-func InstrumentHandlerCounter(counter *prometheus.CounterVec, next http.Handler) http.HandlerFunc { |
| 157 | ++func InstrumentHandlerCounter(counter *prometheus.CounterVec, next http.Handler, opts ...Option) http.HandlerFunc { |
| 158 | ++ mwOpts := &option{} |
| 159 | ++ for _, o := range opts { |
| 160 | ++ o(mwOpts) |
| 161 | ++ } |
| 162 | ++ |
| 163 | + code, method := checkLabels(counter) |
| 164 | + |
| 165 | + if code { |
| 166 | + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| 167 | + d := newDelegator(w, nil) |
| 168 | + next.ServeHTTP(d, r) |
| 169 | +- counter.With(labels(code, method, r.Method, d.Status())).Inc() |
| 170 | ++ counter.With(labels(code, method, r.Method, d.Status(), mwOpts.extraMethods...)).Inc() |
| 171 | + }) |
| 172 | + } |
| 173 | + |
| 174 | + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| 175 | + next.ServeHTTP(w, r) |
| 176 | +- counter.With(labels(code, method, r.Method, 0)).Inc() |
| 177 | ++ counter.With(labels(code, method, r.Method, 0, mwOpts.extraMethods...)).Inc() |
| 178 | + }) |
| 179 | + } |
| 180 | + |
| 181 | +@@ -126,13 +136,18 @@ func InstrumentHandlerCounter(counter *prometheus.CounterVec, next http.Handler) |
| 182 | + // if used with Go1.9+. |
| 183 | + // |
| 184 | + // See the example for InstrumentHandlerDuration for example usage. |
| 185 | +-func InstrumentHandlerTimeToWriteHeader(obs prometheus.ObserverVec, next http.Handler) http.HandlerFunc { |
| 186 | ++func InstrumentHandlerTimeToWriteHeader(obs prometheus.ObserverVec, next http.Handler, opts ...Option) http.HandlerFunc { |
| 187 | ++ mwOpts := &option{} |
| 188 | ++ for _, o := range opts { |
| 189 | ++ o(mwOpts) |
| 190 | ++ } |
| 191 | ++ |
| 192 | + code, method := checkLabels(obs) |
| 193 | + |
| 194 | + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| 195 | + now := time.Now() |
| 196 | + d := newDelegator(w, func(status int) { |
| 197 | +- obs.With(labels(code, method, r.Method, status)).Observe(time.Since(now).Seconds()) |
| 198 | ++ obs.With(labels(code, method, r.Method, status, mwOpts.extraMethods...)).Observe(time.Since(now).Seconds()) |
| 199 | + }) |
| 200 | + next.ServeHTTP(d, r) |
| 201 | + }) |
| 202 | +@@ -154,7 +169,12 @@ func InstrumentHandlerTimeToWriteHeader(obs prometheus.ObserverVec, next http.Ha |
| 203 | + // If the wrapped Handler panics, no values are reported. |
| 204 | + // |
| 205 | + // See the example for InstrumentHandlerDuration for example usage. |
| 206 | +-func InstrumentHandlerRequestSize(obs prometheus.ObserverVec, next http.Handler) http.HandlerFunc { |
| 207 | ++func InstrumentHandlerRequestSize(obs prometheus.ObserverVec, next http.Handler, opts ...Option) http.HandlerFunc { |
| 208 | ++ mwOpts := &option{} |
| 209 | ++ for _, o := range opts { |
| 210 | ++ o(mwOpts) |
| 211 | ++ } |
| 212 | ++ |
| 213 | + code, method := checkLabels(obs) |
| 214 | + |
| 215 | + if code { |
| 216 | +@@ -162,14 +182,14 @@ func InstrumentHandlerRequestSize(obs prometheus.ObserverVec, next http.Handler) |
| 217 | + d := newDelegator(w, nil) |
| 218 | + next.ServeHTTP(d, r) |
| 219 | + size := computeApproximateRequestSize(r) |
| 220 | +- obs.With(labels(code, method, r.Method, d.Status())).Observe(float64(size)) |
| 221 | ++ obs.With(labels(code, method, r.Method, d.Status(), mwOpts.extraMethods...)).Observe(float64(size)) |
| 222 | + }) |
| 223 | + } |
| 224 | + |
| 225 | + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| 226 | + next.ServeHTTP(w, r) |
| 227 | + size := computeApproximateRequestSize(r) |
| 228 | +- obs.With(labels(code, method, r.Method, 0)).Observe(float64(size)) |
| 229 | ++ obs.With(labels(code, method, r.Method, 0, mwOpts.extraMethods...)).Observe(float64(size)) |
| 230 | + }) |
| 231 | + } |
| 232 | + |
| 233 | +@@ -189,12 +209,18 @@ func InstrumentHandlerRequestSize(obs prometheus.ObserverVec, next http.Handler) |
| 234 | + // If the wrapped Handler panics, no values are reported. |
| 235 | + // |
| 236 | + // See the example for InstrumentHandlerDuration for example usage. |
| 237 | +-func InstrumentHandlerResponseSize(obs prometheus.ObserverVec, next http.Handler) http.Handler { |
| 238 | ++func InstrumentHandlerResponseSize(obs prometheus.ObserverVec, next http.Handler, opts ...Option) http.Handler { |
| 239 | ++ mwOpts := &option{} |
| 240 | ++ for _, o := range opts { |
| 241 | ++ o(mwOpts) |
| 242 | ++ } |
| 243 | ++ |
| 244 | + code, method := checkLabels(obs) |
| 245 | ++ |
| 246 | + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| 247 | + d := newDelegator(w, nil) |
| 248 | + next.ServeHTTP(d, r) |
| 249 | +- obs.With(labels(code, method, r.Method, d.Status())).Observe(float64(d.Written())) |
| 250 | ++ obs.With(labels(code, method, r.Method, d.Status(), mwOpts.extraMethods...)).Observe(float64(d.Written())) |
| 251 | + }) |
| 252 | + } |
| 253 | + |
| 254 | +@@ -279,7 +305,7 @@ func isLabelCurried(c prometheus.Collector, label string) bool { |
| 255 | + // unnecessary allocations on each request. |
| 256 | + var emptyLabels = prometheus.Labels{} |
| 257 | + |
| 258 | +-func labels(code, method bool, reqMethod string, status int) prometheus.Labels { |
| 259 | ++func labels(code, method bool, reqMethod string, status int, extraMethods ...string) prometheus.Labels { |
| 260 | + if !(code || method) { |
| 261 | + return emptyLabels |
| 262 | + } |
| 263 | +@@ -289,7 +315,7 @@ func labels(code, method bool, reqMethod string, status int) prometheus.Labels { |
| 264 | + labels["code"] = sanitizeCode(status) |
| 265 | + } |
| 266 | + if method { |
| 267 | +- labels["method"] = sanitizeMethod(reqMethod) |
| 268 | ++ labels["method"] = sanitizeMethod(reqMethod, extraMethods...) |
| 269 | + } |
| 270 | + |
| 271 | + return labels |
| 272 | +@@ -319,7 +345,12 @@ func computeApproximateRequestSize(r *http.Request) int { |
| 273 | + return s |
| 274 | + } |
| 275 | + |
| 276 | +-func sanitizeMethod(m string) string { |
| 277 | ++// If the wrapped http.Handler has a known method, it will be sanitized and returned. |
| 278 | ++// Otherwise, "unknown" will be returned. The known method list can be extended |
| 279 | ++// as needed by using extraMethods parameter. |
| 280 | ++func sanitizeMethod(m string, extraMethods ...string) string { |
| 281 | ++ // See https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods for |
| 282 | ++ // the methods chosen as default. |
| 283 | + switch m { |
| 284 | + case "GET", "get": |
| 285 | + return "get" |
| 286 | +@@ -337,15 +368,25 @@ func sanitizeMethod(m string) string { |
| 287 | + return "options" |
| 288 | + case "NOTIFY", "notify": |
| 289 | + return "notify" |
| 290 | ++ case "TRACE", "trace": |
| 291 | ++ return "trace" |
| 292 | ++ case "PATCH", "patch": |
| 293 | ++ return "patch" |
| 294 | + default: |
| 295 | +- return strings.ToLower(m) |
| 296 | ++ for _, method := range extraMethods { |
| 297 | ++ if strings.EqualFold(m, method) { |
| 298 | ++ return strings.ToLower(m) |
| 299 | ++ } |
| 300 | ++ } |
| 301 | ++ return "unknown" |
| 302 | + } |
| 303 | + } |
| 304 | + |
| 305 | + // If the wrapped http.Handler has not set a status code, i.e. the value is |
| 306 | +-// currently 0, santizeCode will return 200, for consistency with behavior in |
| 307 | ++// currently 0, sanitizeCode will return 200, for consistency with behavior in |
| 308 | + // the stdlib. |
| 309 | + func sanitizeCode(s int) string { |
| 310 | ++ // See for accepted codes https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml |
| 311 | + switch s { |
| 312 | + case 100: |
| 313 | + return "100" |
| 314 | +@@ -442,6 +483,9 @@ func sanitizeCode(s int) string { |
| 315 | + return "511" |
| 316 | + |
| 317 | + default: |
| 318 | +- return strconv.Itoa(s) |
| 319 | ++ if s >= 100 && s <= 599 { |
| 320 | ++ return strconv.Itoa(s) |
| 321 | ++ } |
| 322 | ++ return "unknown" |
| 323 | + } |
| 324 | + } |
| 325 | +diff --git a/prometheus/promhttp/option.go b/prometheus/promhttp/option.go |
| 326 | +new file mode 100644 |
| 327 | +index 0000000..35e41bd |
| 328 | +--- /dev/null |
| 329 | ++++ b/prometheus/promhttp/option.go |
| 330 | +@@ -0,0 +1,31 @@ |
| 331 | ++// Copyright 2022 The Prometheus Authors |
| 332 | ++// Licensed under the Apache License, Version 2.0 (the "License"); |
| 333 | ++// you may not use this file except in compliance with the License. |
| 334 | ++// You may obtain a copy of the License at |
| 335 | ++// |
| 336 | ++// http://www.apache.org/licenses/LICENSE-2.0 |
| 337 | ++// |
| 338 | ++// Unless required by applicable law or agreed to in writing, software |
| 339 | ++// distributed under the License is distributed on an "AS IS" BASIS, |
| 340 | ++// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 341 | ++// See the License for the specific language governing permissions and |
| 342 | ++// limitations under the License. |
| 343 | ++ |
| 344 | ++package promhttp |
| 345 | ++ |
| 346 | ++// Option are used to configure a middleware or round tripper.. |
| 347 | ++type Option func(*option) |
| 348 | ++ |
| 349 | ++type option struct { |
| 350 | ++ extraMethods []string |
| 351 | ++} |
| 352 | ++ |
| 353 | ++// WithExtraMethods adds additional HTTP methods to the list of allowed methods. |
| 354 | ++// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods for the default list. |
| 355 | ++// |
| 356 | ++// See the example for ExampleInstrumentHandlerWithExtraMethods for example usage. |
| 357 | ++func WithExtraMethods(methods ...string) Option { |
| 358 | ++ return func(o *option) { |
| 359 | ++ o.extraMethods = methods |
| 360 | ++ } |
| 361 | ++} |
| 362 | +-- |
| 363 | +2.33.8 |
| 364 | + |
0 commit comments