diff --git a/README.md b/README.md index c3b11a6..f5acd41 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,8 @@ Toolbox and building blocks for new Go projects, to get started quickly and righ Pick and choose whatever is useful to you! Don't feel the need to use everything, or even to follow this structure. +For advanced Golang knowledge, tips & tricks, see also https://goperf.dev + --- ## Getting started diff --git a/cmd/cli/main.go b/cmd/cli/main.go index f99934e..ae6d71c 100644 --- a/cmd/cli/main.go +++ b/cmd/cli/main.go @@ -53,5 +53,6 @@ func runCli(cCtx *cli.Context) error { log.Info("info message") log.With("key", "value").Warn("warn message") log.Error("error message", "err", errors.ErrUnsupported) + return nil } diff --git a/cmd/httpserver/main.go b/cmd/httpserver/main.go index a5366cc..a9bbd1f 100644 --- a/cmd/httpserver/main.go +++ b/cmd/httpserver/main.go @@ -73,18 +73,19 @@ func main() { enablePprof := cCtx.Bool("pprof") drainDuration := time.Duration(cCtx.Int64("drain-seconds")) * time.Second + uid := "" + if logUID { + uid = uuid.Must(uuid.NewRandom()).String() + } + log := common.SetupLogger(&common.LoggingOpts{ Debug: logDebug, JSON: logJSON, Service: logService, Version: common.Version, + UID: uid, }) - if logUID { - id := uuid.Must(uuid.NewRandom()) - log = log.With("uid", id.String()) - } - cfg := &httpserver.HTTPServerConfig{ ListenAddr: listenAddr, MetricsAddr: metricsAddr, diff --git a/common/logging.go b/common/logging.go index ff2949c..117fdbb 100644 --- a/common/logging.go +++ b/common/logging.go @@ -3,35 +3,47 @@ package common import ( "log/slog" - "os" + + "github.com/go-chi/httplog/v2" ) type LoggingOpts struct { - Debug bool - JSON bool - Service string - Version string + Service string + JSON bool + Debug bool + Concise bool + RequestHeaders bool + Version string + UID string + Tags map[string]string } -func SetupLogger(opts *LoggingOpts) (log *slog.Logger) { +func SetupLogger(opts *LoggingOpts) (log *httplog.Logger) { logLevel := slog.LevelInfo if opts.Debug { logLevel = slog.LevelDebug } - if opts.JSON { - log = slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: logLevel})) - } else { - log = slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: logLevel})) - } - - if opts.Service != "" { - log = log.With("service", opts.Service) + // If version is provided, add it to the tags. + if opts.Version != "" || opts.UID != "" { + if opts.Tags == nil { + opts.Tags = make(map[string]string) + } + if opts.Version != "" { + opts.Tags["version"] = opts.Version + } + if opts.UID != "" { + opts.Tags["uid"] = opts.UID + } } - if opts.Version != "" { - log = log.With("version", opts.Version) - } + logger := httplog.NewLogger(opts.Service, httplog.Options{ + JSON: opts.JSON, + LogLevel: logLevel, + Concise: opts.Concise, + RequestHeaders: opts.RequestHeaders, + Tags: opts.Tags, + }) - return log + return logger } diff --git a/common/utils.go b/common/utils.go index 788b3f0..2d3c2a4 100644 --- a/common/utils.go +++ b/common/utils.go @@ -1,6 +1,8 @@ package common -import "os" +import ( + "os" +) func GetEnv(key, defaultValue string) string { if value, ok := os.LookupEnv(key); ok { diff --git a/go.mod b/go.mod index b1134bb..4bd1ef7 100644 --- a/go.mod +++ b/go.mod @@ -3,46 +3,28 @@ module github.com/flashbots/go-template go 1.24.0 require ( - github.com/flashbots/go-utils v0.6.1-0.20240610084140-4461ab748667 - github.com/go-chi/chi/v5 v5.0.12 + github.com/VictoriaMetrics/metrics v1.37.0 + github.com/go-chi/chi/v5 v5.2.1 + github.com/go-chi/httplog/v2 v2.1.1 github.com/google/uuid v1.6.0 github.com/jmoiron/sqlx v1.4.0 github.com/lib/pq v1.10.9 - github.com/prometheus/client_golang v1.19.1 github.com/rubenv/sql-migrate v1.7.0 github.com/stretchr/testify v1.9.0 github.com/urfave/cli/v2 v2.27.2 - go.opentelemetry.io/contrib/instrumentation/runtime v0.46.1 - go.opentelemetry.io/otel/exporters/prometheus v0.44.0 - go.opentelemetry.io/otel/metric v1.21.0 - go.opentelemetry.io/otel/sdk/metric v1.21.0 go.uber.org/atomic v1.11.0 ) require ( - github.com/beorn7/perks v1.0.1 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/ethereum/go-ethereum v1.13.14 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect - github.com/go-logr/logr v1.3.0 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/holiman/uint256 v1.2.4 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_model v0.5.0 // indirect - github.com/prometheus/common v0.48.0 // indirect - github.com/prometheus/procfs v0.12.0 // indirect + github.com/rogpeppe/go-internal v1.10.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/sirupsen/logrus v1.9.3 // indirect + github.com/valyala/fastrand v1.1.0 // indirect + github.com/valyala/histogram v1.2.0 // indirect github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect - go.opentelemetry.io/otel v1.21.0 // indirect - go.opentelemetry.io/otel/sdk v1.21.0 // indirect - go.opentelemetry.io/otel/trace v1.21.0 // indirect - go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.25.0 // indirect - golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect - golang.org/x/sys v0.17.0 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/sys v0.33.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index bafb283..aa95914 100644 --- a/go.sum +++ b/go.sum @@ -1,37 +1,21 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= -github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -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.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/VictoriaMetrics/metrics v1.37.0 h1:u5Yr+HFofQyn7kgmmkufgkX0nEA6G1oEyK2eaKsVaUM= +github.com/VictoriaMetrics/metrics v1.37.0/go.mod h1:r7hveu6xMdUACXvB8TYdAj8WEsKzWB0EkpJN+RDtOf8= github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -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/ethereum/go-ethereum v1.13.14 h1:EwiY3FZP94derMCIam1iW4HFVrSgIcpsu0HwTQtm6CQ= -github.com/ethereum/go-ethereum v1.13.14/go.mod h1:TN8ZiHrdJwSe8Cb6x+p0hs5CxhJZPbqB7hHkaUXcmIU= -github.com/flashbots/go-utils v0.6.1-0.20240610084140-4461ab748667 h1:Zpdah3TPNH96wp4IZG8eH81WU0ISS39+b1EEuVrwGBA= -github.com/flashbots/go-utils v0.6.1-0.20240610084140-4461ab748667/go.mod h1:6ZfgrAI+ApKSBF4QghFO06VfRJGGAOOyG4DO0siN2ow= -github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s= -github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8= +github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= +github.com/go-chi/httplog/v2 v2.1.1 h1:ojojiu4PIaoeJ/qAO4GWUxJqvYUTobeo7zmuHQJAxRk= +github.com/go-chi/httplog/v2 v2.1.1/go.mod h1:/XXdxicJsp4BA5fapgIC3VuTD+z0Z/VzukoB3VDc1YE= github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= -github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU= -github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -46,62 +30,28 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= -github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= -github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= -github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= -github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= -github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= -github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= -github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= -github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/rubenv/sql-migrate v1.7.0 h1:HtQq1xyTN2ISmQDggnh0c9U3JlP8apWh8YO2jzlXpTI= github.com/rubenv/sql-migrate v1.7.0/go.mod h1:S4wtDEG1CKn+0ShpTtzWhFpHHI5PvCUtiGI+C+Z2THE= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI= github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM= +github.com/valyala/fastrand v1.1.0 h1:f+5HkLW4rsgzdNoleUOB69hyT9IlD2ZQh9GyDMfb5G8= +github.com/valyala/fastrand v1.1.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ= +github.com/valyala/histogram v1.2.0 h1:wyYGAZZt3CpwUiIb9AU/Zbllg1llXyrtApRS815OLoQ= +github.com/valyala/histogram v1.2.0/go.mod h1:Hb4kBwb4UxsaNbbbh+RRz8ZR6pdodR57tzWUS3BUzXY= github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw= github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk= -go.opentelemetry.io/contrib/instrumentation/runtime v0.46.1 h1:m9ReioVPIffxjJlGNRd0d5poy+9oTro3D+YbiEzUDOc= -go.opentelemetry.io/contrib/instrumentation/runtime v0.46.1/go.mod h1:CANkrsXNzqOKXfOomu2zhOmc1/J5UZK9SGjrat6ZCG0= -go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc= -go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= -go.opentelemetry.io/otel/exporters/prometheus v0.44.0 h1:08qeJgaPC0YEBu2PQMbqU3rogTlyzpjhCI2b58Yn00w= -go.opentelemetry.io/otel/exporters/prometheus v0.44.0/go.mod h1:ERL2uIeBtg4TxZdojHUwzZfIFlUIjZtxubT5p4h1Gjg= -go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4= -go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= -go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8= -go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= -go.opentelemetry.io/otel/sdk/metric v1.21.0 h1:smhI5oD714d6jHE6Tie36fPx4WDFIg+Y6RfAY4ICcR0= -go.opentelemetry.io/otel/sdk/metric v1.21.0/go.mod h1:FJ8RAsoPGv/wYMgBdUJXOm+6pzFY3YdljnXtv1SBE8Q= -go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc= -go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= -go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= -go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= -go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c= -go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk= -golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= -golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/httpserver/handler.go b/httpserver/handler.go index 05a1f9a..53817e6 100644 --- a/httpserver/handler.go +++ b/httpserver/handler.go @@ -3,23 +3,9 @@ package httpserver import ( "net/http" "time" - - "github.com/flashbots/go-template/metrics" ) func (srv *Server) handleAPI(w http.ResponseWriter, r *http.Request) { - m := srv.metricsSrv.Float64Histogram( - "request_duration_api", - "API request handling duration", - metrics.UomMicroseconds, - metrics.BucketsRequestDuration..., - ) - defer func(start time.Time) { - m.Record(r.Context(), float64(time.Since(start).Microseconds())) - }(time.Now()) - - // do work - w.WriteHeader(http.StatusOK) } diff --git a/httpserver/handler_test.go b/httpserver/handler_test.go index c27d5fb..ed23461 100644 --- a/httpserver/handler_test.go +++ b/httpserver/handler_test.go @@ -2,17 +2,17 @@ package httpserver import ( "io" - "log/slog" "net/http" "net/http/httptest" "testing" "time" "github.com/flashbots/go-template/common" + "github.com/go-chi/httplog/v2" "github.com/stretchr/testify/require" ) -func getTestLogger() *slog.Logger { +func getTestLogger() *httplog.Logger { return common.SetupLogger(&common.LoggingOpts{ Debug: true, JSON: false, diff --git a/httpserver/server.go b/httpserver/server.go index 084d1a5..57b57a5 100644 --- a/httpserver/server.go +++ b/httpserver/server.go @@ -3,15 +3,14 @@ package httpserver import ( "context" "errors" - "log/slog" "net/http" "time" - "github.com/flashbots/go-template/common" + victoriaMetrics "github.com/VictoriaMetrics/metrics" "github.com/flashbots/go-template/metrics" - "github.com/flashbots/go-utils/httplogger" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" + "github.com/go-chi/httplog/v2" "go.uber.org/atomic" ) @@ -19,7 +18,7 @@ type HTTPServerConfig struct { ListenAddr string MetricsAddr string EnablePprof bool - Log *slog.Logger + Log *httplog.Logger DrainDuration time.Duration GracefulShutdownDuration time.Duration @@ -30,25 +29,27 @@ type HTTPServerConfig struct { type Server struct { cfg *HTTPServerConfig isReady atomic.Bool - log *slog.Logger + log *httplog.Logger srv *http.Server - metricsSrv *metrics.MetricsServer + metricsSrv *http.Server } func New(cfg *HTTPServerConfig) (srv *Server, err error) { - metricsSrv, err := metrics.New(common.PackageName, cfg.MetricsAddr) - if err != nil { - return nil, err + srv = &Server{ + cfg: cfg, + log: cfg.Log, + srv: nil, } - srv = &Server{ - cfg: cfg, - log: cfg.Log, - srv: nil, - metricsSrv: metricsSrv, + if cfg.MetricsAddr != "" { + srv.metricsSrv = &http.Server{ + Addr: cfg.MetricsAddr, + Handler: srv.getMetricsRouter(), + ReadTimeout: cfg.ReadTimeout, + WriteTimeout: cfg.WriteTimeout, + } } - srv.isReady.Swap(true) srv.srv = &http.Server{ Addr: cfg.ListenAddr, @@ -57,16 +58,23 @@ func New(cfg *HTTPServerConfig) (srv *Server, err error) { WriteTimeout: cfg.WriteTimeout, } + srv.isReady.Swap(true) + return srv, nil } func (srv *Server) getRouter() http.Handler { mux := chi.NewRouter() - mux.With(srv.httpLogger).Get("/api", srv.handleAPI) // Never serve at `/` (root) path - mux.With(srv.httpLogger).Get("/livez", srv.handleLivenessCheck) - mux.With(srv.httpLogger).Get("/readyz", srv.handleReadinessCheck) - mux.With(srv.httpLogger).Get("/drain", srv.handleDrain) - mux.With(srv.httpLogger).Get("/undrain", srv.handleUndrain) + + mux.Use(httplog.RequestLogger(srv.log)) + mux.Use(middleware.Recoverer) + mux.Use(metrics.Middleware) + + mux.Get("/api", srv.handleAPI) // Never serve at `/` (root) path + mux.Get("/livez", srv.handleLivenessCheck) + mux.Get("/readyz", srv.handleReadinessCheck) + mux.Get("/drain", srv.handleDrain) + mux.Get("/undrain", srv.handleUndrain) if srv.cfg.EnablePprof { srv.log.Info("pprof API enabled") @@ -75,8 +83,12 @@ func (srv *Server) getRouter() http.Handler { return mux } -func (srv *Server) httpLogger(next http.Handler) http.Handler { - return httplogger.LoggingMiddlewareSlog(srv.log, next) +func (srv *Server) getMetricsRouter() http.Handler { + mux := chi.NewRouter() + mux.Get("/metrics", func(w http.ResponseWriter, r *http.Request) { + victoriaMetrics.WritePrometheus(w, true) + }) + return mux } func (srv *Server) RunInBackground() { diff --git a/metrics/README.md b/metrics/README.md index 27fadeb..8ad150e 100644 --- a/metrics/README.md +++ b/metrics/README.md @@ -1,34 +1,7 @@ # metrics -This example metrics module uses the OpenTelemetry package. - -A solid alternative is [VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics), see an example -implementation here: https://github.com/flashbots/mev-share-node/blob/main/metrics/metrics.go - ---- - -Introduction: -- https://opentelemetry.io/docs/languages/go/instrumentation/ - -Clients: -- https://github.com/open-telemetry/opentelemetry-go -- https://github.com/prometheus/client_golang -- https://github.com/VictoriaMetrics/metrics - -Onboarding: -- https://www.timescale.com/blog/four-types-prometheus-metrics-to-collect/ -- https://blog.pvincent.io/2017/12/prometheus-blog-series-part-1-metrics-and-labels/ -- https://pierrevincent.github.io/2017/12/prometheus-blog-series-part-2-metric-types/ -- https://pierrevincent.github.io/2017/12/prometheus-blog-series-part-3-exposing-and-collecting-metrics/ -- https://pierrevincent.github.io/2017/12/prometheus-blog-series-part-4-instrumenting-code-in-go-and-java/ -- https://developers.soundcloud.com/blog/prometheus-monitoring-at-soundcloud - -Best practices: -- https://prometheus.io/docs/practices/naming/ +This template uses [VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics) See also: -- https://pkg.go.dev/github.com/prometheus/client_golang@v1.17.0/prometheus#NewRegistry -- https://pkg.go.dev/go.opentelemetry.io/otel/exporters/prometheus@v0.44.0#New -- https://pkg.go.dev/go.opentelemetry.io/otel/sdk/metric@v1.21.0#NewMeterProvider -- https://pkg.go.dev/go.opentelemetry.io/otel/sdk/metric@v1.21.0#MeterProvider.Meter -- https://pkg.go.dev/go.opentelemetry.io/contrib/instrumentation/runtime@v0.46.1#Start \ No newline at end of file +- https://prometheus.io/docs/concepts/metric_types/ +- https://prometheus.io/docs/practices/histograms/ \ No newline at end of file diff --git a/metrics/metrics.go b/metrics/metrics.go index ef7a62c..ea12785 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -1,135 +1,14 @@ package metrics import ( - "context" "fmt" - "net/http" - "sync" - "time" - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promhttp" - "go.opentelemetry.io/contrib/instrumentation/runtime" - promotel "go.opentelemetry.io/otel/exporters/prometheus" - "go.opentelemetry.io/otel/metric" - sdk "go.opentelemetry.io/otel/sdk/metric" + "github.com/VictoriaMetrics/metrics" ) -const ( - UomMicroseconds = "us" -) - -var BucketsRequestDuration = []float64{ - 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0, 60.0, -} - -type MetricsServer struct { - exporter *promotel.Exporter - meter metric.Meter - provider *sdk.MeterProvider - registry *prometheus.Registry - - server *http.Server - - float64Histogram map[string]metric.Float64Histogram - mxFloat64Histogram sync.RWMutex -} - -func New(name, addr string) (metricsServer *MetricsServer, err error) { - registry := prometheus.NewRegistry() - - exporter, err := promotel.New(promotel.WithRegisterer(registry)) - if err != nil { - return nil, fmt.Errorf("failed to create prometheus exporter: %w", err) - } - - provider := sdk.NewMeterProvider(sdk.WithReader(exporter)) - meter := provider.Meter(name) - - mux := http.NewServeMux() - mux.Handle("/metrics", promhttp.HandlerFor(registry, promhttp.HandlerOpts{})) - - server := &http.Server{ - Addr: addr, - Handler: mux, - - ReadTimeout: 5 * time.Second, - WriteTimeout: 5 * time.Second, - } - - metricsServer = &MetricsServer{ - exporter: exporter, - meter: meter, - provider: provider, - registry: registry, - - server: server, - - float64Histogram: make(map[string]metric.Float64Histogram), - } - - if err := runtime.Start( - runtime.WithMeterProvider(metricsServer.provider), - runtime.WithMinimumReadMemStatsInterval(time.Minute), // querying go-runtime is expensive - ); err != nil { - return nil, fmt.Errorf("failed to start go-runtime's metrics exporter: %w", err) - } - return metricsServer, nil -} - -func (ms *MetricsServer) ListenAndServe() error { - return ms.server.ListenAndServe() -} - -// Shutdown gracefully shuts down the server without interrupting any -// active connections. -func (ms *MetricsServer) Shutdown(ctx context.Context) error { - return ms.server.Shutdown(ctx) -} - -// Float64Histogram returns a float64 histogram with given name and -// parameters. -// -// Once created it's cached and reused further on. All subsequent calls -// to this method that use the same name will retrieve already created -// histogram from the cache. -// -// It is thread-safe. -// -// See also: https://pkg.go.dev/go.opentelemetry.io/otel/metric@v1.21.0#Meter.Float64Histogram -// -//nolint:ireturn,nolintlint -func (ms *MetricsServer) Float64Histogram( - name string, - description string, - uom string, - bucketBounds ...float64, -) metric.Float64Histogram { - ms.mxFloat64Histogram.RLock() - if h, exists := ms.float64Histogram[name]; exists { - ms.mxFloat64Histogram.RUnlock() - return h - } - ms.mxFloat64Histogram.RUnlock() - - ms.mxFloat64Histogram.Lock() - defer ms.mxFloat64Histogram.Unlock() - - // avoid race condition between ro-unlock and rw-lock - if h, exists := ms.float64Histogram[name]; exists { - return h - } - - h, err := ms.meter.Float64Histogram( - name, - metric.WithDescription(description), - metric.WithExplicitBucketBoundaries(bucketBounds...), - metric.WithUnit(uom), - ) - if err != nil { - panic(err) - } +const requestDurationLabel = `http_server_request_duration_milliseconds{route="%s"}` - ms.float64Histogram[name] = h - return h +func recordRequestDuration(route string, duration int64) { + l := fmt.Sprintf(requestDurationLabel, route) + metrics.GetOrCreateSummary(l).Update(float64(duration)) } diff --git a/metrics/middleware.go b/metrics/middleware.go new file mode 100644 index 0000000..d44a069 --- /dev/null +++ b/metrics/middleware.go @@ -0,0 +1,21 @@ +package metrics + +import ( + "net/http" + "time" + + "github.com/go-chi/chi/v5" +) + +func Middleware(next http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + startAt := time.Now() + defer func() { + routePattern := chi.RouteContext(r.Context()).RoutePattern() + recordRequestDuration(routePattern, time.Since(startAt).Milliseconds()) + }() + + next.ServeHTTP(w, r) + } + return http.HandlerFunc(fn) +}