mirror of
https://github.com/nais/wonderwall.git
synced 2026-05-20 07:12:48 +00:00
add http request metrics
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"github.com/nais/wonderwall/pkg/metrics"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
@@ -99,6 +100,13 @@ func run() error {
|
||||
|
||||
r := router.New(handler, prefixes)
|
||||
|
||||
go func() {
|
||||
err := metrics.Handle(cfg.MetricsBindAddress)
|
||||
if err != nil {
|
||||
log.Errorf("fatal: metrics server error: %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}()
|
||||
return http.ListenAndServe(cfg.BindAddress, r)
|
||||
}
|
||||
|
||||
|
||||
1
go.mod
1
go.mod
@@ -9,6 +9,7 @@ require (
|
||||
github.com/lestrrat-go/jwx v1.2.5
|
||||
github.com/nais/liberator v0.0.0-20210809103005-edb0141d646d
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||
github.com/prometheus/client_golang v1.0.0
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/spf13/viper v1.8.1
|
||||
|
||||
6
go.sum
6
go.sum
@@ -65,6 +65,7 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj
|
||||
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
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/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
|
||||
@@ -358,6 +359,7 @@ github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
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/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
@@ -419,12 +421,16 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/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 v1.0.0 h1:vrDKnkGzuGvhNAL56c7DBz29ZL+KxnoR0x7enabFceM=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
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.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.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/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/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
|
||||
@@ -12,14 +12,15 @@ import (
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
BindAddress string `json:"bind-address"`
|
||||
UpstreamHost string `json:"upstream-host"`
|
||||
EncryptionKey string `json:"encryption-key"`
|
||||
IDPorten IDPorten `json:"idporten"`
|
||||
LogFormat string `json:"log-format"`
|
||||
LogLevel string `json:"log-level"`
|
||||
Redis string `json:"redis"`
|
||||
Ingresses []string `json:"ingresses"`
|
||||
BindAddress string `json:"bind-address"`
|
||||
MetricsBindAddress string `json:"metrics-bind-address"`
|
||||
UpstreamHost string `json:"upstream-host"`
|
||||
EncryptionKey string `json:"encryption-key"`
|
||||
IDPorten IDPorten `json:"idporten"`
|
||||
LogFormat string `json:"log-format"`
|
||||
LogLevel string `json:"log-level"`
|
||||
Redis string `json:"redis"`
|
||||
Ingresses []string `json:"ingresses"`
|
||||
}
|
||||
|
||||
type IDPorten struct {
|
||||
@@ -37,6 +38,7 @@ type IDPorten struct {
|
||||
|
||||
const (
|
||||
BindAddress = "bind-address"
|
||||
MetricsBindAddress = "metrics-bind-address"
|
||||
UpstreamHost = "upstream-host"
|
||||
LogFormat = "log-format"
|
||||
LogLevel = "log-level"
|
||||
@@ -67,7 +69,8 @@ func Initialize() *Config {
|
||||
|
||||
flag.String(LogFormat, "text", "Log format, either 'json' or 'text'.")
|
||||
flag.String(LogLevel, "debug", "Logging verbosity level.")
|
||||
flag.String(BindAddress, "127.0.0.1:8090", "Listen address.")
|
||||
flag.String(BindAddress, "127.0.0.1:8090", "Listen address for public connections.")
|
||||
flag.String(MetricsBindAddress, "127.0.0.1:8091", "Listen address for metrics only.")
|
||||
flag.String(UpstreamHost, "127.0.0.1:8080", "Address of upstream host.")
|
||||
flag.String(EncryptionKey, "", "Base64 encoded 256-bit cookie encryption key; must be identical in instances that share session store.")
|
||||
flag.String(Redis, "", "Address of Redis. An empty value will use in-memory session storage.")
|
||||
|
||||
11
pkg/metrics/metrics.go
Normal file
11
pkg/metrics/metrics.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func Handle(address string) error {
|
||||
handler := promhttp.Handler()
|
||||
return http.ListenAndServe(address, handler)
|
||||
}
|
||||
86
pkg/middleware/prometheus.go
Normal file
86
pkg/middleware/prometheus.go
Normal file
@@ -0,0 +1,86 @@
|
||||
// This code was originally written by Rene Zbinden and modified by Vladimir Konovalov.
|
||||
// Copied from https://github.com/766b/chi-prometheus and further adapted.
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
chi_middleware "github.com/go-chi/chi/middleware"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultBuckets = []float64{.001, .01, .05, .1, .5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5}
|
||||
)
|
||||
|
||||
const (
|
||||
reqsName = "requests_total"
|
||||
latencyName = "request_duration_ms"
|
||||
)
|
||||
|
||||
type middleware func(http.Handler) http.Handler
|
||||
|
||||
// Middleware is a handler that exposes prometheus metrics for the number of requests,
|
||||
// the latency and the response size, partitioned by status code, method and HTTP path.
|
||||
type Middleware struct {
|
||||
reqs *prometheus.CounterVec
|
||||
latency *prometheus.HistogramVec
|
||||
}
|
||||
|
||||
// NewMiddleware returns a new prometheus Middleware handler.
|
||||
func PrometheusMiddleware(name string, buckets ...float64) *Middleware {
|
||||
var m Middleware
|
||||
m.reqs = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: reqsName,
|
||||
Help: "How many HTTP requests processed, partitioned by status code, method and HTTP path.",
|
||||
ConstLabels: prometheus.Labels{"service": name},
|
||||
},
|
||||
[]string{"code", "method", "path"},
|
||||
)
|
||||
|
||||
if len(buckets) == 0 {
|
||||
buckets = defaultBuckets
|
||||
}
|
||||
m.latency = prometheus.NewHistogramVec(prometheus.HistogramOpts{
|
||||
Name: latencyName,
|
||||
Help: "How long it took to process the request, partitioned by status code, method and HTTP path.",
|
||||
ConstLabels: prometheus.Labels{"service": name},
|
||||
Buckets: buckets,
|
||||
},
|
||||
[]string{"code", "method", "path"},
|
||||
)
|
||||
|
||||
prometheus.Register(m.reqs)
|
||||
prometheus.Register(m.latency)
|
||||
|
||||
return &m
|
||||
}
|
||||
|
||||
func (m *Middleware) Initialize(path, method string, code int) {
|
||||
m.reqs.WithLabelValues(
|
||||
strconv.Itoa(code),
|
||||
method,
|
||||
path,
|
||||
)
|
||||
}
|
||||
|
||||
func (m *Middleware) Handler() middleware {
|
||||
return m.handler
|
||||
}
|
||||
|
||||
func (m Middleware) handler(next http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
start := time.Now()
|
||||
ww := chi_middleware.NewWrapResponseWriter(w, r.ProtoMajor)
|
||||
next.ServeHTTP(ww, r)
|
||||
statusCode := strconv.Itoa(ww.Status())
|
||||
duration := time.Since(start)
|
||||
m.reqs.WithLabelValues(statusCode, r.Method, r.URL.Path).Inc()
|
||||
m.latency.WithLabelValues(statusCode, r.Method, r.URL.Path).Observe(duration.Seconds())
|
||||
}
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"github.com/nais/wonderwall/pkg/middleware"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -20,7 +21,7 @@ import (
|
||||
"github.com/nais/wonderwall/pkg/token"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/chi/middleware"
|
||||
chi_middleware "github.com/go-chi/chi/middleware"
|
||||
"github.com/lestrrat-go/jwx/jwk"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/oauth2"
|
||||
@@ -321,7 +322,10 @@ func (h *Handler) Default(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(upstreamResponse.StatusCode)
|
||||
|
||||
// Forward server's reply downstream
|
||||
io.Copy(w, upstreamResponse.Body)
|
||||
_, err = io.Copy(w, upstreamResponse.Body)
|
||||
if err != nil {
|
||||
log.Errorf("proxy data from upstream to client: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Logout triggers self-initiated for the current user
|
||||
@@ -404,9 +408,13 @@ func (h *Handler) FrontChannelLogout(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func New(handler *Handler, prefixes []string) chi.Router {
|
||||
r := chi.NewRouter()
|
||||
mm := middleware.PrometheusMiddleware("wonderwall")
|
||||
|
||||
r.Use(mm.Handler())
|
||||
|
||||
for _, prefix := range prefixes {
|
||||
r.Route(prefix+"/oauth2", func(r chi.Router) {
|
||||
r.With(middleware.NoCache)
|
||||
r.Use(chi_middleware.NoCache)
|
||||
r.Get("/login", handler.Login)
|
||||
r.Get("/callback", handler.Callback)
|
||||
r.Get("/logout", handler.Logout)
|
||||
|
||||
Reference in New Issue
Block a user