Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prometheus metrics #1381

Closed
wants to merge 18 commits into from
Closed
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 22 additions & 5 deletions consent/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ package consent

import (
"encoding/json"
"log"
"net/http"
"net/url"
"time"
Expand Down Expand Up @@ -248,6 +249,7 @@ func (h *Handler) GetLoginRequest(w http.ResponseWriter, r *http.Request, ps htt

request, err := h.r.ConsentManager().GetLoginRequest(r.Context(), challenge)
if err != nil {
log.Println(err)
kminehart marked this conversation as resolved.
Show resolved Hide resolved
h.r.Writer().WriteError(w, r, err)
return
}
Expand Down Expand Up @@ -298,13 +300,21 @@ func (h *Handler) AcceptLoginRequest(w http.ResponseWriter, r *http.Request, ps
return
}

var p HandledLoginRequest
var (
p HandledLoginRequest
err error
)
d := json.NewDecoder(r.Body)
d.DisallowUnknownFields()
if err := d.Decode(&p); err != nil {
if err = d.Decode(&p); err != nil {
h.r.Writer().WriteErrorCode(w, r, http.StatusBadRequest, errors.WithStack(err))
return
}

defer func(p *HandledLoginRequest, err error) {
kminehart marked this conversation as resolved.
Show resolved Hide resolved
recordAcceptLoginRequest(h.r.PrometheusManager(), p, err)
}(&p, err)

if p.Subject == "" {
h.r.Writer().WriteErrorCode(w, r, http.StatusBadRequest, errors.New("Subject from payload can not be empty"))
}
Expand Down Expand Up @@ -505,14 +515,21 @@ func (h *Handler) AcceptConsentRequest(w http.ResponseWriter, r *http.Request, p
return
}

var p HandledConsentRequest
var (
p HandledConsentRequest
err error
)
d := json.NewDecoder(r.Body)
d.DisallowUnknownFields()
if err := d.Decode(&p); err != nil {
if err = d.Decode(&p); err != nil {
h.r.Writer().WriteErrorCode(w, r, http.StatusBadRequest, errors.WithStack(err))
return
}

defer func(p *HandledConsentRequest, err error) {
recordAcceptConsentRequest(h.r.PrometheusManager(), p, err)
}(&p, err)

cr, err := h.r.ConsentManager().GetConsentRequest(r.Context(), challenge)
if err != nil {
h.r.Writer().WriteError(w, r, errors.WithStack(err))
Expand All @@ -535,7 +552,6 @@ func (h *Handler) AcceptConsentRequest(w http.ResponseWriter, r *http.Request, p
h.r.Writer().WriteError(w, r, err)
return
}

h.r.Writer().Write(w, r, &RequestHandlerResponse{
RedirectTo: urlx.SetQuery(ru, url.Values{"consent_verifier": {hr.Verifier}}).String(),
})
Expand Down Expand Up @@ -587,6 +603,7 @@ func (h *Handler) RejectConsentRequest(w http.ResponseWriter, r *http.Request, p
h.r.Writer().WriteErrorCode(w, r, http.StatusBadRequest, errors.WithStack(err))
return
}
// defer recordAcceptConsentRequest(h.r.PrometheusManager(), &p)

hr, err := h.r.ConsentManager().GetConsentRequest(r.Context(), challenge)
if err != nil {
Expand Down
69 changes: 69 additions & 0 deletions consent/metrics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package consent

import (
"strconv"

"github.com/ory/hydra/metrics/prometheus"

prom "github.com/prometheus/client_golang/prometheus"
)

func recordAcceptConsentRequest(
m *prometheus.MetricsManager,
cr *HandledConsentRequest,
err error,
) {
m.PrometheusMetrics.ConsentRequests.With(prom.Labels{
"error": strconv.FormatBool(err != nil),
}).Inc()

for _, v := range cr.GrantedScope {
m.PrometheusMetrics.ConsentRequestScopes.With(prom.Labels{
"scope": v,
"type": "granted",
}).Inc()
}

for _, v := range cr.GrantedAudience {
m.PrometheusMetrics.ConsentRequestAudiences.With(prom.Labels{
"audience": v,
"type": "granted",
}).Inc()
}

if cr.ConsentRequest != nil {
kminehart marked this conversation as resolved.
Show resolved Hide resolved
for _, v := range cr.ConsentRequest.RequestedScope {
m.PrometheusMetrics.ConsentRequestScopes.With(prom.Labels{
"scope": v,
"type": "requested",
}).Inc()
}
for _, v := range cr.ConsentRequest.RequestedAudience {
m.PrometheusMetrics.ConsentRequestAudiences.With(prom.Labels{
"audience": v,
"type": "requested",
}).Inc()
}
}
}

func recordAcceptLoginRequest(
m *prometheus.MetricsManager,
lr *HandledLoginRequest,
err error,
) {
m.PrometheusMetrics.LoginRequests.With(prom.Labels{
"error": strconv.FormatBool(err != nil),
}).Inc()

m.PrometheusMetrics.LoginRequestSubjects.With(prom.Labels{
"subject": lr.Subject,
}).Inc()
}

// func recordRejectConsentRequest(
// m *prometheus.MetricManager,
// cr *handledConsentRequest,
// ) {
//
// }
2 changes: 2 additions & 0 deletions consent/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/ory/hydra/client"
"github.com/ory/hydra/driver/configuration"
"github.com/ory/hydra/jwk"
"github.com/ory/hydra/metrics/prometheus"
"github.com/ory/hydra/x"
)

Expand All @@ -17,6 +18,7 @@ type InternalRegistry interface {

OAuth2Storage() x.FositeStorer
OpenIDJWTStrategy() jwk.JWTStrategy
PrometheusManager() *prometheus.MetricsManager
OpenIDConnectRequestValidator() *openid.OpenIDConnectRequestValidator
ScopeStrategy() fosite.ScopeStrategy
}
Expand Down
45 changes: 45 additions & 0 deletions metrics/prometheus/manager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package prometheus

import (
"log"
"net/http"

// "github.com/gorilla/mux"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/urfave/negroni"
)

type MetricsManager struct {
PrometheusMetrics *Metrics
}

func NewMetricsManager(version, hash, buildTime string) *MetricsManager {
return &MetricsManager{
PrometheusMetrics: NewMetrics(version, hash, buildTime),
}
}

// Main middleware method to collect metrics for Prometheus.
func (pmm *MetricsManager) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
nextHandler := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
nrw := negroni.NewResponseWriter(rw)
log.Println(r.RequestURI)
next(nrw, r)
})

sizeHandler := promhttp.InstrumentHandlerResponseSize(
pmm.PrometheusMetrics.HTTPResponseSizeBytes,
nextHandler,
)
durationHandler := promhttp.InstrumentHandlerDuration(
pmm.PrometheusMetrics.HTTPRequestDuration,
sizeHandler,
)

countHandler := promhttp.InstrumentHandlerCounter(
pmm.PrometheusMetrics.HTTPRequestCount,
durationHandler,
)

countHandler(rw, r)
}
152 changes: 115 additions & 37 deletions metrics/prometheus/metrics.go
Original file line number Diff line number Diff line change
@@ -1,42 +1,120 @@
package prometheus

// Metrics prototypes
// Example:
// Counter *prometheus.CounterVec
// ResponseTime *prometheus.HistogramVec
type Metrics struct{}

// Method for creation new custom Prometheus metrics
// Example:
// pm := &Metrics{
// Counter: prometheus.NewCounterVec(
// prometheus.CounterOpts{
// Name: "servicename_requests_total",
// Help: "Description",
// ConstLabels: map[string]string{
// "version": version,
// "hash": hash,
// "buildTime": buildTime,
// },
// },
// []string{"endpoint"},
// ),
// ResponseTime: prometheus.NewHistogramVec(
// prometheus.HistogramOpts{
// Name: "servicename_response_time_seconds",
// Help: "Description",
// ConstLabels: map[string]string{
// "version": version,
// "hash": hash,
// "buildTime": buildTime,
// },
// },
// []string{"endpoint"},
// ),
// }
// prometheus.Register(pm.Counter)
// prometheus.Register(pm.ResponseTime)
import (
"github.com/prometheus/client_golang/prometheus"
)

// Metrics contains the metrics that will be recorded.
// Rather than create metrics ad-hoc, we prefer having a central list of metrics,
// as per the Prometheus "best practices" in their documentation.
// See: https://prometheus.io/docs/practices/instrumentation/#avoid-missing-metrics
//
// The labels associated with metrics should be exclusively descriptive.
// For example, one might think that this is an applicable demonstration of "hydra_consent_requests_accepted":
// hydra_consent_requests_accepted{scope="scope:1"} 1
// hydra_consent_requests_accepted{scope="scope:2"} 1
// when a consent request requests []string{"scope:1", "scope:2"}.
// This is not good; if we were to query Prometheus for `sum(hydra_consent_requests_accepted)`, we would get 2, when in reality,
// only 1 consent request was accepted. Instead, separate data like "scopes" into a more descriptive time series:
// hydra_consent_requests_accepted{scopes="2"} 1
// hydra_consent_requests_accepted_scopes{scope="scope:1"} 1
// hydra_consent_requests_accepted_scopes{scope="scope:2"} 1
//
// Some of these values will be populated by HTTP middleware, while others
// will be populated elsewhere throughout the project
type Metrics struct {
HTTPRequestCount *prometheus.CounterVec
HTTPRequestDuration *prometheus.HistogramVec
HTTPResponseSizeBytes *prometheus.HistogramVec

ConsentRequests *prometheus.CounterVec
ConsentRequestScopes *prometheus.CounterVec
ConsentRequestAudiences *prometheus.CounterVec
ConsentRequestsRejected *prometheus.CounterVec

LoginRequests *prometheus.CounterVec
LoginRequestSubjects *prometheus.CounterVec

AccessTokensIssued *prometheus.CounterVec
AccessTokensRevoked *prometheus.CounterVec
RefreshTokensIssued *prometheus.CounterVec
RefreshTokensRevoked *prometheus.CounterVec
IDTokensIssued *prometheus.CounterVec

Grants *prometheus.GaugeVec
Clients *prometheus.GaugeVec
Keys *prometheus.GaugeVec
}

// NewMetrics registers our metrics and their labels to the prometheus Registry.
func NewMetrics(version, hash, date string) *Metrics {
pm := &Metrics{}
pm := &Metrics{
HTTPRequestCount: prometheus.NewCounterVec(prometheus.CounterOpts{
Name: "http_request_count",
Help: "",
}, []string{"code", "method"}),
HTTPRequestDuration: prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Help: "A histogram of latencies for requests",
Buckets: []float64{.05, .25, .5, 1, 2.5, 5, 10},
kminehart marked this conversation as resolved.
Show resolved Hide resolved
},
[]string{"method"},
),
HTTPResponseSizeBytes: prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_response_size_bytes",
Help: "A histogram of response sizes for requests",
Buckets: []float64{200, 400, 800, 1600},
kminehart marked this conversation as resolved.
Show resolved Hide resolved
},
[]string{},
),
ConsentRequests: prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: "hydra",
Subsystem: "consent",
Name: "requests",
Help: "incremented when a request is sent to consent.AcceptConsentRequest",
kminehart marked this conversation as resolved.
Show resolved Hide resolved
}, []string{"error"}),
ConsentRequestScopes: prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: "hydra",
Subsystem: "consent",
Name: "request_scopes",
Help: "tracks the number of consent requests submitted per scope",
}, []string{"scope", "type"}),
ConsentRequestAudiences: prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: "hydra",
Subsystem: "consent",
Name: "request_audiences",
Help: "tracks the number of consent requests submitted per audience",
}, []string{"audience", "type"}),
ConsentRequestsRejected: prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: "hydra",
Subsystem: "consent",
Name: "requests_rejected",
Help: "incremented when consent.RejectConsentRequest is successful",
}, []string{}),
LoginRequests: prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: "hydra",
Subsystem: "login",
Name: "requests",
Help: "incremented when a request is sent to consent.AcceptLoginRequest",
}, []string{"error"}),
LoginRequestSubjects: prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: "hydra",
Subsystem: "login",
Name: "request_subjects",
Help: "incremented when consent.RejectConsentRequest is successful",
}, []string{"subject"}),
}

prometheus.MustRegister(
pm.HTTPRequestCount,
pm.HTTPRequestDuration,
pm.HTTPResponseSizeBytes,
pm.ConsentRequests,
pm.ConsentRequestScopes,
pm.ConsentRequestAudiences,
pm.ConsentRequestsRejected,
)
return pm
}
27 changes: 0 additions & 27 deletions metrics/prometheus/middleware.go

This file was deleted.