diff --git a/Gopkg.lock b/Gopkg.lock index da7956c414e..0ced00d1d0a 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -28,6 +28,12 @@ revision = "73945b6115bfbbcc57d89b7316e28109364124e1" version = "v7" +[[projects]] + branch = "master" + name = "github.com/beorn7/perks" + packages = ["quantile"] + revision = "3a771d992973f24aa725d07868b467d1ddfceafb" + [[projects]] name = "github.com/cenk/backoff" packages = ["."] @@ -222,6 +228,12 @@ revision = "be5ece7dd465ab0765a9682137865547526d1dfb" version = "v1.7.3" +[[projects]] + name = "github.com/matttproud/golang_protobuf_extensions" + packages = ["pbutil"] + revision = "3247c84500bff8d9fb6d579d800f20b3e091582c" + version = "v1.0.0" + [[projects]] branch = "master" name = "github.com/meatballhat/negroni-logrus" @@ -350,6 +362,42 @@ revision = "792786c7400a136282c1664665ae0a8db921c6c2" version = "v1.0.0" +[[projects]] + name = "github.com/prometheus/client_golang" + packages = [ + "prometheus", + "prometheus/promhttp" + ] + revision = "c5b7fccd204277076155f10851dad72b76a49317" + version = "v0.8.0" + +[[projects]] + branch = "master" + name = "github.com/prometheus/client_model" + packages = ["go"] + revision = "99fa1f4be8e564e8a6b613da7fa6f46c9edafc6c" + +[[projects]] + branch = "master" + name = "github.com/prometheus/common" + packages = [ + "expfmt", + "internal/bitbucket.org/ww/goautoneg", + "model" + ] + revision = "e5b036cc37a466a0af27d604d1ee500211d16d6a" + +[[projects]] + branch = "master" + name = "github.com/prometheus/procfs" + packages = [ + ".", + "internal/util", + "nfs", + "xfs" + ] + revision = "8b1c2da0d56deffdbb9e48d4414b4e674bd8083e" + [[projects]] name = "github.com/rs/cors" packages = ["."] @@ -559,6 +607,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "317cab4cb3c65a5029c1794f8a1a8d58fb8eb6f8a184d8da47482c94a6a73547" + inputs-digest = "e34494bb1a19a082a848bcfaa8fca2646d80557fc2615fe35d1c805418355622" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 8f6a135b4fb..72b50c04302 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -144,3 +144,7 @@ [[constraint]] branch = "v2" name = "gopkg.in/yaml.v2" + +[[constraint]] + name = "github.com/prometheus/client_golang" + version = "0.8.0" diff --git a/cmd/server/handler.go b/cmd/server/handler.go index cdf1b02c6f3..ae6abfc3439 100644 --- a/cmd/server/handler.go +++ b/cmd/server/handler.go @@ -105,12 +105,14 @@ func RunHost(c *config.Config) func(cmd *cobra.Command, args []string) { n := negroni.New() if ok, _ := cmd.Flags().GetBool("disable-telemetry"); !ok && os.Getenv("DISABLE_TELEMETRY") != "1" { - metrics := c.GetMetrics() - go metrics.RegisterSegment() - go metrics.CommitMemoryStatistics() - n.Use(metrics) + telemetryMetrics := c.GetTelemetryMetrics() + go telemetryMetrics.RegisterSegment() + go telemetryMetrics.CommitMemoryStatistics() + n.Use(telemetryMetrics) } + n.Use(c.GetPrometheusMetrics()) + n.Use(negronilogrus.NewMiddlewareFromLogger(logger, c.Issuer)) n.UseFunc(serverHandler.rejectInsecureRequests) n.UseHandler(router) diff --git a/cmd/server/handler_health_factory.go b/cmd/server/handler_health_factory.go index 9dc0c02164f..897e1690833 100644 --- a/cmd/server/handler_health_factory.go +++ b/cmd/server/handler_health_factory.go @@ -29,7 +29,7 @@ import ( func newHealthHandler(c *config.Config, router *httprouter.Router) *health.Handler { h := &health.Handler{ - Metrics: c.GetMetrics(), + Metrics: c.GetTelemetryMetrics(), H: herodot.NewJSONWriter(c.GetLogger()), W: c.Context().Warden, ResourcePrefix: c.AccessControlResourcePrefix, diff --git a/config/config.go b/config/config.go index 02fb65f9742..94907e986ff 100644 --- a/config/config.go +++ b/config/config.go @@ -37,7 +37,8 @@ import ( foauth2 "github.com/ory/fosite/handler/oauth2" "github.com/ory/fosite/token/hmac" "github.com/ory/hydra/health" - "github.com/ory/hydra/metrics" + "github.com/ory/hydra/metrics/telemetry" + "github.com/ory/hydra/metrics/prometheus" "github.com/ory/hydra/pkg" "github.com/ory/hydra/warden/group" "github.com/ory/ladon" @@ -84,15 +85,16 @@ type Config struct { SendOAuth2DebugMessagesToClients bool `mapstructure:"OAUTH2_SHARE_ERROR_DEBUG" yaml:"-"` ForceHTTP bool `yaml:"-"` - BuildVersion string `yaml:"-"` - BuildHash string `yaml:"-"` - BuildTime string `yaml:"-"` - logger *logrus.Logger `yaml:"-"` - metrics *metrics.MetricsManager `yaml:"-"` - cluster *url.URL `yaml:"-"` - oauth2Client *http.Client `yaml:"-"` - context *Context `yaml:"-"` - systemSecret []byte `yaml:"-"` + BuildVersion string `yaml:"-"` + BuildHash string `yaml:"-"` + BuildTime string `yaml:"-"` + logger *logrus.Logger `yaml:"-"` + telemetry *telemetry.MetricsManager `yaml:"-"` + prometheus *prometheus.MetricsManager `yaml:"-"` + cluster *url.URL `yaml:"-"` + oauth2Client *http.Client `yaml:"-"` + context *Context `yaml:"-"` + systemSecret []byte `yaml:"-"` } func (c *Config) GetClusterURLWithoutTailingSlash() string { @@ -154,12 +156,21 @@ func (c *Config) GetLogger() *logrus.Logger { return c.logger } -func (c *Config) GetMetrics() *metrics.MetricsManager { - if c.metrics == nil { - c.metrics = metrics.NewMetricsManager(c.Issuer, c.DatabaseURL, c.GetLogger(), c.BuildVersion, c.BuildHash, c.BuildTime) +func (c *Config) GetTelemetryMetrics() *telemetry.MetricsManager { + if c.telemetry == nil { + c.telemetry = telemetry.NewMetricsManager(c.Issuer, c.DatabaseURL, c.GetLogger(), c.BuildVersion, c.BuildHash, c.BuildTime) } - return c.metrics + return c.telemetry +} + +func (c *Config) GetPrometheusMetrics() *prometheus.MetricsManager { + if c.prometheus == nil { + c.GetLogger().Info("Setting up Prometheus metrics") + c.prometheus = prometheus.NewMetricsManager(c.BuildVersion, c.BuildHash, c.BuildTime) + } + + return c.prometheus } func (c *Config) DoesRequestSatisfyTermination(r *http.Request) error { diff --git a/health/handler.go b/health/handler.go index 6fca3ce80cf..c74c3f28187 100644 --- a/health/handler.go +++ b/health/handler.go @@ -26,15 +26,17 @@ import ( "github.com/julienschmidt/httprouter" "github.com/ory/herodot" "github.com/ory/hydra/firewall" - "github.com/ory/hydra/metrics" + "github.com/ory/hydra/metrics/telemetry" + "github.com/prometheus/client_golang/prometheus/promhttp" ) const ( - HealthStatusPath = "/health/status" + HealthStatusPath = "/health/status" + PrometheusStatusPath = "/health/prometheus" ) type Handler struct { - Metrics *metrics.MetricsManager + Metrics *telemetry.MetricsManager H *herodot.JSONWriter W firewall.Firewall ResourcePrefix string @@ -54,6 +56,7 @@ func (h *Handler) PrefixResource(resource string) string { func (h *Handler) SetRoutes(r *httprouter.Router) { r.GET(HealthStatusPath, h.Health) + r.Handler("GET", PrometheusStatusPath, promhttp.Handler()) } // swagger:route GET /health/status health getInstanceStatus diff --git a/metrics/prometheus/metrics.go b/metrics/prometheus/metrics.go new file mode 100644 index 00000000000..0361f9cd7d3 --- /dev/null +++ b/metrics/prometheus/metrics.go @@ -0,0 +1,37 @@ +package prometheus + +import "github.com/prometheus/client_golang/prometheus" + +type Metrics struct { + Counter *prometheus.CounterVec + ResponseTime *prometheus.HistogramVec +} + +func NewMetrics(version, hash, buildTime string) *Metrics { + labels := map[string]string{ + "version": version, + "hash": hash, + "buildTime": buildTime, + } + pm := &Metrics{ + Counter: prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "sts_requests_total", + Help: "Secure token service requests served per endpoint", + ConstLabels: labels, + }, + []string{"endpoint"}, + ), + ResponseTime: prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Name: "sts_response_time_seconds", + Help: "Secure token service response time served per endpoint", + ConstLabels: labels, + }, + []string{"endpoint"}, + ), + } + prometheus.Register(pm.Counter) + prometheus.Register(pm.ResponseTime) + return pm +} diff --git a/metrics/prometheus/middleware.go b/metrics/prometheus/middleware.go new file mode 100644 index 00000000000..6c8c5b15db9 --- /dev/null +++ b/metrics/prometheus/middleware.go @@ -0,0 +1,23 @@ +package prometheus + +import ( + "net/http" + "time" +) + +type MetricsManager struct { + prometheusMetrics *Metrics +} + +func NewMetricsManager(version, hash, buildTime string) *MetricsManager { + return &MetricsManager{ + prometheusMetrics: NewMetrics(version, hash, buildTime), + } +} + +func (pmm *MetricsManager) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { + start := time.Now() + next(rw, r) + pmm.prometheusMetrics.Counter.WithLabelValues(r.URL.Path).Inc() + pmm.prometheusMetrics.ResponseTime.WithLabelValues(r.URL.Path).Observe(time.Since(start).Seconds()) +} diff --git a/metrics/metrics.go b/metrics/telemetry/metrics.go similarity index 99% rename from metrics/metrics.go rename to metrics/telemetry/metrics.go index f4152cb1947..aa118416fbf 100644 --- a/metrics/metrics.go +++ b/metrics/telemetry/metrics.go @@ -18,7 +18,7 @@ * @license Apache-2.0 */ -package metrics +package telemetry import ( "runtime" diff --git a/metrics/middleware.go b/metrics/telemetry/middleware.go similarity index 99% rename from metrics/middleware.go rename to metrics/telemetry/middleware.go index 471264dc9bf..c1eaf91664c 100644 --- a/metrics/middleware.go +++ b/metrics/telemetry/middleware.go @@ -18,7 +18,7 @@ * @license Apache-2.0 */ -package metrics +package telemetry import ( "crypto/sha256" @@ -100,6 +100,7 @@ func NewMetricsManager(issuerURL string, databaseURL string, l logrus.FieldLogge salt: uuid.New(), BuildTime: buildTime, BuildVersion: version, BuildHash: hash, } + return mm } diff --git a/metrics/middleware_test.go b/metrics/telemetry/middleware_test.go similarity index 99% rename from metrics/middleware_test.go rename to metrics/telemetry/middleware_test.go index 760949e054a..138c077f559 100644 --- a/metrics/middleware_test.go +++ b/metrics/telemetry/middleware_test.go @@ -18,7 +18,7 @@ * @license Apache-2.0 */ -package metrics +package telemetry import ( "net/url"