Skip to content

Commit

Permalink
Add prometheus metrics modules
Browse files Browse the repository at this point in the history
  • Loading branch information
heppu committed Jan 4, 2024
1 parent 26a0835 commit 3cd6896
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 26 deletions.
71 changes: 46 additions & 25 deletions metrics/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"net/http"
"strconv"
"sync"
"time"
"unicode/utf8"

Expand All @@ -19,37 +20,26 @@ type Prometheus struct {
ReqCntURLLabelMappingFn func(c *gin.Context) string
SkipMetricsURLFn func(c *gin.Context) bool
reg *prometheus.Registry
init sync.Once
cs []prometheus.Collector
}

func initRegistry(cs ...prometheus.Collector) *Prometheus {
reg := prometheus.NewPedanticRegistry()
reg.MustRegister(
collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}),
collectors.NewGoCollector(),
)
p := &Prometheus{
reqCnt: reqCnt,
reqDur: reqDur,
ReqCntURLLabelMappingFn: func(c *gin.Context) string {
return c.Request.URL.Path
},
SkipMetricsURLFn: func(c *gin.Context) bool {
return false
},
reg: reg,
}
reg.MustRegister(p.reqCnt)
reg.MustRegister(p.reqDur)
for _, c := range cs {
reg.MustRegister(c)
}
return p
// New creates Prometheus instance with given collectors.
// Before usage p.Init() must be called.
func New(cs ...prometheus.Collector) *Prometheus {
return &Prometheus{cs: cs}
}

// NewPrometheus creates registers collectors and starts metrics server.
// Deprecated: This function will panic instead of returning error. Use metrics module instead.
func NewPrometheus(port int, cs ...prometheus.Collector) *Prometheus {
p := New(cs...)
if err := p.Init(); err != nil {
panic(err)
}

pMux := http.NewServeMux()
p := initRegistry(cs...)
pMux.Handle("/metrics", promhttp.HandlerFor(p.reg, promhttp.HandlerOpts{}))
pMux.Handle("/metrics", NewPrometheusHandler(p))
go func() {
listenAddr := fmt.Sprintf(":%d", port)

Expand All @@ -66,6 +56,32 @@ func NewPrometheus(port int, cs ...prometheus.Collector) *Prometheus {
return p
}

func (p *Prometheus) Init() (err error) {
p.init.Do(func() {
p.reqCnt = reqCnt
p.reqDur = reqDur
p.ReqCntURLLabelMappingFn = func(c *gin.Context) string { return c.Request.URL.Path }
p.SkipMetricsURLFn = func(c *gin.Context) bool { return false }
p.reg = prometheus.NewPedanticRegistry()

collectors := []prometheus.Collector{
collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}),
collectors.NewGoCollector(),
p.reqCnt,
p.reqDur,
}

collectors = append(collectors, p.cs...)
for _, c := range collectors {
if err = p.reg.Register(c); err != nil {
return
}
}
})

return err
}

func (p *Prometheus) GetRegistry() *prometheus.Registry {
return p.reg
}
Expand Down Expand Up @@ -93,3 +109,8 @@ func (p *Prometheus) HandlerFunc() gin.HandlerFunc {
}
}
}

// NewPrometheusHandler creates http.Handler with Prometheus registry.
func NewPrometheusHandler(p *Prometheus) http.Handler {
return promhttp.HandlerFor(p.reg, promhttp.HandlerOpts{})
}
3 changes: 2 additions & 1 deletion service/module/httpserver/pprof/pprof_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@ func TestServer(t *testing.T) {

require.NotEmpty(t, srv.Name())
require.NoError(t, srv.Init())
url := srv.URL() + "/debug/pprof/heap"
wg := &multierror.Group{}
wg.Go(srv.Run)

assertOK(t, srv.URL()+"/debug/pprof/heap")
assertOK(t, url)

assert.NoError(t, srv.Stop())
err := wg.Wait().ErrorOrNil()
Expand Down
21 changes: 21 additions & 0 deletions service/module/httpserver/prom/prom.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Package prom provides prometheus metrics handler options for httpserver module.
package prom

import (
"fmt"

"github.com/elisasre/go-common/metrics"
"github.com/elisasre/go-common/service/module/httpserver"
)

// WithMetrics replaces servers handler with http.Handler which is instrumented with /metrics endpoint.
// This option is meant be used with stand alone metrics server, not embedded inside application server.
// For serving metrics endpoint inside your application web server check lower level functionalities from metrics.
func WithMetrics(p *metrics.Prometheus) httpserver.Opt {
return func(s *httpserver.Server) error {
if err := p.Init(); err != nil {
return fmt.Errorf("failed to initialize prometheus handler: %w", err)
}
return httpserver.WithHandler(metrics.NewPrometheusHandler(p))(s)
}
}
64 changes: 64 additions & 0 deletions service/module/httpserver/prom/prom_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package prom_test

import (
"io"
"net/http"
"testing"
"time"

"github.com/elisasre/go-common/metrics"
"github.com/elisasre/go-common/service/module/httpserver"
"github.com/elisasre/go-common/service/module/httpserver/prom"
"github.com/hashicorp/go-multierror"
"github.com/prometheus/client_golang/prometheus/collectors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestInitError(t *testing.T) {
c := collectors.NewGoCollector()
srv := httpserver.New(
httpserver.WithServer(&http.Server{ReadHeaderTimeout: time.Second}),
httpserver.WithAddr("127.0.0.1:0"),
prom.WithMetrics(metrics.New(c)),
)

require.Error(t, srv.Init())
}

func TestServer(t *testing.T) {
srv := httpserver.New(
httpserver.WithServer(&http.Server{ReadHeaderTimeout: time.Second}),
httpserver.WithAddr("127.0.0.1:0"),
prom.WithMetrics(metrics.New()),
)

require.NotEmpty(t, srv.Name())
require.NoError(t, srv.Init())
url := srv.URL() + "/metrics"
wg := &multierror.Group{}
wg.Go(srv.Run)

assertOK(t, url)

assert.NoError(t, srv.Stop())
err := wg.Wait().ErrorOrNil()
require.NoError(t, err)
}

func assertOK(t testing.TB, url string) {
resp, err := http.Get(url) //nolint:gosec
if !assert.NoError(t, err) {
return
}

data, err := io.ReadAll(resp.Body)
if !assert.NoError(t, err) {
return
}

assert.Equal(t, "200 OK", resp.Status)
assert.Equal(t, http.StatusOK, resp.StatusCode)
assert.NoError(t, resp.Body.Close())
assert.NotEmpty(t, data)
}

0 comments on commit 3cd6896

Please sign in to comment.