From ce4fb61cfe9b70b98397589751603647d8871af9 Mon Sep 17 00:00:00 2001 From: louis Date: Thu, 3 Oct 2024 13:03:13 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=B8=20Add=20Prometheus=20Metrics?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 42 ++++++++++++++++++++++++++++++++++++++++++ adapter.go | 26 ++++++++++++++++++++++++++ config.go | 9 +++++++++ config_test.go | 8 ++++++++ go.mod | 12 +++++++++++- go.sum | 32 +++++++++++++++++++++++++++++++- main.go | 2 ++ prometheus.go | 35 +++++++++++++++++++++++++++++++++++ 8 files changed, 164 insertions(+), 2 deletions(-) create mode 100644 prometheus.go diff --git a/README.md b/README.md index 83364fe..d546e49 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ The adapter is configured via environment variables: - `DOMAIN_LISTEN_ADDR`: The address to listen on for incoming requests. Default: `10002`. - `MAILBOX_LISTEN_ADDR`: The address to listen on for incoming requests. Default: `10003`. - `SENDERS_LISTEN_ADDR`: The address to listen on for incoming requests. Default: `10004`. +- `METRICS_LISTEN_ADDR`: The address to listen on for metrics. Default: `10005`. In Postfix, you can configure the adapter as a transport like this: @@ -23,3 +24,44 @@ virtual_mailbox_domains = tcp:localhost:10002 virtual_mailbox_maps = tcp:localhost:10003 smtpd_sender_login_maps = tcp:localhost:10004 ``` + +## Metrics + +The adapter exposes metrics in the Prometheus format. You can access them on the `/metrics` endpoint. + +```text +# HELP userli_postfix_adapter_request_duration_seconds Duration of requests to userli +# TYPE userli_postfix_adapter_request_duration_seconds histogram +userli_postfix_adapter_request_duration_seconds_bucket{handler="alias",status="success",le="0.1"} 1 +userli_postfix_adapter_request_duration_seconds_bucket{handler="alias",status="success",le="0.15000000000000002"} 1 +userli_postfix_adapter_request_duration_seconds_bucket{handler="alias",status="success",le="0.22500000000000003"} 1 +userli_postfix_adapter_request_duration_seconds_bucket{handler="alias",status="success",le="0.3375"} 1 +userli_postfix_adapter_request_duration_seconds_bucket{handler="alias",status="success",le="0.5062500000000001"} 1 +userli_postfix_adapter_request_duration_seconds_bucket{handler="alias",status="success",le="+Inf"} 1 +userli_postfix_adapter_request_duration_seconds_sum{handler="alias",status="success"} 0.074540625 +userli_postfix_adapter_request_duration_seconds_count{handler="alias",status="success"} 1 +userli_postfix_adapter_request_duration_seconds_bucket{handler="domain",status="success",le="0.1"} 3 +userli_postfix_adapter_request_duration_seconds_bucket{handler="domain",status="success",le="0.15000000000000002"} 3 +userli_postfix_adapter_request_duration_seconds_bucket{handler="domain",status="success",le="0.22500000000000003"} 3 +userli_postfix_adapter_request_duration_seconds_bucket{handler="domain",status="success",le="0.3375"} 3 +userli_postfix_adapter_request_duration_seconds_bucket{handler="domain",status="success",le="0.5062500000000001"} 3 +userli_postfix_adapter_request_duration_seconds_bucket{handler="domain",status="success",le="+Inf"} 3 +userli_postfix_adapter_request_duration_seconds_sum{handler="domain",status="success"} 0.246158083 +userli_postfix_adapter_request_duration_seconds_count{handler="domain",status="success"} 3 +userli_postfix_adapter_request_duration_seconds_bucket{handler="mailbox",status="success",le="0.1"} 1 +userli_postfix_adapter_request_duration_seconds_bucket{handler="mailbox",status="success",le="0.15000000000000002"} 1 +userli_postfix_adapter_request_duration_seconds_bucket{handler="mailbox",status="success",le="0.22500000000000003"} 1 +userli_postfix_adapter_request_duration_seconds_bucket{handler="mailbox",status="success",le="0.3375"} 1 +userli_postfix_adapter_request_duration_seconds_bucket{handler="mailbox",status="success",le="0.5062500000000001"} 1 +userli_postfix_adapter_request_duration_seconds_bucket{handler="mailbox",status="success",le="+Inf"} 1 +userli_postfix_adapter_request_duration_seconds_sum{handler="mailbox",status="success"} 0.097836333 +userli_postfix_adapter_request_duration_seconds_count{handler="mailbox",status="success"} 1 +userli_postfix_adapter_request_duration_seconds_bucket{handler="senders",status="success",le="0.1"} 1 +userli_postfix_adapter_request_duration_seconds_bucket{handler="senders",status="success",le="0.15000000000000002"} 1 +userli_postfix_adapter_request_duration_seconds_bucket{handler="senders",status="success",le="0.22500000000000003"} 1 +userli_postfix_adapter_request_duration_seconds_bucket{handler="senders",status="success",le="0.3375"} 1 +userli_postfix_adapter_request_duration_seconds_bucket{handler="senders",status="success",le="0.5062500000000001"} 1 +userli_postfix_adapter_request_duration_seconds_bucket{handler="senders",status="success",le="+Inf"} 1 +userli_postfix_adapter_request_duration_seconds_sum{handler="senders",status="success"} 0.097870375 +userli_postfix_adapter_request_duration_seconds_count{handler="senders",status="success"} 1 +``` diff --git a/adapter.go b/adapter.go index 727a50d..c156b1d 100644 --- a/adapter.go +++ b/adapter.go @@ -6,7 +6,9 @@ import ( "fmt" "net" "strings" + "time" + "github.com/prometheus/client_golang/prometheus" log "github.com/sirupsen/logrus" ) @@ -27,10 +29,13 @@ func NewPostfixAdapter(client UserliService) *PostfixAdapter { func (p *PostfixAdapter) AliasHandler(conn net.Conn) { defer conn.Close() + now := time.Now() + payload, err := p.payload(conn) if err != nil { log.WithError(err).Error("Error getting payload") _, _ = conn.Write([]byte("400 Error getting payload\n")) + requestDurations.With(prometheus.Labels{"handler": "alias", "status": "error"}).Observe(time.Since(now).Seconds()) return } email := strings.TrimSuffix(payload, "\n") @@ -38,15 +43,18 @@ func (p *PostfixAdapter) AliasHandler(conn net.Conn) { if err != nil { log.WithError(err).WithField("email", email).Error("Error fetching aliases") _, _ = conn.Write([]byte("400 Error fetching aliases\n")) + requestDurations.With(prometheus.Labels{"handler": "alias", "status": "error"}).Observe(time.Since(now).Seconds()) return } if len(aliases) == 0 { _, _ = conn.Write([]byte("500 NO%20RESULT\n")) + requestDurations.With(prometheus.Labels{"handler": "alias", "status": "success"}).Observe(time.Since(now).Seconds()) return } _, _ = conn.Write([]byte(fmt.Sprintf("200 %s \n", strings.Join(aliases, ",")))) + requestDurations.With(prometheus.Labels{"handler": "alias", "status": "success"}).Observe(time.Since(now).Seconds()) } // DomainHandler handles the get command for domains. @@ -55,10 +63,13 @@ func (p *PostfixAdapter) AliasHandler(conn net.Conn) { func (p *PostfixAdapter) DomainHandler(conn net.Conn) { defer conn.Close() + now := time.Now() + payload, err := p.payload(conn) if err != nil { log.WithError(err).Error("Error getting payload") _, _ = conn.Write([]byte("400 Error getting payload\n")) + requestDurations.With(prometheus.Labels{"handler": "domain", "status": "error"}).Observe(time.Since(now).Seconds()) return } @@ -67,15 +78,18 @@ func (p *PostfixAdapter) DomainHandler(conn net.Conn) { if err != nil { log.WithError(err).WithField("domain", domain).Error("Error fetching domain") _, _ = conn.Write([]byte("400 Error fetching domain\n")) + requestDurations.With(prometheus.Labels{"handler": "domain", "status": "error"}).Observe(time.Since(now).Seconds()) return } if !exists { _, _ = conn.Write([]byte("500 NO%20RESULT\n")) + requestDurations.With(prometheus.Labels{"handler": "domain", "status": "success"}).Observe(time.Since(now).Seconds()) return } _, _ = conn.Write([]byte("200 1\n")) + requestDurations.With(prometheus.Labels{"handler": "domain", "status": "success"}).Observe(time.Since(now).Seconds()) } // MailboxHandler handles the get command for mailboxes. @@ -84,10 +98,13 @@ func (p *PostfixAdapter) DomainHandler(conn net.Conn) { func (p *PostfixAdapter) MailboxHandler(conn net.Conn) { defer conn.Close() + now := time.Now() + payload, err := p.payload(conn) if err != nil { log.WithError(err).Error("Error getting payload") _, _ = conn.Write([]byte("400 Error getting payload\n")) + requestDurations.With(prometheus.Labels{"handler": "mailbox", "status": "error"}).Observe(time.Since(now).Seconds()) return } @@ -96,15 +113,18 @@ func (p *PostfixAdapter) MailboxHandler(conn net.Conn) { if err != nil { log.WithError(err).WithField("email", email).Error("Error fetching mailbox") _, _ = conn.Write([]byte("400 Error fetching mailbox\n")) + requestDurations.With(prometheus.Labels{"handler": "mailbox", "status": "error"}).Observe(time.Since(now).Seconds()) return } if !exists { _, _ = conn.Write([]byte("500 NO%20RESULT\n")) + requestDurations.With(prometheus.Labels{"handler": "mailbox", "status": "success"}).Observe(time.Since(now).Seconds()) return } _, _ = conn.Write([]byte("200 1\n")) + requestDurations.With(prometheus.Labels{"handler": "mailbox", "status": "success"}).Observe(time.Since(now).Seconds()) } // SendersHandler handles the get command for senders. @@ -113,10 +133,13 @@ func (p *PostfixAdapter) MailboxHandler(conn net.Conn) { func (p *PostfixAdapter) SendersHandler(conn net.Conn) { defer conn.Close() + now := time.Now() + payload, err := p.payload(conn) if err != nil { log.WithError(err).Error("Error getting payload") _, _ = conn.Write([]byte("400 Error getting payload\n")) + requestDurations.With(prometheus.Labels{"handler": "senders", "status": "error"}).Observe(time.Since(now).Seconds()) return } @@ -125,15 +148,18 @@ func (p *PostfixAdapter) SendersHandler(conn net.Conn) { if err != nil { log.WithError(err).WithField("email", email).Error("Error fetching senders") _, _ = conn.Write([]byte("400 Error fetching senders\n")) + requestDurations.With(prometheus.Labels{"handler": "senders", "status": "error"}).Observe(time.Since(now).Seconds()) return } if len(senders) == 0 { _, _ = conn.Write([]byte("500 NO%20RESULT\n")) + requestDurations.With(prometheus.Labels{"handler": "senders", "status": "success"}).Observe(time.Since(now).Seconds()) return } _, _ = conn.Write([]byte(fmt.Sprintf("200 %s \n", strings.Join(senders, ",")))) + requestDurations.With(prometheus.Labels{"handler": "senders", "status": "success"}).Observe(time.Since(now).Seconds()) } // payload reads the data from the connection. It checks for valid diff --git a/config.go b/config.go index bdb7306..a9f386a 100644 --- a/config.go +++ b/config.go @@ -25,6 +25,9 @@ type Config struct { // SendersListenAddr is the address to listen for senders requests. SendersListenAddr string + + // MetricsListenAddr is the address to listen for metrics requests. + MetricsListenAddr string } // NewConfig creates a new Config with default values. @@ -81,6 +84,11 @@ func NewConfig() *Config { sendersListenAddr = ":10004" } + metricsListenAddr := os.Getenv("METRICS_LISTEN_ADDR") + if metricsListenAddr == "" { + metricsListenAddr = ":10005" + } + return &Config{ UserliBaseURL: userliBaseURL, UserliToken: userliToken, @@ -88,5 +96,6 @@ func NewConfig() *Config { DomainListenAddr: domainListenAddr, MailboxListenAddr: mailboxListenAddr, SendersListenAddr: sendersListenAddr, + MetricsListenAddr: metricsListenAddr, } } diff --git a/config_test.go b/config_test.go index 6972b32..d6d902f 100644 --- a/config_test.go +++ b/config_test.go @@ -1,6 +1,7 @@ package main import ( + "io" "os" "testing" @@ -13,6 +14,10 @@ type ConfigTestSuite struct { suite.Suite } +func (s *ConfigTestSuite) SetupTest() { + log.SetOutput(io.Discard) +} + func (s *ConfigTestSuite) TestNewConfig() { s.Run("fail when userli token not set", func() { defer func() { log.StandardLogger().ExitFunc = nil }() @@ -35,6 +40,7 @@ func (s *ConfigTestSuite) TestNewConfig() { s.Equal(":10002", config.DomainListenAddr) s.Equal(":10003", config.MailboxListenAddr) s.Equal(":10004", config.SendersListenAddr) + s.Equal(":10005", config.MetricsListenAddr) }) s.Run("custom config", func() { @@ -44,6 +50,7 @@ func (s *ConfigTestSuite) TestNewConfig() { os.Setenv("DOMAIN_LISTEN_ADDR", ":20002") os.Setenv("MAILBOX_LISTEN_ADDR", ":20003") os.Setenv("SENDERS_LISTEN_ADDR", ":20004") + os.Setenv("METRICS_LISTEN_ADDR", ":20005") config := NewConfig() @@ -53,6 +60,7 @@ func (s *ConfigTestSuite) TestNewConfig() { s.Equal(":20002", config.DomainListenAddr) s.Equal(":20003", config.MailboxListenAddr) s.Equal(":20004", config.SendersListenAddr) + s.Equal(":20005", config.MetricsListenAddr) }) } diff --git a/go.mod b/go.mod index 306d6c7..fcabe3f 100644 --- a/go.mod +++ b/go.mod @@ -4,15 +4,25 @@ go 1.23.1 require ( github.com/h2non/gock v1.2.0 + github.com/prometheus/client_golang v1.20.4 + github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.9.0 ) require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect + github.com/klauspost/compress v1.17.9 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/sirupsen/logrus v1.9.3 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect github.com/stretchr/objx v0.5.2 // indirect golang.org/x/sys v0.25.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index d4d799d..f9008b1 100644 --- a/go.sum +++ b/go.sum @@ -1,14 +1,41 @@ +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.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/h2non/gock v1.2.0 h1:K6ol8rfrRkUOefooBC8elXoaNGYkpp7y2qcxGG6BzUE= github.com/h2non/gock v1.2.0/go.mod h1:tNhoxHYW2W42cYkYb1WqzdbYIieALC99kpYr7rH/BQk= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI= +github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= +github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +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/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= @@ -20,8 +47,11 @@ github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8 golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= 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/main.go b/main.go index 292fa4a..5f43722 100644 --- a/main.go +++ b/main.go @@ -17,6 +17,8 @@ func main() { ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer stop() + go StartMetricsServer(ctx, config.MetricsListenAddr) + var wg sync.WaitGroup wg.Add(4) diff --git a/prometheus.go b/prometheus.go new file mode 100644 index 0000000..7668706 --- /dev/null +++ b/prometheus.go @@ -0,0 +1,35 @@ +package main + +import ( + "context" + "net/http" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/collectors" + "github.com/prometheus/client_golang/prometheus/promhttp" + + log "github.com/sirupsen/logrus" +) + +var ( + requestDurations = prometheus.NewHistogramVec(prometheus.HistogramOpts{ + Name: "userli_postfix_adapter_request_duration_seconds", + Help: "Duration of requests to userli", + Buckets: prometheus.ExponentialBuckets(0.1, 1.5, 5.0), + }, []string{"handler", "status"}) +) + +// StartMetricsServer starts a new HTTP server for prometheus metrics. +func StartMetricsServer(ctx context.Context, listenAddr string) { + registry := prometheus.NewRegistry() + + registry.MustRegister( + collectors.NewGoCollector(), + requestDurations, + ) + + http.Handle("/metrics", promhttp.HandlerFor(registry, promhttp.HandlerOpts{})) + + log.Info("Metrics server started on ", listenAddr) + log.Fatal(http.ListenAndServe(listenAddr, nil)) +}