diff --git a/CHANGELOG.md b/CHANGELOG.md index 0096a10..f95bc87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ ## [Unreleased] +### Breaking changes + +- The Recorder methods now receive properties in a single argument, this will make less breaking changes and better API (there where too many arguments for a function). + +### Added + +- Added new `service` property to identify the service. + +### Changed + +- The Recorder methods now receive properties in a single argument, this will make less breaking changes and better API (there where too many arguments for a function) + ## [0.5.0] - 2019-12-12 ### Added diff --git a/Makefile b/Makefile index da7ad60..b2bedc5 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ UNIT_TEST_CMD := go test `go list ./... | grep -v vendor` -race -v INTEGRATION_TEST_CMD := go test `go list ./... | grep -v vendor` -race -v -tags='integration' BENCHMARK_CMD := go test `go list ./... | grep -v vendor` -benchmem -bench=. -DEPS_CMD := GO111MODULE=on go mod tidy && GO111MODULE=on go mod vendor +DEPS_CMD := go mod tidy MOCKS_CMD := go generate ./internal/mocks .PHONY: default diff --git a/Readme.md b/Readme.md index 554137d..6a575f7 100644 --- a/Readme.md +++ b/Readme.md @@ -129,6 +129,10 @@ The factory options are the ones that are passed in the moment of creating the m This is the implementation of the metrics backend, by default it's a dummy recorder. +#### Service + +This is an optional argument that can be used to set a specific service on all the middleware metrics, this is helpful when the service uses multiple middlewares on the same app, for example for the HTTP api server and the metrics server. This also gives the ability to use the same recorder with different middlewares. + #### GroupedStatus Storing all the status codes could increase the cardinality of the metrics, usually this is not a common case because the used status codes by a service are not too much and are finite, but some services use a lot of different status codes, grouping the status on the `\dxx` form could impact the performance (in a good way) of the queries on Prometheus (as they are already aggregated), on the other hand it losses detail. For example the metrics code `code="401"`, `code="404"`, `code="403"` with this enabled option would end being `code="4xx"` label. By default is disabled. @@ -163,7 +167,7 @@ DurationBuckets are the buckets used for the request duration histogram metric, #### SizeBuckets -This works the same as the `DurationBuckets` but for the metric that measures the size of the responses. It's measured in bytes and by default goes form 1B to 1GB. +This works the same as the `DurationBuckets` but for the metric that measures the size of the responses. It's measured in bytes and by default goes from 1B to 1GB. #### Registry diff --git a/go.mod b/go.mod index fa4ac1a..684d826 100644 --- a/go.mod +++ b/go.mod @@ -2,10 +2,10 @@ module github.com/slok/go-http-metrics require ( contrib.go.opencensus.io/exporter/prometheus v0.1.0 - github.com/emicklei/go-restful v2.9.6+incompatible + github.com/emicklei/go-restful v2.11.1+incompatible github.com/gin-gonic/gin v1.5.0 - github.com/julienschmidt/httprouter v1.2.0 - github.com/prometheus/client_golang v1.0.0 + github.com/julienschmidt/httprouter v1.3.0 + github.com/prometheus/client_golang v1.2.1 github.com/stretchr/testify v1.4.0 github.com/urfave/negroni v1.0.0 go.opencensus.io v0.22.0 diff --git a/go.sum b/go.sum index 07e6039..3d71f58 100644 --- a/go.sum +++ b/go.sum @@ -3,23 +3,31 @@ contrib.go.opencensus.io/exporter/prometheus v0.1.0 h1:SByaIoWwNgMdPSgl5sMqM2KDE contrib.go.opencensus.io/exporter/prometheus v0.1.0/go.mod h1:cGFniUXGZlKRjzOyuZJ6mgB+PgBcCIa79kEKR8YCW+A= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cespare/xxhash/v2 v2.1.0 h1:yTUvW7Vhb89inJ+8irsUqiWjh8iT6sQPZiQzI6ReGkA= +github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/emicklei/go-restful v2.9.6+incompatible h1:tfrHha8zJ01ywiOEC1miGY8st1/igzWB8OmvPgoYX7w= -github.com/emicklei/go-restful v2.9.6+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful v2.11.1+incompatible h1:CjKsv3uWcCMvySPQYKxO8XX3f9zD4FeZRsW4G0B4ffE= +github.com/emicklei/go-restful v2.11.1+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.5.0 h1:fi+bqFAx/oLK54somfCtEZs9HeH1LHVoEPUgARpTqyc= github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-playground/locales v0.12.1 h1:2FITxuFt/xuCNP1Acdhv62OzaCiviiE4kotfhkmOqEc= github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= github.com/go-playground/universal-translator v0.16.0 h1:X++omBR/4cE2MNg91AoC3rmGrCjJ8eAeUP/K/EKx4DM= @@ -45,9 +53,12 @@ github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/leodido/go-urn v1.1.0 h1:Sm1gr51B1kKyfD2BlRcLSiEkffoG96g6TPv6eRoEiB8= @@ -64,24 +75,34 @@ github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= github.com/prometheus/client_golang v1.0.0 h1:vrDKnkGzuGvhNAL56c7DBz29ZL+KxnoR0x7enabFceM= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.2.1 h1:JnMpQc6ppsNgw9QPAGF6Dod479itz7lvlsMzzNayLOI= +github.com/prometheus/client_golang v1.2.1/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLyCY= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNGfs= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8= +github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -115,6 +136,7 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ= @@ -127,9 +149,13 @@ golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd h1:r7DufRZuZbWB7j439YfAzP8RPDa9unLkpwQKUYbIMPI= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY= +golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -146,6 +172,7 @@ google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiq gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v9 v9.29.1 h1:SvGtYmN60a5CVKTOzMSyfzWDeZRxRuGvRQyEAKbw1xc= gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= diff --git a/internal/mocks/metrics/Recorder.go b/internal/mocks/metrics/Recorder.go index b6bbcdc..7fd8d2e 100644 --- a/internal/mocks/metrics/Recorder.go +++ b/internal/mocks/metrics/Recorder.go @@ -3,7 +3,7 @@ package metrics import context "context" - +import metrics "github.com/slok/go-http-metrics/metrics" import mock "github.com/stretchr/testify/mock" import time "time" @@ -12,17 +12,17 @@ type Recorder struct { mock.Mock } -// AddInflightRequests provides a mock function with given fields: ctx, id, quantity -func (_m *Recorder) AddInflightRequests(ctx context.Context, id string, quantity int) { - _m.Called(ctx, id, quantity) +// AddInflightRequests provides a mock function with given fields: ctx, props, quantity +func (_m *Recorder) AddInflightRequests(ctx context.Context, props metrics.HTTPProperties, quantity int) { + _m.Called(ctx, props, quantity) } -// ObserveHTTPRequestDuration provides a mock function with given fields: ctx, id, duration, method, code -func (_m *Recorder) ObserveHTTPRequestDuration(ctx context.Context, id string, duration time.Duration, method string, code string) { - _m.Called(ctx, id, duration, method, code) +// ObserveHTTPRequestDuration provides a mock function with given fields: ctx, props, duration +func (_m *Recorder) ObserveHTTPRequestDuration(ctx context.Context, props metrics.HTTPReqProperties, duration time.Duration) { + _m.Called(ctx, props, duration) } -// ObserveHTTPResponseSize provides a mock function with given fields: ctx, id, sizeBytes, method, code -func (_m *Recorder) ObserveHTTPResponseSize(ctx context.Context, id string, sizeBytes int64, method string, code string) { - _m.Called(ctx, id, sizeBytes, method, code) +// ObserveHTTPResponseSize provides a mock function with given fields: ctx, props, sizeBytes +func (_m *Recorder) ObserveHTTPResponseSize(ctx context.Context, props metrics.HTTPReqProperties, sizeBytes int64) { + _m.Called(ctx, props, sizeBytes) } diff --git a/metrics/metrics.go b/metrics/metrics.go index 9b2487d..f4fa46a 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -5,26 +5,47 @@ import ( "time" ) +// HTTPReqProperties are the metric properties for the metrics based +// on client request. +type HTTPReqProperties struct { + // Service is the service that has served the request. + Service string + // ID is the id of the request handler. + ID string + // Method is the method of the request. + Method string + // Code is the response of the request. + Code string +} + +// HTTPProperties are the metric properties for the global server metrics. +type HTTPProperties struct { + // Service is the service that has served the request. + Service string + // ID is the id of the request handler. + ID string +} + // Recorder knows how to record and measure the metrics. This // Interface has the required methods to be used with the HTTP // middlewares. type Recorder interface { // ObserveHTTPRequestDuration measures the duration of an HTTP request. - ObserveHTTPRequestDuration(ctx context.Context, id string, duration time.Duration, method, code string) + ObserveHTTPRequestDuration(ctx context.Context, props HTTPReqProperties, duration time.Duration) // ObserveHTTPResponseSize measures the size of an HTTP response in bytes. - ObserveHTTPResponseSize(ctx context.Context, id string, sizeBytes int64, method, code string) + ObserveHTTPResponseSize(ctx context.Context, props HTTPReqProperties, sizeBytes int64) // AddInflightRequests increments and decrements the number of inflight request being // processed. - AddInflightRequests(ctx context.Context, id string, quantity int) + AddInflightRequests(ctx context.Context, props HTTPProperties, quantity int) } // Dummy is a dummy recorder. -var Dummy = &dummy{} +const Dummy = dummy(0) -type dummy struct{} +type dummy int -func (dummy) ObserveHTTPRequestDuration(ctx context.Context, id string, duration time.Duration, method, code string) { -} -func (dummy) ObserveHTTPResponseSize(ctx context.Context, id string, sizeBytes int64, method, code string) { -} -func (dummy) AddInflightRequests(ctx context.Context, id string, quantity int) {} +func (dummy) ObserveHTTPRequestDuration(_ context.Context, _ HTTPReqProperties, _ time.Duration) {} +func (dummy) ObserveHTTPResponseSize(_ context.Context, _ HTTPReqProperties, _ int64) {} +func (dummy) AddInflightRequests(_ context.Context, _ HTTPProperties, _ int) {} + +var _ Recorder = Dummy diff --git a/metrics/opencensus/opencensus.go b/metrics/opencensus/opencensus.go index d7c1818..3590ae6 100644 --- a/metrics/opencensus/opencensus.go +++ b/metrics/opencensus/opencensus.go @@ -30,6 +30,8 @@ type Config struct { StatusCodeLabel string // MethodLabel is the name that will be set to the method label, by default is `method`. MethodLabel string + // ServiceLabel is the name that will be set to the service label, by default is `service`. + ServiceLabel string // UnregisterViewsBeforeRegister will unregister the previous Recorder views before registering // again. This is required on cases where multiple instances of recorder will be made due to how // Opencensus is implemented (everything is at global state). Sadly this option is a kind of hack @@ -58,6 +60,10 @@ func (c *Config) defaults() { if c.MethodLabel == "" { c.MethodLabel = "method" } + + if c.ServiceLabel == "" { + c.ServiceLabel = "service" + } } type recorder struct { @@ -65,6 +71,7 @@ type recorder struct { codeKey tag.Key methodKey tag.Key handlerKey tag.Key + serviceKey tag.Key // Measures. latencySecs *stats.Float64Measure @@ -112,6 +119,12 @@ func (r *recorder) createKeys(cfg Config) error { } r.handlerKey = handler + service, err := tag.NewKey(cfg.ServiceLabel) + if err != nil { + return err + } + r.serviceKey = service + return nil } @@ -132,25 +145,25 @@ func (r *recorder) createMeasurements() { func (r recorder) registerViews(cfg Config) error { - // OpenCensus uses global states, sadly we can't have view insta + // OpenCensus uses global states, sadly we can't have view instance. durationView := &view.View{ Name: "http_request_duration_seconds", Description: "The latency of the HTTP requests", - TagKeys: []tag.Key{r.handlerKey, r.methodKey, r.codeKey}, + TagKeys: []tag.Key{r.serviceKey, r.handlerKey, r.methodKey, r.codeKey}, Measure: r.latencySecs, Aggregation: view.Distribution(cfg.DurationBuckets...), } sizeView := &view.View{ Name: "http_response_size_bytes", Description: "The size of the HTTP responses", - TagKeys: []tag.Key{r.handlerKey, r.methodKey, r.codeKey}, + TagKeys: []tag.Key{r.serviceKey, r.handlerKey, r.methodKey, r.codeKey}, Measure: r.sizeBytes, Aggregation: view.Distribution(cfg.SizeBuckets...), } inflightView := &view.View{ Name: "http_requests_inflight", Description: "The number of inflight requests being handled at the same time", - TagKeys: []tag.Key{r.handlerKey}, + TagKeys: []tag.Key{r.serviceKey, r.handlerKey}, Measure: r.inflightCount, Aggregation: view.Sum(), } @@ -168,30 +181,35 @@ func (r recorder) registerViews(cfg Config) error { return nil } -func (r recorder) ObserveHTTPRequestDuration(ctx context.Context, id string, duration time.Duration, method, code string) { - ctx, _ = tag.New(ctx, - tag.Upsert(r.handlerKey, id), - tag.Upsert(r.methodKey, method), - tag.Upsert(r.codeKey, code), - ) - +func (r recorder) ObserveHTTPRequestDuration(ctx context.Context, p metrics.HTTPReqProperties, duration time.Duration) { + ctx = r.ctxWithTagFromHTTPReqProperties(ctx, p) stats.Record(ctx, r.latencySecs.M(duration.Seconds())) } -func (r recorder) ObserveHTTPResponseSize(ctx context.Context, id string, sizeBytes int64, method, code string) { - ctx, _ = tag.New(ctx, - tag.Upsert(r.handlerKey, id), - tag.Upsert(r.methodKey, method), - tag.Upsert(r.codeKey, code), - ) - +func (r recorder) ObserveHTTPResponseSize(ctx context.Context, p metrics.HTTPReqProperties, sizeBytes int64) { + ctx = r.ctxWithTagFromHTTPReqProperties(ctx, p) stats.Record(ctx, r.sizeBytes.M(sizeBytes)) } -func (r recorder) AddInflightRequests(ctx context.Context, id string, quantity int) { - ctx, _ = tag.New(ctx, - tag.Upsert(r.handlerKey, id), +func (r recorder) AddInflightRequests(ctx context.Context, p metrics.HTTPProperties, quantity int) { + ctx = r.ctxWithTagFromHTTPProperties(ctx, p) + stats.Record(ctx, r.inflightCount.M(int64(quantity))) +} + +func (r recorder) ctxWithTagFromHTTPReqProperties(ctx context.Context, p metrics.HTTPReqProperties) context.Context { + newCtx, _ := tag.New(ctx, + tag.Upsert(r.serviceKey, p.Service), + tag.Upsert(r.handlerKey, p.ID), + tag.Upsert(r.methodKey, p.Method), + tag.Upsert(r.codeKey, p.Code), ) + return newCtx +} - stats.Record(ctx, r.inflightCount.M(int64(quantity))) +func (r recorder) ctxWithTagFromHTTPProperties(ctx context.Context, p metrics.HTTPProperties) context.Context { + newCtx, _ := tag.New(ctx, + tag.Upsert(r.serviceKey, p.Service), + tag.Upsert(r.handlerKey, p.ID), + ) + return newCtx } diff --git a/metrics/opencensus/opencensus_test.go b/metrics/opencensus/opencensus_test.go index 975800c..8b39162 100644 --- a/metrics/opencensus/opencensus_test.go +++ b/metrics/opencensus/opencensus_test.go @@ -28,85 +28,85 @@ func TestOpenCensusRecorder(t *testing.T) { name: "Default configuration should measure with the default metric style.", config: ocmetrics.Config{}, recordMetrics: func(r metrics.Recorder) { - r.ObserveHTTPRequestDuration(context.TODO(), "test1", 4*time.Second, http.MethodGet, "200") - r.ObserveHTTPRequestDuration(context.TODO(), "test1", 175*time.Millisecond, http.MethodGet, "200") - r.ObserveHTTPRequestDuration(context.TODO(), "test2", 30*time.Millisecond, http.MethodGet, "201") - r.ObserveHTTPRequestDuration(context.TODO(), "test3", 700*time.Millisecond, http.MethodPost, "500") - r.ObserveHTTPResponseSize(context.TODO(), "test4", 529930, http.MethodPost, "500") - r.ObserveHTTPResponseSize(context.TODO(), "test4", 231, http.MethodPost, "500") - r.ObserveHTTPResponseSize(context.TODO(), "test4", 99999999, http.MethodPatch, "429") - r.AddInflightRequests(context.TODO(), "test1", 5) - r.AddInflightRequests(context.TODO(), "test1", -3) - r.AddInflightRequests(context.TODO(), "test2", 9) + r.ObserveHTTPRequestDuration(context.TODO(), metrics.HTTPReqProperties{Service: "svc1", ID: "test1", Method: http.MethodGet, Code: "200"}, 5*time.Second) + r.ObserveHTTPRequestDuration(context.TODO(), metrics.HTTPReqProperties{Service: "svc1", ID: "test1", Method: http.MethodGet, Code: "200"}, 175*time.Millisecond) + r.ObserveHTTPRequestDuration(context.TODO(), metrics.HTTPReqProperties{Service: "svc1", ID: "test2", Method: http.MethodGet, Code: "201"}, 50*time.Millisecond) + r.ObserveHTTPRequestDuration(context.TODO(), metrics.HTTPReqProperties{Service: "svc2", ID: "test3", Method: http.MethodPost, Code: "500"}, 700*time.Millisecond) + r.ObserveHTTPResponseSize(context.TODO(), metrics.HTTPReqProperties{Service: "svc1", ID: "test4", Method: http.MethodPost, Code: "500"}, 529930) + r.ObserveHTTPResponseSize(context.TODO(), metrics.HTTPReqProperties{Service: "svc1", ID: "test4", Method: http.MethodPost, Code: "500"}, 231) + r.ObserveHTTPResponseSize(context.TODO(), metrics.HTTPReqProperties{Service: "svc2", ID: "test4", Method: http.MethodPatch, Code: "429"}, 99999999) + r.AddInflightRequests(context.TODO(), metrics.HTTPProperties{Service: "svc1", ID: "test1"}, 5) + r.AddInflightRequests(context.TODO(), metrics.HTTPProperties{Service: "svc1", ID: "test1"}, -3) + r.AddInflightRequests(context.TODO(), metrics.HTTPProperties{Service: "svc2", ID: "test2"}, 9) }, expMetrics: []string{ - `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="0.005"} 0`, - `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="0.01"} 0`, - `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="0.025"} 0`, - `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="0.05"} 0`, - `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="0.1"} 0`, - `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="0.25"} 1`, - `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="0.5"} 1`, - `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="1"} 1`, - `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="2.5"} 1`, - `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="5"} 2`, - `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="10"} 2`, - `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="+Inf"} 2`, - `http_request_duration_seconds_count{code="200",handler="test1",method="GET"} 2`, - - `http_request_duration_seconds_bucket{code="201",handler="test2",method="GET",le="0.005"} 0`, - `http_request_duration_seconds_bucket{code="201",handler="test2",method="GET",le="0.01"} 0`, - `http_request_duration_seconds_bucket{code="201",handler="test2",method="GET",le="0.025"} 0`, - `http_request_duration_seconds_bucket{code="201",handler="test2",method="GET",le="0.05"} 1`, - `http_request_duration_seconds_bucket{code="201",handler="test2",method="GET",le="0.1"} 1`, - `http_request_duration_seconds_bucket{code="201",handler="test2",method="GET",le="0.25"} 1`, - `http_request_duration_seconds_bucket{code="201",handler="test2",method="GET",le="0.5"} 1`, - `http_request_duration_seconds_bucket{code="201",handler="test2",method="GET",le="1"} 1`, - `http_request_duration_seconds_bucket{code="201",handler="test2",method="GET",le="2.5"} 1`, - `http_request_duration_seconds_bucket{code="201",handler="test2",method="GET",le="5"} 1`, - `http_request_duration_seconds_bucket{code="201",handler="test2",method="GET",le="10"} 1`, - `http_request_duration_seconds_bucket{code="201",handler="test2",method="GET",le="+Inf"} 1`, - `http_request_duration_seconds_count{code="201",handler="test2",method="GET"} 1`, - - `http_request_duration_seconds_bucket{code="500",handler="test3",method="POST",le="0.005"} 0`, - `http_request_duration_seconds_bucket{code="500",handler="test3",method="POST",le="0.01"} 0`, - `http_request_duration_seconds_bucket{code="500",handler="test3",method="POST",le="0.025"} 0`, - `http_request_duration_seconds_bucket{code="500",handler="test3",method="POST",le="0.05"} 0`, - `http_request_duration_seconds_bucket{code="500",handler="test3",method="POST",le="0.1"} 0`, - `http_request_duration_seconds_bucket{code="500",handler="test3",method="POST",le="0.25"} 0`, - `http_request_duration_seconds_bucket{code="500",handler="test3",method="POST",le="0.5"} 0`, - `http_request_duration_seconds_bucket{code="500",handler="test3",method="POST",le="1"} 1`, - `http_request_duration_seconds_bucket{code="500",handler="test3",method="POST",le="2.5"} 1`, - `http_request_duration_seconds_bucket{code="500",handler="test3",method="POST",le="5"} 1`, - `http_request_duration_seconds_bucket{code="500",handler="test3",method="POST",le="10"} 1`, - `http_request_duration_seconds_bucket{code="500",handler="test3",method="POST",le="+Inf"} 1`, - `http_request_duration_seconds_count{code="500",handler="test3",method="POST"} 1`, - - `http_response_size_bytes_bucket{code="429",handler="test4",method="PATCH",le="100"} 0`, - `http_response_size_bytes_bucket{code="429",handler="test4",method="PATCH",le="1000"} 0`, - `http_response_size_bytes_bucket{code="429",handler="test4",method="PATCH",le="10000"} 0`, - `http_response_size_bytes_bucket{code="429",handler="test4",method="PATCH",le="100000"} 0`, - `http_response_size_bytes_bucket{code="429",handler="test4",method="PATCH",le="1e+06"} 0`, - `http_response_size_bytes_bucket{code="429",handler="test4",method="PATCH",le="1e+07"} 0`, - `http_response_size_bytes_bucket{code="429",handler="test4",method="PATCH",le="1e+08"} 1`, - `http_response_size_bytes_bucket{code="429",handler="test4",method="PATCH",le="1e+09"} 1`, - `http_response_size_bytes_bucket{code="429",handler="test4",method="PATCH",le="+Inf"} 1`, - `http_response_size_bytes_count{code="429",handler="test4",method="PATCH"} 1`, - - `http_response_size_bytes_bucket{code="500",handler="test4",method="POST",le="100"} 0`, - `http_response_size_bytes_bucket{code="500",handler="test4",method="POST",le="1000"} 1`, - `http_response_size_bytes_bucket{code="500",handler="test4",method="POST",le="10000"} 1`, - `http_response_size_bytes_bucket{code="500",handler="test4",method="POST",le="100000"} 1`, - `http_response_size_bytes_bucket{code="500",handler="test4",method="POST",le="1e+06"} 2`, - `http_response_size_bytes_bucket{code="500",handler="test4",method="POST",le="1e+07"} 2`, - `http_response_size_bytes_bucket{code="500",handler="test4",method="POST",le="1e+08"} 2`, - `http_response_size_bytes_bucket{code="500",handler="test4",method="POST",le="1e+09"} 2`, - `http_response_size_bytes_bucket{code="500",handler="test4",method="POST",le="+Inf"} 2`, - `http_response_size_bytes_count{code="500",handler="test4",method="POST"} 2`, - - `http_requests_inflight{handler="test1"} 2`, - - `http_requests_inflight{handler="test2"} 9`, + `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="0.005"} 0`, + `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="0.01"} 0`, + `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="0.025"} 0`, + `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="0.05"} 0`, + `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="0.1"} 0`, + `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="0.25"} 1`, + `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="0.5"} 1`, + `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="1"} 1`, + `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="2.5"} 1`, + `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="5"} 1`, + `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="10"} 2`, + `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="+Inf"} 2`, + `http_request_duration_seconds_count{code="200",handler="test1",method="GET",service="svc1"} 2`, + + `http_request_duration_seconds_bucket{code="201",handler="test2",method="GET",service="svc1",le="0.005"} 0`, + `http_request_duration_seconds_bucket{code="201",handler="test2",method="GET",service="svc1",le="0.01"} 0`, + `http_request_duration_seconds_bucket{code="201",handler="test2",method="GET",service="svc1",le="0.025"} 0`, + `http_request_duration_seconds_bucket{code="201",handler="test2",method="GET",service="svc1",le="0.05"} 0`, + `http_request_duration_seconds_bucket{code="201",handler="test2",method="GET",service="svc1",le="0.1"} 1`, + `http_request_duration_seconds_bucket{code="201",handler="test2",method="GET",service="svc1",le="0.25"} 1`, + `http_request_duration_seconds_bucket{code="201",handler="test2",method="GET",service="svc1",le="0.5"} 1`, + `http_request_duration_seconds_bucket{code="201",handler="test2",method="GET",service="svc1",le="1"} 1`, + `http_request_duration_seconds_bucket{code="201",handler="test2",method="GET",service="svc1",le="2.5"} 1`, + `http_request_duration_seconds_bucket{code="201",handler="test2",method="GET",service="svc1",le="5"} 1`, + `http_request_duration_seconds_bucket{code="201",handler="test2",method="GET",service="svc1",le="10"} 1`, + `http_request_duration_seconds_bucket{code="201",handler="test2",method="GET",service="svc1",le="+Inf"} 1`, + `http_request_duration_seconds_count{code="201",handler="test2",method="GET",service="svc1"} 1`, + + `http_request_duration_seconds_bucket{code="500",handler="test3",method="POST",service="svc2",le="0.005"} 0`, + `http_request_duration_seconds_bucket{code="500",handler="test3",method="POST",service="svc2",le="0.01"} 0`, + `http_request_duration_seconds_bucket{code="500",handler="test3",method="POST",service="svc2",le="0.025"} 0`, + `http_request_duration_seconds_bucket{code="500",handler="test3",method="POST",service="svc2",le="0.05"} 0`, + `http_request_duration_seconds_bucket{code="500",handler="test3",method="POST",service="svc2",le="0.1"} 0`, + `http_request_duration_seconds_bucket{code="500",handler="test3",method="POST",service="svc2",le="0.25"} 0`, + `http_request_duration_seconds_bucket{code="500",handler="test3",method="POST",service="svc2",le="0.5"} 0`, + `http_request_duration_seconds_bucket{code="500",handler="test3",method="POST",service="svc2",le="1"} 1`, + `http_request_duration_seconds_bucket{code="500",handler="test3",method="POST",service="svc2",le="2.5"} 1`, + `http_request_duration_seconds_bucket{code="500",handler="test3",method="POST",service="svc2",le="5"} 1`, + `http_request_duration_seconds_bucket{code="500",handler="test3",method="POST",service="svc2",le="10"} 1`, + `http_request_duration_seconds_bucket{code="500",handler="test3",method="POST",service="svc2",le="+Inf"} 1`, + `http_request_duration_seconds_count{code="500",handler="test3",method="POST",service="svc2"} 1`, + + `http_response_size_bytes_bucket{code="429",handler="test4",method="PATCH",service="svc2",le="100"} 0`, + `http_response_size_bytes_bucket{code="429",handler="test4",method="PATCH",service="svc2",le="1000"} 0`, + `http_response_size_bytes_bucket{code="429",handler="test4",method="PATCH",service="svc2",le="10000"} 0`, + `http_response_size_bytes_bucket{code="429",handler="test4",method="PATCH",service="svc2",le="100000"} 0`, + `http_response_size_bytes_bucket{code="429",handler="test4",method="PATCH",service="svc2",le="1e+06"} 0`, + `http_response_size_bytes_bucket{code="429",handler="test4",method="PATCH",service="svc2",le="1e+07"} 0`, + `http_response_size_bytes_bucket{code="429",handler="test4",method="PATCH",service="svc2",le="1e+08"} 1`, + `http_response_size_bytes_bucket{code="429",handler="test4",method="PATCH",service="svc2",le="1e+09"} 1`, + `http_response_size_bytes_bucket{code="429",handler="test4",method="PATCH",service="svc2",le="+Inf"} 1`, + `http_response_size_bytes_count{code="429",handler="test4",method="PATCH",service="svc2"} 1`, + + `http_response_size_bytes_bucket{code="500",handler="test4",method="POST",service="svc1",le="100"} 0`, + `http_response_size_bytes_bucket{code="500",handler="test4",method="POST",service="svc1",le="1000"} 1`, + `http_response_size_bytes_bucket{code="500",handler="test4",method="POST",service="svc1",le="10000"} 1`, + `http_response_size_bytes_bucket{code="500",handler="test4",method="POST",service="svc1",le="100000"} 1`, + `http_response_size_bytes_bucket{code="500",handler="test4",method="POST",service="svc1",le="1e+06"} 2`, + `http_response_size_bytes_bucket{code="500",handler="test4",method="POST",service="svc1",le="1e+07"} 2`, + `http_response_size_bytes_bucket{code="500",handler="test4",method="POST",service="svc1",le="1e+08"} 2`, + `http_response_size_bytes_bucket{code="500",handler="test4",method="POST",service="svc1",le="1e+09"} 2`, + `http_response_size_bytes_bucket{code="500",handler="test4",method="POST",service="svc1",le="+Inf"} 2`, + `http_response_size_bytes_count{code="500",handler="test4",method="POST",service="svc1"} 2`, + + `http_requests_inflight{handler="test1",service="svc1"} 2`, + + `http_requests_inflight{handler="test2",service="svc2"} 9`, }, }, { @@ -115,23 +115,23 @@ func TestOpenCensusRecorder(t *testing.T) { DurationBuckets: []float64{1, 2, 10, 20, 50, 200, 500, 1000, 2000, 5000, 10000}, }, recordMetrics: func(r metrics.Recorder) { - r.ObserveHTTPRequestDuration(context.TODO(), "test1", 75*time.Minute, http.MethodGet, "200") - r.ObserveHTTPRequestDuration(context.TODO(), "test1", 200*time.Hour, http.MethodGet, "200") + r.ObserveHTTPRequestDuration(context.TODO(), metrics.HTTPReqProperties{Service: "svc1", ID: "test1", Method: http.MethodGet, Code: "200"}, 75*time.Minute) + r.ObserveHTTPRequestDuration(context.TODO(), metrics.HTTPReqProperties{Service: "svc1", ID: "test1", Method: http.MethodGet, Code: "200"}, 200*time.Hour) }, expMetrics: []string{ - `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="1"} 0`, - `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="2"} 0`, - `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="10"} 0`, - `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="20"} 0`, - `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="50"} 0`, - `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="200"} 0`, - `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="500"} 0`, - `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="1000"} 0`, - `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="2000"} 0`, - `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="5000"} 1`, - `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="10000"} 1`, - `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="+Inf"} 2`, - `http_request_duration_seconds_count{code="200",handler="test1",method="GET"} 2`, + `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="1"} 0`, + `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="2"} 0`, + `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="10"} 0`, + `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="20"} 0`, + `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="50"} 0`, + `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="200"} 0`, + `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="500"} 0`, + `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="1000"} 0`, + `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="2000"} 0`, + `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="5000"} 1`, + `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="10000"} 1`, + `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="+Inf"} 2`, + `http_request_duration_seconds_count{code="200",handler="test1",method="GET",service="svc1"} 2`, }, }, { @@ -140,25 +140,26 @@ func TestOpenCensusRecorder(t *testing.T) { HandlerIDLabel: "route_id", StatusCodeLabel: "status_code", MethodLabel: "http_method", + ServiceLabel: "http_service", }, recordMetrics: func(r metrics.Recorder) { - r.ObserveHTTPRequestDuration(context.TODO(), "test1", 4*time.Second, http.MethodGet, "200") - r.ObserveHTTPRequestDuration(context.TODO(), "test1", 175*time.Millisecond, http.MethodGet, "200") + r.ObserveHTTPRequestDuration(context.TODO(), metrics.HTTPReqProperties{Service: "svc1", ID: "test1", Method: http.MethodGet, Code: "200"}, 6*time.Second) + r.ObserveHTTPRequestDuration(context.TODO(), metrics.HTTPReqProperties{Service: "svc1", ID: "test1", Method: http.MethodGet, Code: "200"}, 175*time.Millisecond) }, expMetrics: []string{ - `http_request_duration_seconds_bucket{http_method="GET",route_id="test1",status_code="200",le="0.005"} 0`, - `http_request_duration_seconds_bucket{http_method="GET",route_id="test1",status_code="200",le="0.01"} 0`, - `http_request_duration_seconds_bucket{http_method="GET",route_id="test1",status_code="200",le="0.025"} 0`, - `http_request_duration_seconds_bucket{http_method="GET",route_id="test1",status_code="200",le="0.05"} 0`, - `http_request_duration_seconds_bucket{http_method="GET",route_id="test1",status_code="200",le="0.1"} 0`, - `http_request_duration_seconds_bucket{http_method="GET",route_id="test1",status_code="200",le="0.25"} 1`, - `http_request_duration_seconds_bucket{http_method="GET",route_id="test1",status_code="200",le="0.5"} 1`, - `http_request_duration_seconds_bucket{http_method="GET",route_id="test1",status_code="200",le="1"} 1`, - `http_request_duration_seconds_bucket{http_method="GET",route_id="test1",status_code="200",le="2.5"} 1`, - `http_request_duration_seconds_bucket{http_method="GET",route_id="test1",status_code="200",le="5"} 2`, - `http_request_duration_seconds_bucket{http_method="GET",route_id="test1",status_code="200",le="10"} 2`, - `http_request_duration_seconds_bucket{http_method="GET",route_id="test1",status_code="200",le="+Inf"} 2`, - `http_request_duration_seconds_count{http_method="GET",route_id="test1",status_code="200"} 2`, + `http_request_duration_seconds_bucket{http_method="GET",http_service="svc1",route_id="test1",status_code="200",le="0.005"} 0`, + `http_request_duration_seconds_bucket{http_method="GET",http_service="svc1",route_id="test1",status_code="200",le="0.01"} 0`, + `http_request_duration_seconds_bucket{http_method="GET",http_service="svc1",route_id="test1",status_code="200",le="0.025"} 0`, + `http_request_duration_seconds_bucket{http_method="GET",http_service="svc1",route_id="test1",status_code="200",le="0.05"} 0`, + `http_request_duration_seconds_bucket{http_method="GET",http_service="svc1",route_id="test1",status_code="200",le="0.1"} 0`, + `http_request_duration_seconds_bucket{http_method="GET",http_service="svc1",route_id="test1",status_code="200",le="0.25"} 1`, + `http_request_duration_seconds_bucket{http_method="GET",http_service="svc1",route_id="test1",status_code="200",le="0.5"} 1`, + `http_request_duration_seconds_bucket{http_method="GET",http_service="svc1",route_id="test1",status_code="200",le="1"} 1`, + `http_request_duration_seconds_bucket{http_method="GET",http_service="svc1",route_id="test1",status_code="200",le="2.5"} 1`, + `http_request_duration_seconds_bucket{http_method="GET",http_service="svc1",route_id="test1",status_code="200",le="5"} 1`, + `http_request_duration_seconds_bucket{http_method="GET",http_service="svc1",route_id="test1",status_code="200",le="10"} 2`, + `http_request_duration_seconds_bucket{http_method="GET",http_service="svc1",route_id="test1",status_code="200",le="+Inf"} 2`, + `http_request_duration_seconds_count{http_method="GET",http_service="svc1",route_id="test1",status_code="200"} 2`, }, }, } diff --git a/metrics/prometheus/prometheus.go b/metrics/prometheus/prometheus.go index e066e8b..41e95b5 100644 --- a/metrics/prometheus/prometheus.go +++ b/metrics/prometheus/prometheus.go @@ -28,6 +28,8 @@ type Config struct { StatusCodeLabel string // MethodLabel is the name that will be set to the method label, by default is `method`. MethodLabel string + // ServiceLabel is the name that will be set to the service label, by default is `service`. + ServiceLabel string } func (c *Config) defaults() { @@ -54,14 +56,16 @@ func (c *Config) defaults() { if c.MethodLabel == "" { c.MethodLabel = "method" } + + if c.ServiceLabel == "" { + c.ServiceLabel = "service" + } } type recorder struct { httpRequestDurHistogram *prometheus.HistogramVec httpResponseSizeHistogram *prometheus.HistogramVec httpRequestsInflight *prometheus.GaugeVec - - cfg Config } // NewRecorder returns a new metrics recorder that implements the recorder @@ -76,45 +80,41 @@ func NewRecorder(cfg Config) metrics.Recorder { Name: "request_duration_seconds", Help: "The latency of the HTTP requests.", Buckets: cfg.DurationBuckets, - }, []string{cfg.HandlerIDLabel, cfg.MethodLabel, cfg.StatusCodeLabel}), + }, []string{cfg.ServiceLabel, cfg.HandlerIDLabel, cfg.MethodLabel, cfg.StatusCodeLabel}), + httpResponseSizeHistogram: prometheus.NewHistogramVec(prometheus.HistogramOpts{ Namespace: cfg.Prefix, Subsystem: "http", Name: "response_size_bytes", Help: "The size of the HTTP responses.", Buckets: cfg.SizeBuckets, - }, []string{cfg.HandlerIDLabel, cfg.MethodLabel, cfg.StatusCodeLabel}), + }, []string{cfg.ServiceLabel, cfg.HandlerIDLabel, cfg.MethodLabel, cfg.StatusCodeLabel}), + httpRequestsInflight: prometheus.NewGaugeVec(prometheus.GaugeOpts{ Namespace: cfg.Prefix, Subsystem: "http", Name: "requests_inflight", Help: "The number of inflight requests being handled at the same time.", - }, []string{cfg.HandlerIDLabel}), - - cfg: cfg, + }, []string{cfg.ServiceLabel, cfg.HandlerIDLabel}), } - r.registerMetrics() - - return r -} - -func (r recorder) registerMetrics() { - r.cfg.Registry.MustRegister( + cfg.Registry.MustRegister( r.httpRequestDurHistogram, r.httpResponseSizeHistogram, r.httpRequestsInflight, ) + + return r } -func (r recorder) ObserveHTTPRequestDuration(_ context.Context, id string, duration time.Duration, method, code string) { - r.httpRequestDurHistogram.WithLabelValues(id, method, code).Observe(duration.Seconds()) +func (r recorder) ObserveHTTPRequestDuration(_ context.Context, p metrics.HTTPReqProperties, duration time.Duration) { + r.httpRequestDurHistogram.WithLabelValues(p.Service, p.ID, p.Method, p.Code).Observe(duration.Seconds()) } -func (r recorder) ObserveHTTPResponseSize(_ context.Context, id string, sizeBytes int64, method, code string) { - r.httpResponseSizeHistogram.WithLabelValues(id, method, code).Observe(float64(sizeBytes)) +func (r recorder) ObserveHTTPResponseSize(_ context.Context, p metrics.HTTPReqProperties, sizeBytes int64) { + r.httpResponseSizeHistogram.WithLabelValues(p.Service, p.ID, p.Method, p.Code).Observe(float64(sizeBytes)) } -func (r recorder) AddInflightRequests(_ context.Context, id string, quantity int) { - r.httpRequestsInflight.WithLabelValues(id).Add(float64(quantity)) +func (r recorder) AddInflightRequests(_ context.Context, p metrics.HTTPProperties, quantity int) { + r.httpRequestsInflight.WithLabelValues(p.Service, p.ID).Add(float64(quantity)) } diff --git a/metrics/prometheus/prometheus_test.go b/metrics/prometheus/prometheus_test.go index da87420..bbc8be8 100644 --- a/metrics/prometheus/prometheus_test.go +++ b/metrics/prometheus/prometheus_test.go @@ -27,85 +27,85 @@ func TestPrometheusRecorder(t *testing.T) { name: "Default configuration should measure with the default metric style.", config: libprometheus.Config{}, recordMetrics: func(r metrics.Recorder) { - r.ObserveHTTPRequestDuration(context.TODO(), "test1", 5*time.Second, http.MethodGet, "200") - r.ObserveHTTPRequestDuration(context.TODO(), "test1", 175*time.Millisecond, http.MethodGet, "200") - r.ObserveHTTPRequestDuration(context.TODO(), "test2", 50*time.Millisecond, http.MethodGet, "201") - r.ObserveHTTPRequestDuration(context.TODO(), "test3", 700*time.Millisecond, http.MethodPost, "500") - r.ObserveHTTPResponseSize(context.TODO(), "test4", 529930, http.MethodPost, "500") - r.ObserveHTTPResponseSize(context.TODO(), "test4", 231, http.MethodPost, "500") - r.ObserveHTTPResponseSize(context.TODO(), "test4", 99999999, http.MethodPatch, "429") - r.AddInflightRequests(context.TODO(), "test1", 5) - r.AddInflightRequests(context.TODO(), "test1", -3) - r.AddInflightRequests(context.TODO(), "test2", 9) + r.ObserveHTTPRequestDuration(context.TODO(), metrics.HTTPReqProperties{Service: "svc1", ID: "test1", Method: http.MethodGet, Code: "200"}, 5*time.Second) + r.ObserveHTTPRequestDuration(context.TODO(), metrics.HTTPReqProperties{Service: "svc1", ID: "test1", Method: http.MethodGet, Code: "200"}, 175*time.Millisecond) + r.ObserveHTTPRequestDuration(context.TODO(), metrics.HTTPReqProperties{Service: "svc1", ID: "test2", Method: http.MethodGet, Code: "201"}, 50*time.Millisecond) + r.ObserveHTTPRequestDuration(context.TODO(), metrics.HTTPReqProperties{Service: "svc2", ID: "test3", Method: http.MethodPost, Code: "500"}, 700*time.Millisecond) + r.ObserveHTTPResponseSize(context.TODO(), metrics.HTTPReqProperties{Service: "svc1", ID: "test4", Method: http.MethodPost, Code: "500"}, 529930) + r.ObserveHTTPResponseSize(context.TODO(), metrics.HTTPReqProperties{Service: "svc1", ID: "test4", Method: http.MethodPost, Code: "500"}, 231) + r.ObserveHTTPResponseSize(context.TODO(), metrics.HTTPReqProperties{Service: "svc2", ID: "test4", Method: http.MethodPatch, Code: "429"}, 99999999) + r.AddInflightRequests(context.TODO(), metrics.HTTPProperties{Service: "svc1", ID: "test1"}, 5) + r.AddInflightRequests(context.TODO(), metrics.HTTPProperties{Service: "svc1", ID: "test1"}, -3) + r.AddInflightRequests(context.TODO(), metrics.HTTPProperties{Service: "svc2", ID: "test2"}, 9) }, expMetrics: []string{ - `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="0.005"} 0`, - `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="0.01"} 0`, - `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="0.025"} 0`, - `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="0.05"} 0`, - `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="0.1"} 0`, - `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="0.25"} 1`, - `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="0.5"} 1`, - `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="1"} 1`, - `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="2.5"} 1`, - `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="5"} 2`, - `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="10"} 2`, - `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="+Inf"} 2`, - `http_request_duration_seconds_count{code="200",handler="test1",method="GET"} 2`, + `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="0.005"} 0`, + `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="0.01"} 0`, + `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="0.025"} 0`, + `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="0.05"} 0`, + `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="0.1"} 0`, + `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="0.25"} 1`, + `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="0.5"} 1`, + `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="1"} 1`, + `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="2.5"} 1`, + `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="5"} 2`, + `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="10"} 2`, + `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="+Inf"} 2`, + `http_request_duration_seconds_count{code="200",handler="test1",method="GET",service="svc1"} 2`, - `http_request_duration_seconds_bucket{code="201",handler="test2",method="GET",le="0.005"} 0`, - `http_request_duration_seconds_bucket{code="201",handler="test2",method="GET",le="0.01"} 0`, - `http_request_duration_seconds_bucket{code="201",handler="test2",method="GET",le="0.025"} 0`, - `http_request_duration_seconds_bucket{code="201",handler="test2",method="GET",le="0.05"} 1`, - `http_request_duration_seconds_bucket{code="201",handler="test2",method="GET",le="0.1"} 1`, - `http_request_duration_seconds_bucket{code="201",handler="test2",method="GET",le="0.25"} 1`, - `http_request_duration_seconds_bucket{code="201",handler="test2",method="GET",le="0.5"} 1`, - `http_request_duration_seconds_bucket{code="201",handler="test2",method="GET",le="1"} 1`, - `http_request_duration_seconds_bucket{code="201",handler="test2",method="GET",le="2.5"} 1`, - `http_request_duration_seconds_bucket{code="201",handler="test2",method="GET",le="5"} 1`, - `http_request_duration_seconds_bucket{code="201",handler="test2",method="GET",le="10"} 1`, - `http_request_duration_seconds_bucket{code="201",handler="test2",method="GET",le="+Inf"} 1`, - `http_request_duration_seconds_count{code="201",handler="test2",method="GET"} 1`, + `http_request_duration_seconds_bucket{code="201",handler="test2",method="GET",service="svc1",le="0.005"} 0`, + `http_request_duration_seconds_bucket{code="201",handler="test2",method="GET",service="svc1",le="0.01"} 0`, + `http_request_duration_seconds_bucket{code="201",handler="test2",method="GET",service="svc1",le="0.025"} 0`, + `http_request_duration_seconds_bucket{code="201",handler="test2",method="GET",service="svc1",le="0.05"} 1`, + `http_request_duration_seconds_bucket{code="201",handler="test2",method="GET",service="svc1",le="0.1"} 1`, + `http_request_duration_seconds_bucket{code="201",handler="test2",method="GET",service="svc1",le="0.25"} 1`, + `http_request_duration_seconds_bucket{code="201",handler="test2",method="GET",service="svc1",le="0.5"} 1`, + `http_request_duration_seconds_bucket{code="201",handler="test2",method="GET",service="svc1",le="1"} 1`, + `http_request_duration_seconds_bucket{code="201",handler="test2",method="GET",service="svc1",le="2.5"} 1`, + `http_request_duration_seconds_bucket{code="201",handler="test2",method="GET",service="svc1",le="5"} 1`, + `http_request_duration_seconds_bucket{code="201",handler="test2",method="GET",service="svc1",le="10"} 1`, + `http_request_duration_seconds_bucket{code="201",handler="test2",method="GET",service="svc1",le="+Inf"} 1`, + `http_request_duration_seconds_count{code="201",handler="test2",method="GET",service="svc1"} 1`, - `http_request_duration_seconds_bucket{code="500",handler="test3",method="POST",le="0.005"} 0`, - `http_request_duration_seconds_bucket{code="500",handler="test3",method="POST",le="0.01"} 0`, - `http_request_duration_seconds_bucket{code="500",handler="test3",method="POST",le="0.025"} 0`, - `http_request_duration_seconds_bucket{code="500",handler="test3",method="POST",le="0.05"} 0`, - `http_request_duration_seconds_bucket{code="500",handler="test3",method="POST",le="0.1"} 0`, - `http_request_duration_seconds_bucket{code="500",handler="test3",method="POST",le="0.25"} 0`, - `http_request_duration_seconds_bucket{code="500",handler="test3",method="POST",le="0.5"} 0`, - `http_request_duration_seconds_bucket{code="500",handler="test3",method="POST",le="1"} 1`, - `http_request_duration_seconds_bucket{code="500",handler="test3",method="POST",le="2.5"} 1`, - `http_request_duration_seconds_bucket{code="500",handler="test3",method="POST",le="5"} 1`, - `http_request_duration_seconds_bucket{code="500",handler="test3",method="POST",le="10"} 1`, - `http_request_duration_seconds_bucket{code="500",handler="test3",method="POST",le="+Inf"} 1`, - `http_request_duration_seconds_count{code="500",handler="test3",method="POST"} 1`, + `http_request_duration_seconds_bucket{code="500",handler="test3",method="POST",service="svc2",le="0.005"} 0`, + `http_request_duration_seconds_bucket{code="500",handler="test3",method="POST",service="svc2",le="0.01"} 0`, + `http_request_duration_seconds_bucket{code="500",handler="test3",method="POST",service="svc2",le="0.025"} 0`, + `http_request_duration_seconds_bucket{code="500",handler="test3",method="POST",service="svc2",le="0.05"} 0`, + `http_request_duration_seconds_bucket{code="500",handler="test3",method="POST",service="svc2",le="0.1"} 0`, + `http_request_duration_seconds_bucket{code="500",handler="test3",method="POST",service="svc2",le="0.25"} 0`, + `http_request_duration_seconds_bucket{code="500",handler="test3",method="POST",service="svc2",le="0.5"} 0`, + `http_request_duration_seconds_bucket{code="500",handler="test3",method="POST",service="svc2",le="1"} 1`, + `http_request_duration_seconds_bucket{code="500",handler="test3",method="POST",service="svc2",le="2.5"} 1`, + `http_request_duration_seconds_bucket{code="500",handler="test3",method="POST",service="svc2",le="5"} 1`, + `http_request_duration_seconds_bucket{code="500",handler="test3",method="POST",service="svc2",le="10"} 1`, + `http_request_duration_seconds_bucket{code="500",handler="test3",method="POST",service="svc2",le="+Inf"} 1`, + `http_request_duration_seconds_count{code="500",handler="test3",method="POST",service="svc2"} 1`, - `http_response_size_bytes_bucket{code="429",handler="test4",method="PATCH",le="100"} 0`, - `http_response_size_bytes_bucket{code="429",handler="test4",method="PATCH",le="1000"} 0`, - `http_response_size_bytes_bucket{code="429",handler="test4",method="PATCH",le="10000"} 0`, - `http_response_size_bytes_bucket{code="429",handler="test4",method="PATCH",le="100000"} 0`, - `http_response_size_bytes_bucket{code="429",handler="test4",method="PATCH",le="1e+06"} 0`, - `http_response_size_bytes_bucket{code="429",handler="test4",method="PATCH",le="1e+07"} 0`, - `http_response_size_bytes_bucket{code="429",handler="test4",method="PATCH",le="1e+08"} 1`, - `http_response_size_bytes_bucket{code="429",handler="test4",method="PATCH",le="1e+09"} 1`, - `http_response_size_bytes_bucket{code="429",handler="test4",method="PATCH",le="+Inf"} 1`, - `http_response_size_bytes_count{code="429",handler="test4",method="PATCH"} 1`, + `http_response_size_bytes_bucket{code="429",handler="test4",method="PATCH",service="svc2",le="100"} 0`, + `http_response_size_bytes_bucket{code="429",handler="test4",method="PATCH",service="svc2",le="1000"} 0`, + `http_response_size_bytes_bucket{code="429",handler="test4",method="PATCH",service="svc2",le="10000"} 0`, + `http_response_size_bytes_bucket{code="429",handler="test4",method="PATCH",service="svc2",le="100000"} 0`, + `http_response_size_bytes_bucket{code="429",handler="test4",method="PATCH",service="svc2",le="1e+06"} 0`, + `http_response_size_bytes_bucket{code="429",handler="test4",method="PATCH",service="svc2",le="1e+07"} 0`, + `http_response_size_bytes_bucket{code="429",handler="test4",method="PATCH",service="svc2",le="1e+08"} 1`, + `http_response_size_bytes_bucket{code="429",handler="test4",method="PATCH",service="svc2",le="1e+09"} 1`, + `http_response_size_bytes_bucket{code="429",handler="test4",method="PATCH",service="svc2",le="+Inf"} 1`, + `http_response_size_bytes_count{code="429",handler="test4",method="PATCH",service="svc2"} 1`, - `http_response_size_bytes_bucket{code="500",handler="test4",method="POST",le="100"} 0`, - `http_response_size_bytes_bucket{code="500",handler="test4",method="POST",le="1000"} 1`, - `http_response_size_bytes_bucket{code="500",handler="test4",method="POST",le="10000"} 1`, - `http_response_size_bytes_bucket{code="500",handler="test4",method="POST",le="100000"} 1`, - `http_response_size_bytes_bucket{code="500",handler="test4",method="POST",le="1e+06"} 2`, - `http_response_size_bytes_bucket{code="500",handler="test4",method="POST",le="1e+07"} 2`, - `http_response_size_bytes_bucket{code="500",handler="test4",method="POST",le="1e+08"} 2`, - `http_response_size_bytes_bucket{code="500",handler="test4",method="POST",le="1e+09"} 2`, - `http_response_size_bytes_bucket{code="500",handler="test4",method="POST",le="+Inf"} 2`, - `http_response_size_bytes_count{code="500",handler="test4",method="POST"} 2`, + `http_response_size_bytes_bucket{code="500",handler="test4",method="POST",service="svc1",le="100"} 0`, + `http_response_size_bytes_bucket{code="500",handler="test4",method="POST",service="svc1",le="1000"} 1`, + `http_response_size_bytes_bucket{code="500",handler="test4",method="POST",service="svc1",le="10000"} 1`, + `http_response_size_bytes_bucket{code="500",handler="test4",method="POST",service="svc1",le="100000"} 1`, + `http_response_size_bytes_bucket{code="500",handler="test4",method="POST",service="svc1",le="1e+06"} 2`, + `http_response_size_bytes_bucket{code="500",handler="test4",method="POST",service="svc1",le="1e+07"} 2`, + `http_response_size_bytes_bucket{code="500",handler="test4",method="POST",service="svc1",le="1e+08"} 2`, + `http_response_size_bytes_bucket{code="500",handler="test4",method="POST",service="svc1",le="1e+09"} 2`, + `http_response_size_bytes_bucket{code="500",handler="test4",method="POST",service="svc1",le="+Inf"} 2`, + `http_response_size_bytes_count{code="500",handler="test4",method="POST",service="svc1"} 2`, - `http_requests_inflight{handler="test1"} 2`, + `http_requests_inflight{handler="test1",service="svc1"} 2`, - `http_requests_inflight{handler="test2"} 9`, + `http_requests_inflight{handler="test2",service="svc2"} 9`, }, }, { @@ -114,23 +114,23 @@ func TestPrometheusRecorder(t *testing.T) { Prefix: "batman", }, recordMetrics: func(r metrics.Recorder) { - r.ObserveHTTPRequestDuration(context.TODO(), "test1", 5*time.Second, http.MethodGet, "200") - r.ObserveHTTPRequestDuration(context.TODO(), "test1", 175*time.Millisecond, http.MethodGet, "200") + r.ObserveHTTPRequestDuration(context.TODO(), metrics.HTTPReqProperties{Service: "svc1", ID: "test1", Method: http.MethodGet, Code: "200"}, 5*time.Second) + r.ObserveHTTPRequestDuration(context.TODO(), metrics.HTTPReqProperties{Service: "svc1", ID: "test1", Method: http.MethodGet, Code: "200"}, 175*time.Millisecond) }, expMetrics: []string{ - `batman_http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="0.005"} 0`, - `batman_http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="0.01"} 0`, - `batman_http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="0.025"} 0`, - `batman_http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="0.05"} 0`, - `batman_http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="0.1"} 0`, - `batman_http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="0.25"} 1`, - `batman_http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="0.5"} 1`, - `batman_http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="1"} 1`, - `batman_http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="2.5"} 1`, - `batman_http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="5"} 2`, - `batman_http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="10"} 2`, - `batman_http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="+Inf"} 2`, - `batman_http_request_duration_seconds_count{code="200",handler="test1",method="GET"} 2`, + `batman_http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="0.005"} 0`, + `batman_http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="0.01"} 0`, + `batman_http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="0.025"} 0`, + `batman_http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="0.05"} 0`, + `batman_http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="0.1"} 0`, + `batman_http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="0.25"} 1`, + `batman_http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="0.5"} 1`, + `batman_http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="1"} 1`, + `batman_http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="2.5"} 1`, + `batman_http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="5"} 2`, + `batman_http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="10"} 2`, + `batman_http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="+Inf"} 2`, + `batman_http_request_duration_seconds_count{code="200",handler="test1",method="GET",service="svc1"} 2`, }, }, { @@ -139,23 +139,23 @@ func TestPrometheusRecorder(t *testing.T) { DurationBuckets: []float64{1, 2, 10, 20, 50, 200, 500, 1000, 2000, 5000, 10000}, }, recordMetrics: func(r metrics.Recorder) { - r.ObserveHTTPRequestDuration(context.TODO(), "test1", 75*time.Minute, http.MethodGet, "200") - r.ObserveHTTPRequestDuration(context.TODO(), "test1", 200*time.Hour, http.MethodGet, "200") + r.ObserveHTTPRequestDuration(context.TODO(), metrics.HTTPReqProperties{Service: "svc1", ID: "test1", Method: http.MethodGet, Code: "200"}, 75*time.Minute) + r.ObserveHTTPRequestDuration(context.TODO(), metrics.HTTPReqProperties{Service: "svc1", ID: "test1", Method: http.MethodGet, Code: "200"}, 200*time.Hour) }, expMetrics: []string{ - `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="1"} 0`, - `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="2"} 0`, - `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="10"} 0`, - `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="20"} 0`, - `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="50"} 0`, - `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="200"} 0`, - `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="500"} 0`, - `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="1000"} 0`, - `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="2000"} 0`, - `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="5000"} 1`, - `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="10000"} 1`, - `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",le="+Inf"} 2`, - `http_request_duration_seconds_count{code="200",handler="test1",method="GET"} 2`, + `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="1"} 0`, + `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="2"} 0`, + `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="10"} 0`, + `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="20"} 0`, + `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="50"} 0`, + `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="200"} 0`, + `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="500"} 0`, + `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="1000"} 0`, + `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="2000"} 0`, + `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="5000"} 1`, + `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="10000"} 1`, + `http_request_duration_seconds_bucket{code="200",handler="test1",method="GET",service="svc1",le="+Inf"} 2`, + `http_request_duration_seconds_count{code="200",handler="test1",method="GET",service="svc1"} 2`, }, }, { @@ -164,25 +164,26 @@ func TestPrometheusRecorder(t *testing.T) { HandlerIDLabel: "route_id", StatusCodeLabel: "status_code", MethodLabel: "http_method", + ServiceLabel: "http_service", }, recordMetrics: func(r metrics.Recorder) { - r.ObserveHTTPRequestDuration(context.TODO(), "test1", 5*time.Second, http.MethodGet, "200") - r.ObserveHTTPRequestDuration(context.TODO(), "test1", 175*time.Millisecond, http.MethodGet, "200") + r.ObserveHTTPRequestDuration(context.TODO(), metrics.HTTPReqProperties{Service: "svc1", ID: "test1", Method: http.MethodGet, Code: "200"}, 6*time.Second) + r.ObserveHTTPRequestDuration(context.TODO(), metrics.HTTPReqProperties{Service: "svc1", ID: "test1", Method: http.MethodGet, Code: "200"}, 175*time.Millisecond) }, expMetrics: []string{ - `http_request_duration_seconds_bucket{http_method="GET",route_id="test1",status_code="200",le="0.005"} 0`, - `http_request_duration_seconds_bucket{http_method="GET",route_id="test1",status_code="200",le="0.01"} 0`, - `http_request_duration_seconds_bucket{http_method="GET",route_id="test1",status_code="200",le="0.025"} 0`, - `http_request_duration_seconds_bucket{http_method="GET",route_id="test1",status_code="200",le="0.05"} 0`, - `http_request_duration_seconds_bucket{http_method="GET",route_id="test1",status_code="200",le="0.1"} 0`, - `http_request_duration_seconds_bucket{http_method="GET",route_id="test1",status_code="200",le="0.25"} 1`, - `http_request_duration_seconds_bucket{http_method="GET",route_id="test1",status_code="200",le="0.5"} 1`, - `http_request_duration_seconds_bucket{http_method="GET",route_id="test1",status_code="200",le="1"} 1`, - `http_request_duration_seconds_bucket{http_method="GET",route_id="test1",status_code="200",le="2.5"} 1`, - `http_request_duration_seconds_bucket{http_method="GET",route_id="test1",status_code="200",le="5"} 2`, - `http_request_duration_seconds_bucket{http_method="GET",route_id="test1",status_code="200",le="10"} 2`, - `http_request_duration_seconds_bucket{http_method="GET",route_id="test1",status_code="200",le="+Inf"} 2`, - `http_request_duration_seconds_count{http_method="GET",route_id="test1",status_code="200"} 2`, + `http_request_duration_seconds_bucket{http_method="GET",http_service="svc1",route_id="test1",status_code="200",le="0.005"} 0`, + `http_request_duration_seconds_bucket{http_method="GET",http_service="svc1",route_id="test1",status_code="200",le="0.01"} 0`, + `http_request_duration_seconds_bucket{http_method="GET",http_service="svc1",route_id="test1",status_code="200",le="0.025"} 0`, + `http_request_duration_seconds_bucket{http_method="GET",http_service="svc1",route_id="test1",status_code="200",le="0.05"} 0`, + `http_request_duration_seconds_bucket{http_method="GET",http_service="svc1",route_id="test1",status_code="200",le="0.1"} 0`, + `http_request_duration_seconds_bucket{http_method="GET",http_service="svc1",route_id="test1",status_code="200",le="0.25"} 1`, + `http_request_duration_seconds_bucket{http_method="GET",http_service="svc1",route_id="test1",status_code="200",le="0.5"} 1`, + `http_request_duration_seconds_bucket{http_method="GET",http_service="svc1",route_id="test1",status_code="200",le="1"} 1`, + `http_request_duration_seconds_bucket{http_method="GET",http_service="svc1",route_id="test1",status_code="200",le="2.5"} 1`, + `http_request_duration_seconds_bucket{http_method="GET",http_service="svc1",route_id="test1",status_code="200",le="5"} 1`, + `http_request_duration_seconds_bucket{http_method="GET",http_service="svc1",route_id="test1",status_code="200",le="10"} 2`, + `http_request_duration_seconds_bucket{http_method="GET",http_service="svc1",route_id="test1",status_code="200",le="+Inf"} 2`, + `http_request_duration_seconds_count{http_method="GET",http_service="svc1",route_id="test1",status_code="200"} 2`, }, }, } diff --git a/middleware/gin/gin_test.go b/middleware/gin/gin_test.go index 1f86314..f8302d4 100644 --- a/middleware/gin/gin_test.go +++ b/middleware/gin/gin_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/mock" mmetrics "github.com/slok/go-http-metrics/internal/mocks/metrics" + "github.com/slok/go-http-metrics/metrics" "github.com/slok/go-http-metrics/middleware" ginmiddleware "github.com/slok/go-http-metrics/middleware/gin" ) @@ -28,6 +29,7 @@ func TestMiddlewareIntegration(t *testing.T) { req *http.Request config middleware.Config expHandlerID string + expService string expMethod string expStatusCode string }{ @@ -47,10 +49,20 @@ func TestMiddlewareIntegration(t *testing.T) { // Mocks. mr := &mmetrics.Recorder{} - mr.On("ObserveHTTPRequestDuration", mock.Anything, test.expHandlerID, mock.Anything, test.expMethod, test.expStatusCode).Once() - mr.On("ObserveHTTPResponseSize", mock.Anything, test.expHandlerID, mock.Anything, test.expMethod, test.expStatusCode).Once() - mr.On("AddInflightRequests", mock.Anything, test.expHandlerID, 1).Once() - mr.On("AddInflightRequests", mock.Anything, test.expHandlerID, -1).Once() + expHTTPReqProps := metrics.HTTPReqProperties{ + ID: test.expHandlerID, + Service: test.expService, + Method: test.expMethod, + Code: test.expStatusCode, + } + expHTTPProps := metrics.HTTPProperties{ + ID: test.expHandlerID, + Service: test.expService, + } + mr.On("ObserveHTTPRequestDuration", mock.Anything, expHTTPReqProps, mock.Anything).Once() + mr.On("ObserveHTTPResponseSize", mock.Anything, expHTTPReqProps, mock.Anything).Once() + mr.On("AddInflightRequests", mock.Anything, expHTTPProps, 1).Once() + mr.On("AddInflightRequests", mock.Anything, expHTTPProps, -1).Once() // Create our instance with the middleware. mdlw := middleware.New(middleware.Config{Recorder: mr}) diff --git a/middleware/gorestful/gorestful_test.go b/middleware/gorestful/gorestful_test.go index a15da59..d97f070 100644 --- a/middleware/gorestful/gorestful_test.go +++ b/middleware/gorestful/gorestful_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/mock" mmetrics "github.com/slok/go-http-metrics/internal/mocks/metrics" + "github.com/slok/go-http-metrics/metrics" "github.com/slok/go-http-metrics/middleware" gorestfulmiddleware "github.com/slok/go-http-metrics/middleware/gorestful" ) @@ -28,6 +29,7 @@ func TestMiddlewareIntegration(t *testing.T) { req *http.Request config middleware.Config expHandlerID string + expService string expMethod string expStatusCode string }{ @@ -47,10 +49,20 @@ func TestMiddlewareIntegration(t *testing.T) { // Mocks. mr := &mmetrics.Recorder{} - mr.On("ObserveHTTPRequestDuration", mock.Anything, test.expHandlerID, mock.Anything, test.expMethod, test.expStatusCode).Once() - mr.On("ObserveHTTPResponseSize", mock.Anything, test.expHandlerID, mock.Anything, test.expMethod, test.expStatusCode).Once() - mr.On("AddInflightRequests", mock.Anything, test.expHandlerID, 1).Once() - mr.On("AddInflightRequests", mock.Anything, test.expHandlerID, -1).Once() + expHTTPReqProps := metrics.HTTPReqProperties{ + ID: test.expHandlerID, + Service: test.expService, + Method: test.expMethod, + Code: test.expStatusCode, + } + expHTTPProps := metrics.HTTPProperties{ + ID: test.expHandlerID, + Service: test.expService, + } + mr.On("ObserveHTTPRequestDuration", mock.Anything, expHTTPReqProps, mock.Anything).Once() + mr.On("ObserveHTTPResponseSize", mock.Anything, expHTTPReqProps, mock.Anything).Once() + mr.On("AddInflightRequests", mock.Anything, expHTTPProps, 1).Once() + mr.On("AddInflightRequests", mock.Anything, expHTTPProps, -1).Once() // Create our instance with the middleware. mdlw := middleware.New(middleware.Config{Recorder: mr}) diff --git a/middleware/httprouter/httprouter_test.go b/middleware/httprouter/httprouter_test.go index 7af5673..431a1f1 100644 --- a/middleware/httprouter/httprouter_test.go +++ b/middleware/httprouter/httprouter_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/mock" mmetrics "github.com/slok/go-http-metrics/internal/mocks/metrics" + "github.com/slok/go-http-metrics/metrics" "github.com/slok/go-http-metrics/middleware" httproutermiddleware "github.com/slok/go-http-metrics/middleware/httprouter" ) @@ -28,6 +29,7 @@ func TestMiddlewareIntegration(t *testing.T) { req *http.Request config middleware.Config expHandlerID string + expService string expMethod string expStatusCode string }{ @@ -47,10 +49,20 @@ func TestMiddlewareIntegration(t *testing.T) { // Mocks. mr := &mmetrics.Recorder{} - mr.On("ObserveHTTPRequestDuration", mock.Anything, test.expHandlerID, mock.Anything, test.expMethod, test.expStatusCode).Once() - mr.On("ObserveHTTPResponseSize", mock.Anything, test.expHandlerID, mock.Anything, test.expMethod, test.expStatusCode).Once() - mr.On("AddInflightRequests", mock.Anything, test.expHandlerID, 1).Once() - mr.On("AddInflightRequests", mock.Anything, test.expHandlerID, -1).Once() + expHTTPReqProps := metrics.HTTPReqProperties{ + ID: test.expHandlerID, + Service: test.expService, + Method: test.expMethod, + Code: test.expStatusCode, + } + expHTTPProps := metrics.HTTPProperties{ + ID: test.expHandlerID, + Service: test.expService, + } + mr.On("ObserveHTTPRequestDuration", mock.Anything, expHTTPReqProps, mock.Anything).Once() + mr.On("ObserveHTTPResponseSize", mock.Anything, expHTTPReqProps, mock.Anything).Once() + mr.On("AddInflightRequests", mock.Anything, expHTTPProps, 1).Once() + mr.On("AddInflightRequests", mock.Anything, expHTTPProps, -1).Once() // Create our instance with the middleware. mdlw := middleware.New(middleware.Config{Recorder: mr}) diff --git a/middleware/middleware.go b/middleware/middleware.go index 7df0289..ff30c46 100644 --- a/middleware/middleware.go +++ b/middleware/middleware.go @@ -17,6 +17,9 @@ import ( type Config struct { // Recorder is the way the metrics will be recorder in the different backends. Recorder metrics.Recorder + // Service is an optional identifier for the metrics, this can be useful if + // a same service has multiple servers (e.g API, metrics and healthchecks). + Service string // GroupedStatus will group the status label in the form of `\dxx`, for example, // 200, 201, and 203 will have the label `code="2xx"`. This impacts on the cardinality // of the metrics and also improves the performance of queries that are grouped by @@ -86,8 +89,12 @@ func (m *middleware) Handler(handlerID string, h http.Handler) http.Handler { // Measure inflights if required. if !m.cfg.DisableMeasureInflight { - m.cfg.Recorder.AddInflightRequests(r.Context(), hid, 1) - defer m.cfg.Recorder.AddInflightRequests(r.Context(), hid, -1) + props := metrics.HTTPProperties{ + Service: m.cfg.Service, + ID: hid, + } + m.cfg.Recorder.AddInflightRequests(r.Context(), props, 1) + defer m.cfg.Recorder.AddInflightRequests(r.Context(), props, -1) } // Start the timer and when finishing measure the duration. @@ -105,11 +112,17 @@ func (m *middleware) Handler(handlerID string, h http.Handler) http.Handler { code = strconv.Itoa(wi.statusCode) } - m.cfg.Recorder.ObserveHTTPRequestDuration(r.Context(), hid, duration, r.Method, code) + props := metrics.HTTPReqProperties{ + Service: m.cfg.Service, + ID: hid, + Method: r.Method, + Code: code, + } + m.cfg.Recorder.ObserveHTTPRequestDuration(r.Context(), props, duration) // Measure size of response if required. if !m.cfg.DisableMeasureSize { - m.cfg.Recorder.ObserveHTTPResponseSize(r.Context(), hid, int64(wi.bytesWritten), r.Method, code) + m.cfg.Recorder.ObserveHTTPResponseSize(r.Context(), props, int64(wi.bytesWritten)) } }() diff --git a/middleware/middleware_test.go b/middleware/middleware_test.go index 4b551c0..6b36502 100644 --- a/middleware/middleware_test.go +++ b/middleware/middleware_test.go @@ -27,6 +27,7 @@ func TestMiddlewareHandler(t *testing.T) { req *http.Request config middleware.Config expHandlerID string + expService string expMethod string expSize int64 expStatusCode string @@ -37,6 +38,7 @@ func TestMiddlewareHandler(t *testing.T) { body: "Я бэтмен", req: httptest.NewRequest(http.MethodGet, "/test", nil), expHandlerID: "/test", + expService: "", expSize: 15, expMethod: http.MethodGet, expStatusCode: "202", @@ -48,6 +50,7 @@ func TestMiddlewareHandler(t *testing.T) { statusCode: http.StatusTeapot, req: httptest.NewRequest(http.MethodPost, "/test", nil), expHandlerID: "customID", + expService: "", expSize: 10, expMethod: http.MethodPost, expStatusCode: "418", @@ -58,19 +61,42 @@ func TestMiddlewareHandler(t *testing.T) { statusCode: http.StatusGatewayTimeout, req: httptest.NewRequest(http.MethodPatch, "/test", nil), expHandlerID: "/test", + expService: "", expMethod: http.MethodPatch, expStatusCode: "5xx", }, + { + name: "Using the service middleware option should set the service on the metrics.", + config: middleware.Config{Service: "Yoda"}, + statusCode: http.StatusContinue, + body: "May the force be with you", + req: httptest.NewRequest(http.MethodGet, "/test", nil), + expHandlerID: "/test", + expService: "Yoda", + expSize: 25, + expMethod: http.MethodGet, + expStatusCode: "100", + }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { // Mocks. mr := &mmetrics.Recorder{} - mr.On("ObserveHTTPRequestDuration", mock.Anything, test.expHandlerID, mock.Anything, test.expMethod, test.expStatusCode).Once() - mr.On("ObserveHTTPResponseSize", mock.Anything, test.expHandlerID, test.expSize, test.expMethod, test.expStatusCode).Once() - mr.On("AddInflightRequests", mock.Anything, test.expHandlerID, 1).Once() - mr.On("AddInflightRequests", mock.Anything, test.expHandlerID, -1).Once() + expHTTPReqProps := metrics.HTTPReqProperties{ + ID: test.expHandlerID, + Service: test.expService, + Method: test.expMethod, + Code: test.expStatusCode, + } + expHTTPProps := metrics.HTTPProperties{ + ID: test.expHandlerID, + Service: test.expService, + } + mr.On("ObserveHTTPRequestDuration", mock.Anything, expHTTPReqProps, mock.Anything).Once() + mr.On("ObserveHTTPResponseSize", mock.Anything, expHTTPReqProps, test.expSize).Once() + mr.On("AddInflightRequests", mock.Anything, expHTTPProps, 1).Once() + mr.On("AddInflightRequests", mock.Anything, expHTTPProps, -1).Once() // Make the request. test.config.Recorder = mr diff --git a/middleware/negroni/negroni_test.go b/middleware/negroni/negroni_test.go index 156be61..dba524a 100644 --- a/middleware/negroni/negroni_test.go +++ b/middleware/negroni/negroni_test.go @@ -10,6 +10,7 @@ import ( "github.com/urfave/negroni" mmetrics "github.com/slok/go-http-metrics/internal/mocks/metrics" + "github.com/slok/go-http-metrics/metrics" "github.com/slok/go-http-metrics/middleware" negronimiddleware "github.com/slok/go-http-metrics/middleware/negroni" ) @@ -28,6 +29,7 @@ func TestMiddlewareIntegration(t *testing.T) { req *http.Request config middleware.Config expHandlerID string + expService string expMethod string expStatusCode string }{ @@ -47,10 +49,20 @@ func TestMiddlewareIntegration(t *testing.T) { // Mocks. mr := &mmetrics.Recorder{} - mr.On("ObserveHTTPRequestDuration", mock.Anything, test.expHandlerID, mock.Anything, test.expMethod, test.expStatusCode).Once() - mr.On("ObserveHTTPResponseSize", mock.Anything, test.expHandlerID, mock.Anything, test.expMethod, test.expStatusCode).Once() - mr.On("AddInflightRequests", mock.Anything, test.expHandlerID, 1).Once() - mr.On("AddInflightRequests", mock.Anything, test.expHandlerID, -1).Once() + expHTTPReqProps := metrics.HTTPReqProperties{ + ID: test.expHandlerID, + Service: test.expService, + Method: test.expMethod, + Code: test.expStatusCode, + } + expHTTPProps := metrics.HTTPProperties{ + ID: test.expHandlerID, + Service: test.expService, + } + mr.On("ObserveHTTPRequestDuration", mock.Anything, expHTTPReqProps, mock.Anything).Once() + mr.On("ObserveHTTPResponseSize", mock.Anything, expHTTPReqProps, mock.Anything).Once() + mr.On("AddInflightRequests", mock.Anything, expHTTPProps, 1).Once() + mr.On("AddInflightRequests", mock.Anything, expHTTPProps, -1).Once() // Create our negroni instance with the middleware. mdlw := middleware.New(middleware.Config{Recorder: mr})