diff --git a/.schemas/config.schema.json b/.schemas/config.schema.json index 8afbadc927..1c9f5b7c0c 100644 --- a/.schemas/config.schema.json +++ b/.schemas/config.schema.json @@ -616,7 +616,8 @@ "description": "The OAuth 2.0 Scope to be requested during the OAuth 2.0 Client Credentials Grant.", "examples": [ [ - "foo", "bar" + "foo", + "bar" ] ] } @@ -1001,6 +1002,35 @@ "$ref": "#/definitions/tlsx" } } + }, + "prometheus": { + "type": "object", + "title": "Prometheus scraping endpoint", + "additionalProperties": false, + "properties": { + "port": { + "type": "integer", + "default": 9000, + "title": "Port", + "description": "The port to listen on." + }, + "host": { + "type": "string", + "default": "", + "examples": [ + "localhost", + "127.0.0.1" + ], + "title": "Host", + "description": "The network interface to listen on. Leave empty to listen on all interfaces." + }, + "metrics_path": { + "type": "string", + "default": "/metrics", + "title": "Path", + "description": "The path to provide metrics on" + } + } } } }, @@ -1228,9 +1258,11 @@ "default": [ "json" ], - "examples": [[ - "redirect" - ]] + "examples": [ + [ + "redirect" + ] + ] }, "handlers": { "additionalProperties": false, @@ -1628,4 +1660,4 @@ }, "required": [], "additionalProperties": false -} +} \ No newline at end of file diff --git a/cmd/server/server.go b/cmd/server/server.go index 8576d4f454..7557150996 100644 --- a/cmd/server/server.go +++ b/cmd/server/server.go @@ -11,31 +11,30 @@ import ( "time" "github.com/pkg/errors" + "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/urfave/negroni" "github.com/ory/analytics-go/v4" - - "github.com/ory/x/reqlog" - + "github.com/ory/graceful" "github.com/ory/viper" - "github.com/ory/x/healthx" - - "github.com/ory/graceful" "github.com/ory/x/corsx" + "github.com/ory/x/healthx" "github.com/ory/x/logrusx" - "github.com/ory/x/metricsx" + telemetry "github.com/ory/x/metricsx" + "github.com/ory/x/reqlog" "github.com/ory/x/tlsx" "github.com/ory/oathkeeper/api" "github.com/ory/oathkeeper/driver" "github.com/ory/oathkeeper/driver/configuration" + "github.com/ory/oathkeeper/metrics" "github.com/ory/oathkeeper/x" ) -func runProxy(d driver.Driver, n *negroni.Negroni, logger *logrus.Logger) func() { +func runProxy(d driver.Driver, n *negroni.Negroni, logger *logrus.Logger, prom *metrics.PrometheusRepository) func() { return func() { proxy := d.Registry().Proxy() @@ -44,6 +43,7 @@ func runProxy(d driver.Driver, n *negroni.Negroni, logger *logrus.Logger) func() Transport: proxy, } + n.Use(metrics.NewMiddleware(prom, "oathkeeper-proxy").ExcludePaths(healthx.ReadyCheckPath, healthx.AliveCheckPath)) n.Use(reqlog.NewMiddlewareFromLogger(logger, "oathkeeper-proxy").ExcludePaths(healthx.ReadyCheckPath, healthx.AliveCheckPath)) n.UseHandler(handler) @@ -75,13 +75,14 @@ func runProxy(d driver.Driver, n *negroni.Negroni, logger *logrus.Logger) func() } } -func runAPI(d driver.Driver, n *negroni.Negroni, logger *logrus.Logger) func() { +func runAPI(d driver.Driver, n *negroni.Negroni, logger *logrus.Logger, prom *metrics.PrometheusRepository) func() { return func() { router := x.NewAPIRouter() d.Registry().RuleHandler().SetRoutes(router) d.Registry().HealthHandler().SetRoutes(router.Router, true) d.Registry().CredentialHandler().SetRoutes(router) + n.Use(metrics.NewMiddleware(prom, "oathkeeper-api").ExcludePaths(healthx.ReadyCheckPath, healthx.AliveCheckPath)) n.Use(reqlog.NewMiddlewareFromLogger(logger, "oathkeeper-api").ExcludePaths(healthx.ReadyCheckPath, healthx.AliveCheckPath)) n.Use(d.Registry().DecisionHandler()) // This needs to be the last entry, otherwise the judge API won't work @@ -111,6 +112,29 @@ func runAPI(d driver.Driver, n *negroni.Negroni, logger *logrus.Logger) func() { } } +func runPrometheus(d driver.Driver, logger *logrus.Logger, prom *metrics.PrometheusRepository) func() { + return func() { + promPath := d.Configuration().PrometheusMetricsPath() + promAddr := d.Configuration().PrometheusServeAddress() + + server := graceful.WithDefaults(&http.Server{ + Addr: promAddr, + Handler: promhttp.HandlerFor(prom.Registry, promhttp.HandlerOpts{}), + }) + + http.Handle(promPath, promhttp.Handler()) + // Expose the registered metrics via HTTP. + if err := graceful.Graceful(func() error { + logger.Infof("Listening on http://%s", promAddr) + return server.ListenAndServe() + }, server.Shutdown); err != nil { + logger.Fatalf("Unable to gracefully shutdown HTTP(s) server because %v", err) + return + } + logger.Println("HTTP server was shutdown gracefully") + } +} + func cert(daemon string, logger logrus.FieldLogger) []tls.Certificate { cert, err := tlsx.Certificate( viper.GetString("serve."+daemon+".tls.cert.base64"), @@ -161,10 +185,10 @@ func RunServe(version, build, date string) func(cmd *cobra.Command, args []strin adminmw := negroni.New() publicmw := negroni.New() - metrics := metricsx.New(cmd, logger, - &metricsx.Options{ + telemetry := telemetry.New(cmd, logger, + &telemetry.Options{ Service: "ory-oathkeeper", - ClusterID: metricsx.Hash(clusterID(d.Configuration())), + ClusterID: telemetry.Hash(clusterID(d.Configuration())), IsDevelopment: isDevelopment(d.Configuration()), WriteKey: "xRVRP48SAKw6ViJEnvB0u2PY8bVlsO6O", WhitelistedPaths: []string{ @@ -189,18 +213,21 @@ func RunServe(version, build, date string) func(cmd *cobra.Command, args []strin }, ) - adminmw.Use(metrics) - publicmw.Use(metrics) + adminmw.Use(telemetry) + publicmw.Use(telemetry) if tracer := d.Registry().Tracer(); tracer.IsLoaded() { adminmw.Use(tracer) publicmw.Use(tracer) } + prometheusRepo := metrics.NewPrometheusRepository(logger) + var wg sync.WaitGroup tasks := []func(){ - runAPI(d, adminmw, logger), - runProxy(d, publicmw, logger), + runAPI(d, adminmw, logger, prometheusRepo), + runProxy(d, publicmw, logger, prometheusRepo), + runPrometheus(d, logger, prometheusRepo), } wg.Add(len(tasks)) for _, t := range tasks { diff --git a/driver/configuration/provider.go b/driver/configuration/provider.go index 49c25bafa8..6ce9bd7826 100644 --- a/driver/configuration/provider.go +++ b/driver/configuration/provider.go @@ -48,6 +48,9 @@ type Provider interface { ProxyServeAddress() string APIServeAddress() string + PrometheusServeAddress() string + + PrometheusMetricsPath() string ToScopeStrategy(value string, key string) fosite.ScopeStrategy ParseURLs(sources []string) ([]url.URL, error) diff --git a/driver/configuration/provider_viper.go b/driver/configuration/provider_viper.go index 7ec1d664ce..b85bf60991 100644 --- a/driver/configuration/provider_viper.go +++ b/driver/configuration/provider_viper.go @@ -45,6 +45,9 @@ const ( ViperKeyProxyServeAddressPort = "serve.proxy.port" ViperKeyAPIServeAddressHost = "serve.api.host" ViperKeyAPIServeAddressPort = "serve.api.port" + ViperKeyPrometheusServeAddressHost = "serve.prometheus.host" + ViperKeyPrometheusServeAddressPort = "serve.prometheus.port" + ViperKeyPrometheusServeMetricsPath = "serve.prometheus.metrics_path" ViperKeyAccessRuleRepositories = "access_rules.repositories" ViperKeyAccessRuleMatchingStrategy = "access_rules.matching_strategy" ) @@ -178,6 +181,18 @@ func (v *ViperProvider) APIServeAddress() string { ) } +func (v *ViperProvider) PrometheusServeAddress() string { + return fmt.Sprintf( + "%s:%d", + viperx.GetString(v.l, ViperKeyPrometheusServeAddressHost, ""), + viperx.GetInt(v.l, ViperKeyPrometheusServeAddressPort, 9000), + ) +} + +func (v *ViperProvider) PrometheusMetricsPath() string { + return viperx.GetString(v.l, ViperKeyPrometheusServeMetricsPath, "/metrics") +} + func (v *ViperProvider) ParseURLs(sources []string) ([]url.URL, error) { r := make([]url.URL, len(sources)) for k, u := range sources { diff --git a/go.mod b/go.mod index 7ddf359b14..d5cf834d90 100644 --- a/go.mod +++ b/go.mod @@ -47,6 +47,7 @@ require ( github.com/pborman/uuid v1.2.0 github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 github.com/pkg/errors v0.9.1 + github.com/prometheus/client_golang v1.5.0 github.com/rs/cors v1.6.0 github.com/sirupsen/logrus v1.5.0 github.com/spf13/cobra v0.0.7 diff --git a/go.sum b/go.sum index 32ad7ffc3c..89a7259543 100644 --- a/go.sum +++ b/go.sum @@ -41,7 +41,9 @@ github.com/ajg/form v0.0.0-20160822230020-523a5da1a92f h1:zvClvFQwU++UpIUBGC8YmD github.com/ajg/form v0.0.0-20160822230020-523a5da1a92f/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= 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/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA= @@ -52,6 +54,8 @@ github.com/aws/aws-sdk-go v1.23.19/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpi github.com/aws/aws-xray-sdk-go v0.9.4/go.mod h1:XtMKdBQfpVut+tJEwI7+dJFRxxRdxHDyVNp2tHXRq04= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 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/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= @@ -65,6 +69,8 @@ github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3 github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575/go.mod h1:9d6lWj8KzO/fd/NrVaLscBKmPigpZpn5YawRPw+e3Yo= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= @@ -133,6 +139,7 @@ github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0 github.com/go-bindata/go-bindata v3.1.1+incompatible h1:tR4f0e4VTO7LK6B2YWyAoVEzG9ByG1wrXB4TL9+jiYg= github.com/go-bindata/go-bindata v3.1.1+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo= 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-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= @@ -472,6 +479,8 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -572,6 +581,8 @@ github.com/joho/godotenv v1.2.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqx github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= @@ -669,6 +680,7 @@ github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= github.com/mattn/goveralls v0.0.5 h1:spfq8AyZ0cCk57Za6/juJ5btQxeE1FaEGMdfcI+XO48= github.com/mattn/goveralls v0.0.5/go.mod h1:Xg2LHi51faXLyKXwsndxiW6uxEEQT9+3sjGzzwU4xy0= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= @@ -680,6 +692,10 @@ github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQz github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.2.2 h1:dxe5oCinTXiTIcfgmZecdCzPmAJKd46KsCWc35r0TV4= github.com/mitchellh/mapstructure v1.2.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/monoculum/formam v0.0.0-20180901015400-4e68be1d79ba/go.mod h1:RKgILGEJq24YyJ2ban8EO0RUVSJlF1pGsEvoLEACr/Q= @@ -787,12 +803,23 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.5.0 h1:Ctq0iGpCmr3jeP77kbF2UxgvRwzWWz+4Bh9/vJTyg1A= +github.com/prometheus/client_golang v1.5.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.9.1 h1:KOMtN28tlbam3/7ZKEYKHhKoJZYYj3gMH4uc62x7X7U= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= @@ -1251,6 +1278,7 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo= gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= diff --git a/metrics/middleware.go b/metrics/middleware.go new file mode 100644 index 0000000000..4d125bd745 --- /dev/null +++ b/metrics/middleware.go @@ -0,0 +1,71 @@ +package metrics + +import ( + "net/http" + "sync" + "time" + + "github.com/urfave/negroni" +) + +type timer interface { + Now() time.Time + Since(time.Time) time.Duration +} + +type realClock struct{} + +func (rc *realClock) Now() time.Time { + return time.Now() +} + +func (rc *realClock) Since(t time.Time) time.Duration { + return time.Since(t) +} + +// Middleware is a middleware handler that logs the request as it goes in and the response as it goes out. +type Middleware struct { + // Name is the name of the application as recorded in latency metrics + Name string + // Promtheus repository + Prometheus *PrometheusRepository + + clock timer + + // Silence metrics for specific URL paths + // it is protected by the mutex + mutex sync.RWMutex + silencePaths map[string]bool +} + +// NewMiddleware returns a new *Middleware, yay! +func NewMiddleware(prom *PrometheusRepository, name string) *Middleware { + return &Middleware{ + Name: name, + Prometheus: prom, + clock: &realClock{}, + silencePaths: map[string]bool{}, + } +} + +// ExcludePaths adds new URL paths to be ignored during logging. The URL u is parsed, hence the returned error +func (m *Middleware) ExcludePaths(paths ...string) *Middleware { + for _, path := range paths { + m.mutex.Lock() + m.silencePaths[path] = true + m.mutex.Unlock() + } + return m +} + +func (m *Middleware) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { + start := m.clock.Now() + next(rw, r) + latency := m.clock.Since(start) + res := rw.(negroni.ResponseWriter) + + if _, silent := m.silencePaths[r.URL.Path]; !silent { + m.Prometheus.RequestDurationObserve(m.Name, r.RequestURI, r.Method, res.Status())(float64(latency.Seconds())) + m.Prometheus.UpdateRequest(m.Name, r.RequestURI, r.Method, res.Status()) + } +} diff --git a/metrics/prometheus.go b/metrics/prometheus.go new file mode 100644 index 0000000000..126aa8e3e3 --- /dev/null +++ b/metrics/prometheus.go @@ -0,0 +1,93 @@ +// Package contains the collection of prometheus meters/counters +// and related update methods +package metrics + +import ( + "strconv" + + "github.com/prometheus/client_golang/prometheus" + log "github.com/sirupsen/logrus" +) + +var ( + // RequestTotal provides the total number of requests + RequestTotal = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "ory_oathkeeper_requests_total", + Help: "Total number of requests", + }, + []string{"service", "method", "request", "status_code"}, + ) + // HistogramRequestDuration provides the duration of requests + HistogramRequestDuration = prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Name: "ory_oathkeeper_request_duration_seconds", + Help: "Time spent serving requests.", + Buckets: prometheus.DefBuckets, + }, + []string{"service", "method", "request", "status_code"}, + ) +) + +// RequestDurationObserve tracks request durations +type RequestDurationObserve func(histogram *prometheus.HistogramVec, service, request, method string, statusCode int) func(float64) + +// UpdateRequest tracks total requests done +type UpdateRequest func(counter *prometheus.CounterVec, service, request, method string, statusCode int) + +// PrometheusRepository provides methods to manage prometheus metrics +type PrometheusRepository struct { + logger log.FieldLogger + requestDurationObserve RequestDurationObserve + updateRequest UpdateRequest + Registry *prometheus.Registry + metrics []prometheus.Collector +} + +// NewPrometheusRepository creates a new prometheus repository with the given settings +func NewPrometheusRepository(logger log.FieldLogger) *PrometheusRepository { + m := []prometheus.Collector{ + prometheus.NewGoCollector(), + prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{}), + RequestTotal, + HistogramRequestDuration, + } + + r := prometheus.NewRegistry() + + for _, metric := range m { + if err := r.Register(metric); err != nil { + logger.WithError(err).Error("Unable to register prometheus metric.") + } + } + + mr := &PrometheusRepository{ + logger: logger, + Registry: r, + metrics: m, + } + + return mr +} + +// RequestDurationObserve tracks request durations +func (r *PrometheusRepository) RequestDurationObserve(service, request, method string, statusCode int) func(float64) { + return func(v float64) { + HistogramRequestDuration.With(prometheus.Labels{ + "service": service, + "method": method, + "request": request, + "status_code": strconv.Itoa(statusCode), + }).Observe(v) + } +} + +// UpdateRequest tracks total requests done +func (r *PrometheusRepository) UpdateRequest(service, request, method string, statusCode int) { + RequestTotal.With(prometheus.Labels{ + "service": service, + "method": method, + "request": request, + "status_code": strconv.Itoa(statusCode), + }).Inc() +}