From a86754de713094ac73acb774e2a1711f2a4bbd74 Mon Sep 17 00:00:00 2001 From: Pasha Date: Wed, 11 Dec 2024 15:54:32 +0300 Subject: [PATCH] added meat and bones for worker and http prometheus http server --- .env | 4 +- cmd/main.go | 29 ++++++++++ go.mod | 22 +++++-- go.sum | 42 ++++++++++++-- internal/app/app.go | 70 +++++++++++++++++++++++ internal/controller/metrics/prometheus.go | 24 ++++++++ internal/controller/metrics/route.go | 14 +++++ internal/initialize/config.go | 6 +- internal/server/http/http.go | 65 +++++++++++++++++++++ internal/worker/research/research.go | 43 ++++++++++++++ pkg/closer/closer.go | 37 ++++++++++++ 11 files changed, 344 insertions(+), 12 deletions(-) create mode 100644 internal/app/app.go create mode 100644 internal/controller/metrics/prometheus.go create mode 100644 internal/controller/metrics/route.go create mode 100644 internal/server/http/http.go create mode 100644 internal/worker/research/research.go create mode 100644 pkg/closer/closer.go diff --git a/.env b/.env index 659dd1c..1dcea24 100644 --- a/.env +++ b/.env @@ -1,2 +1,4 @@ SMTP_API_URL=https://api.smtp.bz/v1 -SMTP_API_KEY=P0YsjUB137upXrr1NiJefHmXVKW1hmBWlpev \ No newline at end of file +SMTP_API_KEY=P0YsjUB137upXrr1NiJefHmXVKW1hmBWlpev +FREQUENCY=5 +ADMIN_HTTP_URL=localhost:8000 \ No newline at end of file diff --git a/cmd/main.go b/cmd/main.go index 06ab7d0..78756b5 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -1 +1,30 @@ package main + +import ( + "context" + "gitea.pena/PenaDevops/smtpbiz-exporter/internal/app" + "gitea.pena/PenaDevops/smtpbiz-exporter/internal/initialize" + "go.uber.org/zap" + "log" + "os/signal" + "syscall" +) + +func main() { + logger, err := zap.NewProduction() + if err != nil { + log.Fatalf("can't initialize zap logger: %v", err) + } + + config, err := initialize.LoadConfig() + if err != nil { + logger.Fatal("can't initialize config", zap.Error(err)) + } + + ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) + defer stop() + + if err = app.Run(ctx, logger, *config); err != nil { + logger.Fatal("can't start app", zap.Error(err)) + } +} diff --git a/go.mod b/go.mod index e223321..8efe185 100644 --- a/go.mod +++ b/go.mod @@ -2,20 +2,32 @@ module gitea.pena/PenaDevops/smtpbiz-exporter go 1.23.3 -require github.com/gofiber/fiber/v2 v2.52.5 +require ( + github.com/caarlos0/env/v8 v8.0.0 + github.com/gofiber/fiber/v2 v2.52.5 + github.com/joho/godotenv v1.5.1 + github.com/prometheus/client_golang v1.20.5 + go.uber.org/zap v1.27.0 +) require ( github.com/andybalholm/brotli v1.0.5 // indirect - github.com/caarlos0/env/v8 v8.0.0 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/google/uuid v1.5.0 // indirect - github.com/joho/godotenv v1.5.1 // indirect - github.com/klauspost/compress v1.17.0 // indirect + github.com/klauspost/compress v1.17.9 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // 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/rivo/uniseg v0.2.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.51.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect - golang.org/x/sys v0.15.0 // indirect + go.uber.org/multierr v1.10.0 // indirect + golang.org/x/sys v0.22.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect ) diff --git a/go.sum b/go.sum index 8b21421..1254526 100644 --- a/go.sum +++ b/go.sum @@ -1,15 +1,25 @@ github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +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/caarlos0/env/v8 v8.0.0 h1:POhxHhSpuxrLMIdvTGARuZqR4Jjm8AYmoi/JKlcScs0= github.com/caarlos0/env/v8 v8.0.0/go.mod h1:7K4wMY9bH0esiXSSHlfHLX5xKGQMnkH5Fk4TDSSSzfo= +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/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/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo= github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= +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.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= -github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= -github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +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/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= @@ -17,15 +27,39 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +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/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.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= +github.com/prometheus/client_golang v1.20.5/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/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +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/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA= github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g= github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= +go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +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/internal/app/app.go b/internal/app/app.go new file mode 100644 index 0000000..7773507 --- /dev/null +++ b/internal/app/app.go @@ -0,0 +1,70 @@ +package app + +import ( + "context" + "errors" + "gitea.pena/PenaDevops/smtpbiz-exporter/internal/client" + "gitea.pena/PenaDevops/smtpbiz-exporter/internal/controller/metrics" + "gitea.pena/PenaDevops/smtpbiz-exporter/internal/initialize" + "gitea.pena/PenaDevops/smtpbiz-exporter/internal/server/http" + "gitea.pena/PenaDevops/smtpbiz-exporter/internal/worker/research" + "gitea.pena/PenaDevops/smtpbiz-exporter/pkg/closer" + "go.uber.org/zap" + "time" +) + +func Run(ctx context.Context, logger *zap.Logger, cfg initialize.Config) error { + defer func() { + if r := recover(); r != nil { + logger.Error("Recovered from a panic", zap.Any("error", r)) + } + }() + + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + shutdownGroup := closer.NewCloserGroup() + + smtpClient := client.NewSMTPClient(cfg.SmtpApiUrl, cfg.SmtpApiKey) + + researchWorker := research.NewResearch(research.Deps{ + Client: smtpClient, + Logger: logger, + Frequency: cfg.Frequency, + }) + + go researchWorker.Start(ctx) + + prometheusController := metrics.NewPrometheus() + + adminServer := http.NewServer(http.ServerConfig{ + Logger: logger, + Controllers: []http.Controller{prometheusController}, + }) + + go func() { + if err := adminServer.Start(cfg.AdminHttpURL); err != nil { + logger.Error("failed to start admin http server", zap.Error(err)) + cancel() + } + }() + adminServer.ListRoutes() + + shutdownGroup.Add(closer.CloserFunc(adminServer.Shutdown)) + + <-ctx.Done() + + timeoutCtx, timeoutCancel := context.WithTimeout(context.Background(), 10*time.Second) + defer timeoutCancel() + if err := shutdownGroup.Call(timeoutCtx); err != nil { + if errors.Is(err, context.DeadlineExceeded) { + logger.Error("Shutdown timed out", zap.Error(err)) + } else { + logger.Error("Failed to shutdown services gracefully", zap.Error(err)) + } + return err + } + + logger.Info("Application has stopped") + return nil +} diff --git a/internal/controller/metrics/prometheus.go b/internal/controller/metrics/prometheus.go new file mode 100644 index 0000000..9cbfee3 --- /dev/null +++ b/internal/controller/metrics/prometheus.go @@ -0,0 +1,24 @@ +package metrics + +import ( + "github.com/prometheus/client_golang/prometheus/promhttp" + "net/http" +) + +type Prometheus struct { +} + +func NewPrometheus() *Prometheus { + return &Prometheus{} +} + +func (receiver *Prometheus) Metrics(w http.ResponseWriter, r *http.Request) { + receiver.updateMetrics() + handler := promhttp.Handler() + + handler.ServeHTTP(w, r) +} + +func (receiver *Prometheus) updateMetrics() { + +} diff --git a/internal/controller/metrics/route.go b/internal/controller/metrics/route.go new file mode 100644 index 0000000..d634be1 --- /dev/null +++ b/internal/controller/metrics/route.go @@ -0,0 +1,14 @@ +package metrics + +import ( + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/adaptor" +) + +func (receiver *Prometheus) Register(router fiber.Router) { + router.Get("/metrics", adaptor.HTTPHandlerFunc(receiver.Metrics)) +} + +func (receiver *Prometheus) Name() string { + return "" +} diff --git a/internal/initialize/config.go b/internal/initialize/config.go index 56b65fa..9483d2c 100644 --- a/internal/initialize/config.go +++ b/internal/initialize/config.go @@ -7,8 +7,10 @@ import ( ) type Config struct { - SmtpApiUrl string `env:"SMTP_API_URL"` - SmtpApiKey string `env:"SMTP_API_KEY"` + SmtpApiUrl string `env:"SMTP_API_URL"` + SmtpApiKey string `env:"SMTP_API_KEY"` + Frequency int64 `env:"FREQUENCY"` + AdminHttpURL string `env:"ADMIN_HTTP_URL"` } func LoadConfig() (*Config, error) { diff --git a/internal/server/http/http.go b/internal/server/http/http.go new file mode 100644 index 0000000..88633bd --- /dev/null +++ b/internal/server/http/http.go @@ -0,0 +1,65 @@ +package http + +import ( + "context" + "fmt" + "github.com/gofiber/fiber/v2" + "go.uber.org/zap" +) + +type ServerConfig struct { + Logger *zap.Logger + Controllers []Controller +} + +type Server struct { + Logger *zap.Logger + Controllers []Controller + app *fiber.App +} + +func NewServer(config ServerConfig) *Server { + app := fiber.New() + s := &Server{ + Logger: config.Logger, + Controllers: config.Controllers, + app: app, + } + + s.registerRoutes() + + return s +} + +func (s *Server) Start(addr string) error { + if err := s.app.Listen(addr); err != nil { + s.Logger.Error("Failed to start server", zap.Error(err)) + return err + } + return nil +} + +func (s *Server) Shutdown(ctx context.Context) error { + return s.app.Shutdown() +} + +func (s *Server) registerRoutes() { + for _, c := range s.Controllers { + router := s.app.Group(c.Name()) + c.Register(router) + } +} + +type Controller interface { + Register(router fiber.Router) + Name() string +} + +func (s *Server) ListRoutes() { + fmt.Println("Registered routes:") + for _, stack := range s.app.Stack() { + for _, route := range stack { + fmt.Printf("%s %s\n", route.Method, route.Path) + } + } +} diff --git a/internal/worker/research/research.go b/internal/worker/research/research.go new file mode 100644 index 0000000..1de6cbf --- /dev/null +++ b/internal/worker/research/research.go @@ -0,0 +1,43 @@ +package research + +import ( + "context" + "gitea.pena/PenaDevops/smtpbiz-exporter/internal/client" + "go.uber.org/zap" + "time" +) + +type Deps struct { + Client *client.SMTPClient + Logger *zap.Logger + Frequency int64 +} + +type Research struct { + client *client.SMTPClient + logger *zap.Logger + frequency int64 +} + +func NewResearch(deps Deps) *Research { + return &Research{ + client: deps.Client, + logger: deps.Logger, + frequency: deps.Frequency, + } +} + +func (r *Research) Start(ctx context.Context) { + ticker := time.NewTicker(time.Second * time.Duration(r.frequency)) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + r.StartFetching(ctx) + case <-ctx.Done(): + return + } + } +} +func (r *Research) StartFetching(ctx context.Context) {} diff --git a/pkg/closer/closer.go b/pkg/closer/closer.go new file mode 100644 index 0000000..fdfbaf1 --- /dev/null +++ b/pkg/closer/closer.go @@ -0,0 +1,37 @@ +package closer + +import ( + "context" +) + +type Closer interface { + Close(ctx context.Context) error +} + +type CloserFunc func(ctx context.Context) error + +func (cf CloserFunc) Close(ctx context.Context) error { + return cf(ctx) +} + +type CloserGroup struct { + closers []Closer +} + +func NewCloserGroup() *CloserGroup { + return &CloserGroup{} +} + +func (cg *CloserGroup) Add(c Closer) { + cg.closers = append(cg.closers, c) +} + +func (cg *CloserGroup) Call(ctx context.Context) error { + var closeErr error + for i := len(cg.closers) - 1; i >= 0; i-- { + if err := cg.closers[i].Close(ctx); err != nil && closeErr == nil { + closeErr = err + } + } + return closeErr +}