diff --git a/examples/main.go b/examples/main.go deleted file mode 100644 index 2c0441e..0000000 --- a/examples/main.go +++ /dev/null @@ -1,18 +0,0 @@ -package main - -import ( - "github.com/googollee/go-espresso" - "github.com/googollee/go-espresso/examples/restapi" -) - -func main() { - svr, _ := espresso.New() - rest := restapi.NewService( - restapi.User{Email: "person1@domain.com", Password: "123456"}, - restapi.User{Email: "person2@domain.com", Password: "somepass"}, - ) - - svr.WithPrefix("/api").HandleAll(rest) - - svr.ListenAndServe(":8000") -} diff --git a/docs/02-log-request-middleware-example_test.go b/examples/middleware-logging_test.go similarity index 100% rename from docs/02-log-request-middleware-example_test.go rename to examples/middleware-logging_test.go diff --git a/docs/02-an-overall-example_test.go b/examples/overall_test.go similarity index 100% rename from docs/02-an-overall-example_test.go rename to examples/overall_test.go diff --git a/examples/prometheus_test.go b/examples/prometheus_test.go new file mode 100644 index 0000000..588f059 --- /dev/null +++ b/examples/prometheus_test.go @@ -0,0 +1,96 @@ +package espresso_test + +import ( + "fmt" + "io" + "net/http" + "net/http/httptest" + + "github.com/googollee/go-espresso" + "github.com/googollee/go-espresso/monitoring/prometheus" +) + +var mycounter = prometheus.NewCounter(prometheus.CounterOpts{ + Name: "mycounter", +}) + +func AddCounter(ctx espresso.Context) error { + var num int + if err := ctx.Endpoint(http.MethodGet, "/inc/:num"). + BindPath("num", &num).End(); err != nil { + return err + } + + mycounter.Add(float64(num)) + + ctx.ResponseWriter().WriteHeader(http.StatusNoContent) + return nil +} + +func LaunchWithPrometheus() (addr string, cancel func()) { + server, _ := espresso.New(prometheus.New("/metrics")) + + server.HandleFunc(AddCounter) + + httpSvr := httptest.NewServer(server) + addr = httpSvr.URL + cancel = func() { + httpSvr.Close() + } + + return +} + +func ExampleMonitoringWithPrometheus() { + addr, cancel := LaunchWithPrometheus() + defer cancel() + + { + resp, err := http.Get(addr + "/metrics") + if err != nil { + panic(err) + } + + body, _ := io.ReadAll(resp.Body) + resp.Body.Close() + + fmt.Println(resp.StatusCode, resp.Header.Get("Content-Type"), string(body)) + } + + { + resp, err := http.Get(addr + "/inc/100") + if err != nil { + panic(err) + } + resp.Body.Close() + } + + { + resp, err := http.Get(addr + "/metrics") + if err != nil { + panic(err) + } + + body, _ := io.ReadAll(resp.Body) + resp.Body.Close() + + fmt.Println(resp.StatusCode, resp.Header.Get("Content-Type"), string(body)) + } + + // Output: + // 200 text/plain; version=0.0.4; charset=utf-8 # HELP mycounter + // # TYPE mycounter counter + // mycounter 0 + // # HELP promhttp_metric_handler_errors_total Total number of internal errors encountered by the promhttp metric handler. + // # TYPE promhttp_metric_handler_errors_total counter + // promhttp_metric_handler_errors_total{cause="encoding"} 0 + // promhttp_metric_handler_errors_total{cause="gathering"} 0 + + // 200 text/plain; version=0.0.4; charset=utf-8 # HELP mycounter + // # TYPE mycounter counter + // mycounter 100 + // # HELP promhttp_metric_handler_errors_total Total number of internal errors encountered by the promhttp metric handler. + // # TYPE promhttp_metric_handler_errors_total counter + // promhttp_metric_handler_errors_total{cause="encoding"} 0 + // promhttp_metric_handler_errors_total{cause="gathering"} 0 +} diff --git a/go.mod b/go.mod index 908210a..3751180 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,21 @@ module github.com/googollee/go-espresso go 1.20 require ( - github.com/google/go-cmp v0.5.8 + github.com/google/go-cmp v0.5.9 github.com/julienschmidt/httprouter v1.3.0 golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b ) -require github.com/timewasted/go-accept-headers v0.0.0-20130320203746-c78f304b1b09 // indirect +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/prometheus/client_golang v1.16.0 // indirect + github.com/prometheus/client_model v0.3.0 // indirect + github.com/prometheus/common v0.42.0 // indirect + github.com/prometheus/procfs v0.10.1 // indirect + github.com/timewasted/go-accept-headers v0.0.0-20130320203746-c78f304b1b09 // indirect + golang.org/x/sys v0.8.0 // indirect + google.golang.org/protobuf v1.30.0 // indirect +) diff --git a/go.sum b/go.sum index 88fa076..3e6e5e7 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,39 @@ +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/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= +github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= +github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= +github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= +github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= +github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= github.com/timewasted/go-accept-headers v0.0.0-20130320203746-c78f304b1b09 h1:QVxbx5l/0pzciWYOynixQMtUhPYC3YKD6EcUlOsgGqw= github.com/timewasted/go-accept-headers v0.0.0-20130320203746-c78f304b1b09/go.mod h1:Uy/Rnv5WKuOO+PuDhuYLEpUiiKIZtss3z519uk67aF0= golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc= golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b h1:r+vk0EmXNmekl0S0BascoeeoHk/L7wmaW2QF90K+kYI= golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= diff --git a/monitoring/prometheus/metric.go b/monitoring/prometheus/metric.go new file mode 100644 index 0000000..0bd036d --- /dev/null +++ b/monitoring/prometheus/metric.go @@ -0,0 +1,106 @@ +package prometheus + +import ( + "net/http" + + "github.com/googollee/go-espresso" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" +) + +var DefaultRegistry = prometheus.NewRegistry() + +type GaugeOpts = prometheus.GaugeOpts + +func NewGauge(opt GaugeOpts) prometheus.Gauge { + ret := prometheus.NewGauge(opt) + DefaultRegistry.MustRegister(ret) + return ret +} + +func NewGaugeFunc(opt GaugeOpts, fn func() float64) prometheus.GaugeFunc { + ret := prometheus.NewGaugeFunc(opt, fn) + DefaultRegistry.MustRegister(ret) + return ret +} + +func NewGaugeVec(opt GaugeOpts, labels []string) *prometheus.GaugeVec { + ret := prometheus.NewGaugeVec(opt, labels) + DefaultRegistry.MustRegister(ret) + return ret +} + +type CounterOpts = prometheus.CounterOpts + +func NewCounter(opt CounterOpts) prometheus.Counter { + ret := prometheus.NewCounter(opt) + DefaultRegistry.MustRegister(ret) + return ret +} + +func NewCounterFunc(opt CounterOpts, fn func() float64) prometheus.CounterFunc { + ret := prometheus.NewCounterFunc(opt, fn) + DefaultRegistry.MustRegister(ret) + return ret +} + +func NewCounterVec(opt CounterOpts, labels []string) *prometheus.CounterVec { + ret := prometheus.NewCounterVec(opt, labels) + DefaultRegistry.MustRegister(ret) + return ret +} + +type SummaryOpts = prometheus.SummaryOpts + +func NewSummary(opt SummaryOpts) prometheus.Summary { + ret := prometheus.NewSummary(opt) + DefaultRegistry.MustRegister(ret) + return ret +} + +func NewSummaryVec(opt SummaryOpts, labels []string) *prometheus.SummaryVec { + ret := prometheus.NewSummaryVec(opt, labels) + DefaultRegistry.MustRegister(ret) + return ret +} + +type HistogramOpts = prometheus.HistogramOpts + +func NewHistogram(opt HistogramOpts) prometheus.Histogram { + ret := prometheus.NewHistogram(opt) + DefaultRegistry.MustRegister(ret) + return ret +} + +func NewHistogramVec(opt HistogramOpts, labels []string) *prometheus.HistogramVec { + ret := prometheus.NewHistogramVec(opt, labels) + DefaultRegistry.MustRegister(ret) + return ret +} + +type UntypedOpts = prometheus.UntypedOpts + +func NewUntypedFunc(opt UntypedOpts, fn func() float64) prometheus.UntypedFunc { + ret := prometheus.NewUntypedFunc(opt, fn) + DefaultRegistry.MustRegister(ret) + return ret +} + +func New(path string) espresso.ServerOption { + handler := promhttp.HandlerFor(DefaultRegistry, promhttp.HandlerOpts{ + Registry: DefaultRegistry, + }) + + return func(s *espresso.Server) error { + s.HandleFunc(func(ctx espresso.Context) error { + if err := ctx.Endpoint(http.MethodGet, path).End(); err != nil { + return err + } + + handler.ServeHTTP(ctx.ResponseWriter(), ctx.Request()) + return nil + }) + + return nil + } +}