From 1b2ba9ab45c686909276523931206254df921451 Mon Sep 17 00:00:00 2001 From: "e.tsibereva" Date: Sun, 19 Nov 2023 13:29:40 +0300 Subject: [PATCH 01/12] change run method: for to range --- internal/agent/uploader/uploader.go | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/internal/agent/uploader/uploader.go b/internal/agent/uploader/uploader.go index ba6b7d3..bf150db 100644 --- a/internal/agent/uploader/uploader.go +++ b/internal/agent/uploader/uploader.go @@ -41,22 +41,15 @@ func NewUploader( } func (u *Uploader) Run() { - time.NewTicker(u.reportInterval) - ticker := time.NewTicker(u.reportInterval) - for { - select { - case <-ticker.C: - if err := u.SendGaugeMetrics(u.gaugeMetricsFunc()); err != nil { - u.errorChan <- err - return - } - if err := u.SendCounterMetrics(u.counterMetricsFunc()); err != nil { - u.errorChan <- err - return - } - default: - continue + for range ticker.C { + if err := u.SendGaugeMetrics(u.gaugeMetricsFunc()); err != nil { + u.errorChan <- err + return + } + if err := u.SendCounterMetrics(u.counterMetricsFunc()); err != nil { + u.errorChan <- err + return } } } From b8f8dafaa36129a96ab9d92278b4a0f3f745ccc9 Mon Sep 17 00:00:00 2001 From: "e.tsibereva" Date: Sun, 19 Nov 2023 20:05:40 +0300 Subject: [PATCH 02/12] added logging to server side: iter1 --- cmd/agent/root/root.go | 3 ++ cmd/server/root/root.go | 4 ++ go.mod | 2 + go.sum | 6 +++ internal/logger/logger.go | 66 ++++++++++++++++++++++++++++++ internal/server/handler/handler.go | 7 ++-- 6 files changed, 85 insertions(+), 3 deletions(-) create mode 100644 internal/logger/logger.go diff --git a/cmd/agent/root/root.go b/cmd/agent/root/root.go index 77e3398..3f27e28 100644 --- a/cmd/agent/root/root.go +++ b/cmd/agent/root/root.go @@ -7,6 +7,7 @@ import ( "github.com/ElizavetaFirst/go-metrics-alerts/internal/agent/collector" "github.com/ElizavetaFirst/go-metrics-alerts/internal/agent/uploader" + "github.com/ElizavetaFirst/go-metrics-alerts/internal/logger" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -23,6 +24,8 @@ var RootCmd = &cobra.Command{ return nil }, RunE: func(cmd *cobra.Command, args []string) error { + logger.Init() + addr, err := cmd.Flags().GetString("addr") if err != nil { return errors.Wrap(err, "can't get addr flag") diff --git a/cmd/server/root/root.go b/cmd/server/root/root.go index f3eb699..da39ac0 100644 --- a/cmd/server/root/root.go +++ b/cmd/server/root/root.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" + "github.com/ElizavetaFirst/go-metrics-alerts/internal/logger" "github.com/ElizavetaFirst/go-metrics-alerts/internal/server/handler" "github.com/ElizavetaFirst/go-metrics-alerts/internal/server/storage" "github.com/gin-gonic/gin" @@ -23,6 +24,8 @@ var RootCmd = &cobra.Command{ return nil }, RunE: func(cmd *cobra.Command, args []string) error { + logger.Init() + addr, err := cmd.Flags().GetString("addr") if err != nil { return errors.Wrap(err, "can't get addr flag") @@ -44,6 +47,7 @@ var RootCmd = &cobra.Command{ if err != nil { return fmt.Errorf("run addr %s error %w", addr, err) } + return nil }, } diff --git a/go.mod b/go.mod index 8112e63..8c7a887 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/spf13/cobra v1.8.0 github.com/stretchr/testify v1.8.3 + go.uber.org/zap v1.26.0 ) require ( @@ -31,6 +32,7 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.11 // indirect + go.uber.org/multierr v1.10.0 // indirect golang.org/x/arch v0.3.0 // indirect golang.org/x/crypto v0.9.0 // indirect golang.org/x/net v0.10.0 // indirect diff --git a/go.sum b/go.sum index c7e0ed1..f2f2145 100644 --- a/go.sum +++ b/go.sum @@ -70,6 +70,12 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +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.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= +go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= diff --git a/internal/logger/logger.go b/internal/logger/logger.go new file mode 100644 index 0000000..95b0c95 --- /dev/null +++ b/internal/logger/logger.go @@ -0,0 +1,66 @@ +package logger + +import ( + "fmt" + "time" + + "github.com/gin-gonic/gin" + "go.uber.org/zap" +) + +var log *zap.Logger + +func Init() { + var err error + log, err = zap.NewProduction() + if err != nil { + panic(fmt.Sprintf("can't initialize zap logger: %v", err)) + } + defer func() { + if err := log.Sync(); err != nil { + panic(fmt.Sprintf("Can't sync zap logger: %v", err)) + } + }() +} + +func GetLogger() *zap.Logger { + return log +} + +// LogRequest is a middleware that logs HTTP requests. +func LogRequest() gin.HandlerFunc { + return func(c *gin.Context) { + if GetLogger() == nil { + return + } + start := time.Now() + + // Pass to the next middleware/handler + c.Next() + + // Calculate request duration + duration := time.Since(start) + + GetLogger().Info("HTTP Request", + zap.String("method", c.Request.Method), + zap.String("url", c.Request.URL.String()), + zap.String("duration", duration.String()), + ) + } +} + +func LogResponse() gin.HandlerFunc { + return func(c *gin.Context) { + if GetLogger() == nil { + return + } + // Call the next middleware or handler + c.Next() + + // Log the information about the response + GetLogger().Info("HTTP Response", + zap.Int("status", c.Writer.Status()), + zap.Int("response_size", c.Writer.Size()), + ) + } +} diff --git a/internal/server/handler/handler.go b/internal/server/handler/handler.go index adccabf..ffa0262 100644 --- a/internal/server/handler/handler.go +++ b/internal/server/handler/handler.go @@ -6,6 +6,7 @@ import ( "strconv" "strings" + "github.com/ElizavetaFirst/go-metrics-alerts/internal/logger" "github.com/ElizavetaFirst/go-metrics-alerts/internal/server/storage" "github.com/gin-gonic/gin" ) @@ -25,10 +26,10 @@ func NewHandler(s storage.Storage) *Handler { } func (h *Handler) RegisterRoutes(r *gin.Engine) { - r.POST(updateURL, h.handleUpdate) + r.POST(updateURL, logger.LogRequest(), h.handleUpdate) r.GET(updateURL, h.handleNotAllowed) - r.GET("/value/:metricType/:metricName", h.handleGetValue) - r.GET("/", h.handleGetAllValues) + r.GET("/value/:metricType/:metricName", logger.LogResponse(), h.handleGetValue) + r.GET("/", logger.LogResponse(), h.handleGetAllValues) } func (h *Handler) handleUpdate(c *gin.Context) { From 11234334fa3eef8f1611e0b6a966ed0f36b75f81 Mon Sep 17 00:00:00 2001 From: "e.tsibereva" Date: Wed, 22 Nov 2023 21:57:11 +0300 Subject: [PATCH 03/12] added json api --- internal/agent/uploader/uploader.go | 67 ++++++++++++++++++++++++++++- internal/logger/logger.go | 20 +++++---- internal/metrics/model.go | 8 ++++ internal/server/handler/handler.go | 63 +++++++++++++++++++-------- 4 files changed, 130 insertions(+), 28 deletions(-) create mode 100644 internal/metrics/model.go diff --git a/internal/agent/uploader/uploader.go b/internal/agent/uploader/uploader.go index bf150db..4db0a00 100644 --- a/internal/agent/uploader/uploader.go +++ b/internal/agent/uploader/uploader.go @@ -1,10 +1,13 @@ package uploader import ( + "bytes" + "encoding/json" "fmt" "net/http" "time" + "github.com/ElizavetaFirst/go-metrics-alerts/internal/metrics" "github.com/pkg/errors" ) @@ -51,11 +54,21 @@ func (u *Uploader) Run() { u.errorChan <- err return } + /*if err := u.SendGaugeMetricsJson(u.gaugeMetricsFunc()); err != nil { + u.errorChan <- err + return + } + if err := u.SendCounterMetricsJson(u.counterMetricsFunc()); err != nil { + u.errorChan <- err + return + }*/ } } func (u *Uploader) sendMetrics(url string) error { - client := &http.Client{} + client := &http.Client{ + Timeout: time.Second * 30, + } req, _ := http.NewRequest(http.MethodPost, url, nil) req.Header.Set(contentTypeStr, textPlainStr) resp, err := client.Do(req) @@ -87,3 +100,55 @@ func (u *Uploader) SendCounterMetrics(metrics map[string]int64) error { } return nil } + +func (u *Uploader) sendMetricsJson(url string, metrics metrics.Metrics) error { + client := &http.Client{} + + metricsJSON, err := json.Marshal(metrics) + if err != nil { + return errors.Wrap(err, "can't marshal metrics to JSON") + } + + req, _ := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(metricsJSON)) + req.Header.Set(contentTypeStr, "application/json") + resp, err := client.Do(req) + if err != nil { + return errors.Wrap(err, "can't send update request") + } + if err = resp.Body.Close(); err != nil { + return errors.Wrap(err, "can't close update request resp.Body") + } + return nil +} + +func (u *Uploader) SendGaugeMetricsJson(metricsMap map[string]float64) error { + for k, v := range metricsMap { + metric := metrics.Metrics{ + ID: k, + MType: "gauge", + Value: &v, + } + + url := fmt.Sprintf("http://%s/update/gauge/%s/%f", u.addr, k, v) + if err := u.sendMetricsJson(url, metric); err != nil { + return err + } + } + return nil +} + +func (u *Uploader) SendCounterMetricsJson(metricsMap map[string]int64) error { + for k, v := range metricsMap { + metric := metrics.Metrics{ + ID: k, + MType: "counter", + Delta: &v, + } + + url := fmt.Sprintf("http://%s/update/counter/%s/%d", u.addr, k, v) + if err := u.sendMetricsJson(url, metric); err != nil { + return err + } + } + return nil +} diff --git a/internal/logger/logger.go b/internal/logger/logger.go index 95b0c95..0fcb99f 100644 --- a/internal/logger/logger.go +++ b/internal/logger/logger.go @@ -1,24 +1,28 @@ package logger import ( - "fmt" + "os" + "strings" "time" "github.com/gin-gonic/gin" "go.uber.org/zap" + "go.uber.org/zap/zapcore" ) var log *zap.Logger func Init() { - var err error - log, err = zap.NewProduction() - if err != nil { - panic(fmt.Sprintf("can't initialize zap logger: %v", err)) - } + logger := zap.New(zapcore.NewCore( + zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()), + zapcore.Lock(zapcore.AddSync(os.Stderr)), + zap.AtomicLevel{}, + )) defer func() { - if err := log.Sync(); err != nil { - panic(fmt.Sprintf("Can't sync zap logger: %v", err)) + if err := logger.Sync(); err != nil { + if err := logger.Sync(); err != nil && !strings.Contains(err.Error(), "inappropriate ioctl for device") { + logger.Error("Can't sync zap logger", zap.Error(err)) + } } }() } diff --git a/internal/metrics/model.go b/internal/metrics/model.go new file mode 100644 index 0000000..e625205 --- /dev/null +++ b/internal/metrics/model.go @@ -0,0 +1,8 @@ +package metrics + +type Metrics struct { + ID string `json:"id"` // имя метрики + MType string `json:"type"` // параметр, принимающий значение gauge или counter + Delta *int64 `json:"delta,omitempty"` // значение метрики в случае передачи counter + Value *float64 `json:"value,omitempty"` // значение метрики в случае передачи gauge +} diff --git a/internal/server/handler/handler.go b/internal/server/handler/handler.go index ffa0262..cf46ae8 100644 --- a/internal/server/handler/handler.go +++ b/internal/server/handler/handler.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/ElizavetaFirst/go-metrics-alerts/internal/logger" + "github.com/ElizavetaFirst/go-metrics-alerts/internal/metrics" "github.com/ElizavetaFirst/go-metrics-alerts/internal/server/storage" "github.com/gin-gonic/gin" ) @@ -33,27 +34,51 @@ func (h *Handler) RegisterRoutes(r *gin.Engine) { } func (h *Handler) handleUpdate(c *gin.Context) { - metricType := c.Param(metricTypeStr) - metricName := c.Param(metricNameStr) - metricValueParam := c.Param("metricValue") - + var metricType string + var metricName string var metricValue any - var err error - switch metricType { - case "gauge": - metricValue, err = strconv.ParseFloat(metricValueParam, 64) - case "counter": - metricValue, err = strconv.ParseInt(metricValueParam, 10, 64) - default: - c.JSON(http.StatusBadRequest, gin.H{"error": "Bad Request"}) - return + fmt.Println(c.ContentType()) + if c.ContentType() == "application/json" { + var metrics metrics.Metrics + if err := c.ShouldBindJSON(&metrics); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Bad Request: malformed JSON"}) + return + } + + metricName = metrics.ID + metricType = metrics.MType + + switch metricType { + case "gauge": + metricValue = *metrics.Value + case "counter": + metricValue = *metrics.Delta + default: + c.JSON(http.StatusBadRequest, gin.H{"error": "Bad Request: metricType should be gauge or counter"}) + return + } + } else { + metricType = c.Param(metricTypeStr) + metricName = c.Param(metricNameStr) + metricValueParam := c.Param("metricValue") + fmt.Println(metricType, metricName, metricValueParam) + + var err error + switch metricType { + case "gauge": + metricValue, err = strconv.ParseFloat(metricValueParam, 64) + case "counter": + metricValue, err = strconv.ParseInt(metricValueParam, 10, 64) + default: + c.JSON(http.StatusBadRequest, gin.H{"error": "Bad Request"}) + return + } + + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Bad Request"}) + return + } } - - if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "Bad Request"}) - return - } - if err := h.Storage.Update(metricName, storage.Metric{Type: storage.MetricType(metricType), Value: metricValue}); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Error updating metric"}) From 1b32b7c5f6ff867a6e86aa9a45ffb996c0f41e5a Mon Sep 17 00:00:00 2001 From: "e.tsibereva" Date: Wed, 22 Nov 2023 23:02:07 +0300 Subject: [PATCH 04/12] json value/ api --- cmd/agent/root/root.go | 1 + cmd/server/root/root.go | 1 + internal/logger/logger.go | 27 +++++++++++------------- internal/server/handler/handler.go | 33 +++++++++++++++++++++++++++++- 4 files changed, 46 insertions(+), 16 deletions(-) diff --git a/cmd/agent/root/root.go b/cmd/agent/root/root.go index 3f27e28..eba7764 100644 --- a/cmd/agent/root/root.go +++ b/cmd/agent/root/root.go @@ -25,6 +25,7 @@ var RootCmd = &cobra.Command{ }, RunE: func(cmd *cobra.Command, args []string) error { logger.Init() + defer logger.Sync() addr, err := cmd.Flags().GetString("addr") if err != nil { diff --git a/cmd/server/root/root.go b/cmd/server/root/root.go index da39ac0..edb7a9d 100644 --- a/cmd/server/root/root.go +++ b/cmd/server/root/root.go @@ -25,6 +25,7 @@ var RootCmd = &cobra.Command{ }, RunE: func(cmd *cobra.Command, args []string) error { logger.Init() + defer logger.Sync() addr, err := cmd.Flags().GetString("addr") if err != nil { diff --git a/internal/logger/logger.go b/internal/logger/logger.go index 0fcb99f..a312921 100644 --- a/internal/logger/logger.go +++ b/internal/logger/logger.go @@ -1,30 +1,27 @@ package logger import ( - "os" - "strings" + "fmt" "time" "github.com/gin-gonic/gin" "go.uber.org/zap" - "go.uber.org/zap/zapcore" ) var log *zap.Logger func Init() { - logger := zap.New(zapcore.NewCore( - zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()), - zapcore.Lock(zapcore.AddSync(os.Stderr)), - zap.AtomicLevel{}, - )) - defer func() { - if err := logger.Sync(); err != nil { - if err := logger.Sync(); err != nil && !strings.Contains(err.Error(), "inappropriate ioctl for device") { - logger.Error("Can't sync zap logger", zap.Error(err)) - } - } - }() + var err error + log, err = zap.NewProduction() + if err != nil { + panic(fmt.Sprintf("can't initialize zap logger: %v", err)) + } +} + +func Sync() { + if err := log.Sync(); err != nil { + panic(fmt.Sprintf("Can't sync zap logger: %v", err)) + } } func GetLogger() *zap.Logger { diff --git a/internal/server/handler/handler.go b/internal/server/handler/handler.go index cf46ae8..823300c 100644 --- a/internal/server/handler/handler.go +++ b/internal/server/handler/handler.go @@ -30,6 +30,7 @@ func (h *Handler) RegisterRoutes(r *gin.Engine) { r.POST(updateURL, logger.LogRequest(), h.handleUpdate) r.GET(updateURL, h.handleNotAllowed) r.GET("/value/:metricType/:metricName", logger.LogResponse(), h.handleGetValue) + r.POST("/value", logger.LogResponse(), h.handleJsonGetValue) r.GET("/", logger.LogResponse(), h.handleGetAllValues) } @@ -37,7 +38,6 @@ func (h *Handler) handleUpdate(c *gin.Context) { var metricType string var metricName string var metricValue any - fmt.Println(c.ContentType()) if c.ContentType() == "application/json" { var metrics metrics.Metrics if err := c.ShouldBindJSON(&metrics); err != nil { @@ -92,6 +92,37 @@ func (h *Handler) handleNotAllowed(c *gin.Context) { c.JSON(http.StatusMethodNotAllowed, gin.H{"error": "Method Not Allowed"}) } +func (h *Handler) handleJsonGetValue(c *gin.Context) { + var metrics metrics.Metrics + if err := c.ShouldBindJSON(&metrics); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + value, found := h.Storage.Get(metrics.ID) + if !found || string(value.Type) != metrics.MType { + c.Status(http.StatusNotFound) + return + } + + if metrics.MType == "counter" { + delta, ok := value.Value.(int64) + if !ok { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid data type for Delta"}) + return + } + metrics.Delta = &delta + } else if metrics.MType == "gauge" { + val, ok := value.Value.(float64) + if !ok { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid data type for Value"}) + return + } + metrics.Value = &val + } + + c.JSON(http.StatusOK, metrics) +} + func (h *Handler) handleGetValue(c *gin.Context) { metricType := c.Param(metricTypeStr) metricName := c.Param(metricNameStr) From 7db3e1499ca69e81309b33fce39569748a63955c Mon Sep 17 00:00:00 2001 From: "e.tsibereva" Date: Fri, 24 Nov 2023 01:20:37 +0300 Subject: [PATCH 05/12] add functionality for duplicate metrics of differend types --- internal/agent/uploader/uploader.go | 44 ++++++----- internal/logger/logger.go | 4 +- internal/server/handler/handler.go | 99 ++++++++++++++----------- internal/server/handler/handler_test.go | 8 +- internal/server/storage/storage.go | 18 ++--- internal/server/storage/storage_test.go | 8 +- 6 files changed, 101 insertions(+), 80 deletions(-) diff --git a/internal/agent/uploader/uploader.go b/internal/agent/uploader/uploader.go index 4db0a00..943a452 100644 --- a/internal/agent/uploader/uploader.go +++ b/internal/agent/uploader/uploader.go @@ -7,13 +7,16 @@ import ( "net/http" "time" + "github.com/ElizavetaFirst/go-metrics-alerts/internal/logger" "github.com/ElizavetaFirst/go-metrics-alerts/internal/metrics" "github.com/pkg/errors" + "go.uber.org/zap" ) const ( contentTypeStr = "Content-Type" textPlainStr = "text/plain" + maxErrors = 1000 ) type ( @@ -45,23 +48,30 @@ func NewUploader( func (u *Uploader) Run() { ticker := time.NewTicker(u.reportInterval) + + errorCount := 0 for range ticker.C { - if err := u.SendGaugeMetrics(u.gaugeMetricsFunc()); err != nil { - u.errorChan <- err - return - } - if err := u.SendCounterMetrics(u.counterMetricsFunc()); err != nil { - u.errorChan <- err - return - } - /*if err := u.SendGaugeMetricsJson(u.gaugeMetricsFunc()); err != nil { - u.errorChan <- err - return + for { + if err := u.SendGaugeMetricsJson(u.gaugeMetricsFunc()); err != nil { + logger.GetLogger().Warn("SendGaugeMetricsJson return error", zap.Error(err)) + errorCount++ + if errorCount >= maxErrors { + u.errorChan <- err + return + } + continue + } + if err := u.SendCounterMetricsJson(u.counterMetricsFunc()); err != nil { + logger.GetLogger().Warn("SendCounterMetricsJson return error", zap.Error(err)) + errorCount++ + if errorCount >= maxErrors { + u.errorChan <- err + return + } + continue + } + break } - if err := u.SendCounterMetricsJson(u.counterMetricsFunc()); err != nil { - u.errorChan <- err - return - }*/ } } @@ -129,7 +139,7 @@ func (u *Uploader) SendGaugeMetricsJson(metricsMap map[string]float64) error { Value: &v, } - url := fmt.Sprintf("http://%s/update/gauge/%s/%f", u.addr, k, v) + url := fmt.Sprintf("http://%s/update", u.addr) if err := u.sendMetricsJson(url, metric); err != nil { return err } @@ -145,7 +155,7 @@ func (u *Uploader) SendCounterMetricsJson(metricsMap map[string]int64) error { Delta: &v, } - url := fmt.Sprintf("http://%s/update/counter/%s/%d", u.addr, k, v) + url := fmt.Sprintf("http://%s/update", u.addr) if err := u.sendMetricsJson(url, metric); err != nil { return err } diff --git a/internal/logger/logger.go b/internal/logger/logger.go index a312921..25af473 100644 --- a/internal/logger/logger.go +++ b/internal/logger/logger.go @@ -1,7 +1,9 @@ package logger import ( + "errors" "fmt" + "syscall" "time" "github.com/gin-gonic/gin" @@ -19,7 +21,7 @@ func Init() { } func Sync() { - if err := log.Sync(); err != nil { + if err := log.Sync(); err != nil && (!errors.Is(err, syscall.EBADF) && !errors.Is(err, syscall.ENOTTY)) { panic(fmt.Sprintf("Can't sync zap logger: %v", err)) } } diff --git a/internal/server/handler/handler.go b/internal/server/handler/handler.go index 823300c..632952c 100644 --- a/internal/server/handler/handler.go +++ b/internal/server/handler/handler.go @@ -28,56 +28,69 @@ func NewHandler(s storage.Storage) *Handler { func (h *Handler) RegisterRoutes(r *gin.Engine) { r.POST(updateURL, logger.LogRequest(), h.handleUpdate) + r.POST("/update", logger.LogRequest(), h.handleJsonUpdate) r.GET(updateURL, h.handleNotAllowed) r.GET("/value/:metricType/:metricName", logger.LogResponse(), h.handleGetValue) - r.POST("/value", logger.LogResponse(), h.handleJsonGetValue) + r.POST("/value/", logger.LogRequest(), h.handleJsonGetValue) r.GET("/", logger.LogResponse(), h.handleGetAllValues) } -func (h *Handler) handleUpdate(c *gin.Context) { +func (h *Handler) handleJsonUpdate(c *gin.Context) { var metricType string var metricName string var metricValue any - if c.ContentType() == "application/json" { - var metrics metrics.Metrics - if err := c.ShouldBindJSON(&metrics); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "Bad Request: malformed JSON"}) - return - } + var metrics metrics.Metrics + if err := c.ShouldBindJSON(&metrics); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Bad Request: malformed JSON"}) + return + } - metricName = metrics.ID - metricType = metrics.MType + metricName = metrics.ID + metricType = metrics.MType - switch metricType { - case "gauge": - metricValue = *metrics.Value - case "counter": - metricValue = *metrics.Delta - default: - c.JSON(http.StatusBadRequest, gin.H{"error": "Bad Request: metricType should be gauge or counter"}) - return - } - } else { - metricType = c.Param(metricTypeStr) - metricName = c.Param(metricNameStr) - metricValueParam := c.Param("metricValue") - fmt.Println(metricType, metricName, metricValueParam) - - var err error - switch metricType { - case "gauge": - metricValue, err = strconv.ParseFloat(metricValueParam, 64) - case "counter": - metricValue, err = strconv.ParseInt(metricValueParam, 10, 64) - default: - c.JSON(http.StatusBadRequest, gin.H{"error": "Bad Request"}) - return - } + switch metricType { + case "gauge": + metricValue = *metrics.Value + case "counter": + metricValue = *metrics.Delta + default: + c.JSON(http.StatusBadRequest, gin.H{"error": "Bad Request: metricType should be gauge or counter"}) + return + } - if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "Bad Request"}) - return - } + if err := h.Storage.Update(metricName, storage.Metric{Type: storage.MetricType(metricType), + Value: metricValue}); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Error updating metric"}) + return + } + + c.Status(http.StatusOK) +} + +func (h *Handler) handleUpdate(c *gin.Context) { + var metricType string + var metricName string + var metricValue any + + metricType = c.Param(metricTypeStr) + metricName = c.Param(metricNameStr) + metricValueParam := c.Param("metricValue") + fmt.Println(metricType, metricName, metricValueParam) + + var err error + switch metricType { + case "gauge": + metricValue, err = strconv.ParseFloat(metricValueParam, 64) + case "counter": + metricValue, err = strconv.ParseInt(metricValueParam, 10, 64) + default: + c.JSON(http.StatusBadRequest, gin.H{"error": "Bad Request"}) + return + } + + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Bad Request"}) + return } if err := h.Storage.Update(metricName, storage.Metric{Type: storage.MetricType(metricType), Value: metricValue}); err != nil { @@ -98,7 +111,7 @@ func (h *Handler) handleJsonGetValue(c *gin.Context) { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } - value, found := h.Storage.Get(metrics.ID) + value, found := h.Storage.Get(metrics.ID, metrics.MType) if !found || string(value.Type) != metrics.MType { c.Status(http.StatusNotFound) return @@ -127,7 +140,7 @@ func (h *Handler) handleGetValue(c *gin.Context) { metricType := c.Param(metricTypeStr) metricName := c.Param(metricNameStr) - value, found := h.Storage.Get(metricName) + value, found := h.Storage.Get(metricName, metricType) if !found || string(value.Type) != metricType { c.Status(http.StatusNotFound) return @@ -143,7 +156,9 @@ func (h *Handler) handleGetAllValues(c *gin.Context) { htmlResponse.WriteString("") for name, metric := range values { - htmlResponse.WriteString(fmt.Sprintf("

%s (%s): %v

", name, metric.Type, metric.Value)) + metricTypeStr := string(metric.Type) + metricName := strings.TrimSuffix(name, metricTypeStr) + htmlResponse.WriteString(fmt.Sprintf("

%s (%s): %v

", metricName, metric.Type, metric.Value)) } htmlResponse.WriteString("") diff --git a/internal/server/handler/handler_test.go b/internal/server/handler/handler_test.go index 3cf1441..233c30b 100644 --- a/internal/server/handler/handler_test.go +++ b/internal/server/handler/handler_test.go @@ -16,7 +16,7 @@ func (ms *mockStorage) Update(name string, metric storage.Metric) error { return nil } -func (ms *mockStorage) Get(name string) (storage.Metric, bool) { +func (ms *mockStorage) Get(name string, metricType string) (storage.Metric, bool) { return storage.Metric{}, false } @@ -57,12 +57,6 @@ func TestHandler_ServeHTTP(t *testing.T) { "/update/counter/test/123.3", http.StatusBadRequest, }, - /*{ - "Valid get value request", - http.MethodGet, - "/value/gauge/test", - http.StatusOK, - },*/ { "Not found get value", http.MethodGet, diff --git a/internal/server/storage/storage.go b/internal/server/storage/storage.go index 628e329..03f6d51 100644 --- a/internal/server/storage/storage.go +++ b/internal/server/storage/storage.go @@ -15,7 +15,7 @@ const ( type Storage interface { Update(metricName string, update Metric) error - Get(metricName string) (Metric, bool) + Get(metricName string, metricType string) (Metric, bool) GetAll() map[string]Metric } @@ -28,18 +28,17 @@ func NewMemStorage() *memStorage { } func (ms *memStorage) Update(metricName string, update Metric) error { - m, exists := ms.Data.Load(metricName) + uniqueID := metricName + string(update.Type) + fmt.Println(uniqueID) + m, exists := ms.Data.Load(uniqueID) if !exists { - ms.Data.Store(metricName, update) + ms.Data.Store(uniqueID, update) return nil } metric, ok := m.(Metric) if !ok { return errors.New("can't get metric") } - if metric.Type != update.Type { - return nil - } switch metric.Type { case Gauge: @@ -54,12 +53,13 @@ func (ms *memStorage) Update(metricName string, update Metric) error { } } - ms.Data.Store(metricName, metric) + ms.Data.Store(uniqueID, metric) return nil } -func (ms *memStorage) Get(metricName string) (Metric, bool) { - metric, exists := ms.Data.Load(metricName) +func (ms *memStorage) Get(metricName string, metricType string) (Metric, bool) { + uniqueID := metricName + metricType + metric, exists := ms.Data.Load(uniqueID) if exists { return metric.(Metric), exists } diff --git a/internal/server/storage/storage_test.go b/internal/server/storage/storage_test.go index b794db2..5da403f 100644 --- a/internal/server/storage/storage_test.go +++ b/internal/server/storage/storage_test.go @@ -8,7 +8,7 @@ import ( func TestGetGauge(t *testing.T) { ms := NewMemStorage() - _, ok := ms.Get("nonexistent") + _, ok := ms.Get("nonexistent", "gauge") if ok == true { t.Errorf("expected false for nonexistent metric, got %v", ok) } @@ -21,7 +21,7 @@ func TestGetGauge(t *testing.T) { fmt.Printf("can't update testMetric %v", err) } - metric, ok := ms.Get("testMetric") + metric, ok := ms.Get("testMetric", "gauge") if ok == false { t.Errorf("expected true for existent metric, got %v", ok) } else if metric.Value != 23.5 { @@ -32,7 +32,7 @@ func TestGetGauge(t *testing.T) { func TestGetCounter(t *testing.T) { ms := NewMemStorage() - _, ok := ms.Get("nonexistent") + _, ok := ms.Get("nonexistent", "counter") if ok == true { t.Errorf("expected false for nonexistent metric, got %v", ok) } @@ -53,7 +53,7 @@ func TestGetCounter(t *testing.T) { fmt.Printf("can't update testMetric %v", err) } - metric, ok := ms.Get("testMetric") + metric, ok := ms.Get("testMetric", "counter") if ok == false { t.Errorf("expected true for existent metric, got %v", ok) } else if metric.Value != int64(10) { From dd5fca58848b235de28f1d86e6c79ab458271678 Mon Sep 17 00:00:00 2001 From: "e.tsibereva" Date: Fri, 24 Nov 2023 01:54:17 +0300 Subject: [PATCH 06/12] fix lint --- internal/agent/uploader/uploader.go | 49 +++++++++++++++---------- internal/constants/constants.go | 6 +++ internal/metrics/model.go | 4 +- internal/server/handler/handler.go | 19 +++++----- internal/server/handler/handler_test.go | 3 +- internal/server/storage/storage.go | 6 ++- internal/server/storage/storage_test.go | 10 +++-- 7 files changed, 59 insertions(+), 38 deletions(-) create mode 100644 internal/constants/constants.go diff --git a/internal/agent/uploader/uploader.go b/internal/agent/uploader/uploader.go index 943a452..9170d6f 100644 --- a/internal/agent/uploader/uploader.go +++ b/internal/agent/uploader/uploader.go @@ -7,6 +7,7 @@ import ( "net/http" "time" + "github.com/ElizavetaFirst/go-metrics-alerts/internal/constants" "github.com/ElizavetaFirst/go-metrics-alerts/internal/logger" "github.com/ElizavetaFirst/go-metrics-alerts/internal/metrics" "github.com/pkg/errors" @@ -14,9 +15,13 @@ import ( ) const ( - contentTypeStr = "Content-Type" - textPlainStr = "text/plain" - maxErrors = 1000 + contentTypeStr = "Content-Type" + textPlainStr = "text/plain" + maxErrors = 1000 + maxTimeout = 30 + cantSendUpdate = "can't send update request" + cantCloseBody = "can't close update request resp.Body" + updateReqFormat = "http://%s/update" ) type ( @@ -52,7 +57,7 @@ func (u *Uploader) Run() { errorCount := 0 for range ticker.C { for { - if err := u.SendGaugeMetricsJson(u.gaugeMetricsFunc()); err != nil { + if err := u.SendGaugeMetricsJSON(u.gaugeMetricsFunc()); err != nil { logger.GetLogger().Warn("SendGaugeMetricsJson return error", zap.Error(err)) errorCount++ if errorCount >= maxErrors { @@ -61,7 +66,7 @@ func (u *Uploader) Run() { } continue } - if err := u.SendCounterMetricsJson(u.counterMetricsFunc()); err != nil { + if err := u.SendCounterMetricsJSON(u.counterMetricsFunc()); err != nil { logger.GetLogger().Warn("SendCounterMetricsJson return error", zap.Error(err)) errorCount++ if errorCount >= maxErrors { @@ -77,16 +82,16 @@ func (u *Uploader) Run() { func (u *Uploader) sendMetrics(url string) error { client := &http.Client{ - Timeout: time.Second * 30, + Timeout: time.Second * maxTimeout, } req, _ := http.NewRequest(http.MethodPost, url, nil) req.Header.Set(contentTypeStr, textPlainStr) resp, err := client.Do(req) if err != nil { - return errors.Wrap(err, "can't send update request") + return errors.Wrap(err, cantSendUpdate) } if err = resp.Body.Close(); err != nil { - return errors.Wrap(err, "can't close update request resp.Body") + return errors.Wrap(err, cantCloseBody) } return nil } @@ -111,8 +116,10 @@ func (u *Uploader) SendCounterMetrics(metrics map[string]int64) error { return nil } -func (u *Uploader) sendMetricsJson(url string, metrics metrics.Metrics) error { - client := &http.Client{} +func (u *Uploader) sendMetricsJSON(url string, metrics metrics.Metrics) error { + client := &http.Client{ + Timeout: time.Second * maxTimeout, + } metricsJSON, err := json.Marshal(metrics) if err != nil { @@ -123,40 +130,42 @@ func (u *Uploader) sendMetricsJson(url string, metrics metrics.Metrics) error { req.Header.Set(contentTypeStr, "application/json") resp, err := client.Do(req) if err != nil { - return errors.Wrap(err, "can't send update request") + return errors.Wrap(err, cantSendUpdate) } if err = resp.Body.Close(); err != nil { - return errors.Wrap(err, "can't close update request resp.Body") + return errors.Wrap(err, cantCloseBody) } return nil } -func (u *Uploader) SendGaugeMetricsJson(metricsMap map[string]float64) error { +func (u *Uploader) SendGaugeMetricsJSON(metricsMap map[string]float64) error { for k, v := range metricsMap { + v := v metric := metrics.Metrics{ ID: k, - MType: "gauge", + MType: constants.Gauge, Value: &v, } - url := fmt.Sprintf("http://%s/update", u.addr) - if err := u.sendMetricsJson(url, metric); err != nil { + url := fmt.Sprintf(updateReqFormat, u.addr) + if err := u.sendMetricsJSON(url, metric); err != nil { return err } } return nil } -func (u *Uploader) SendCounterMetricsJson(metricsMap map[string]int64) error { +func (u *Uploader) SendCounterMetricsJSON(metricsMap map[string]int64) error { for k, v := range metricsMap { + v := v metric := metrics.Metrics{ ID: k, - MType: "counter", + MType: constants.Counter, Delta: &v, } - url := fmt.Sprintf("http://%s/update", u.addr) - if err := u.sendMetricsJson(url, metric); err != nil { + url := fmt.Sprintf(updateReqFormat, u.addr) + if err := u.sendMetricsJSON(url, metric); err != nil { return err } } diff --git a/internal/constants/constants.go b/internal/constants/constants.go new file mode 100644 index 0000000..3c153c0 --- /dev/null +++ b/internal/constants/constants.go @@ -0,0 +1,6 @@ +package constants + +const ( + Gauge = "gauge" + Counter = "counter" +) diff --git a/internal/metrics/model.go b/internal/metrics/model.go index e625205..5be7688 100644 --- a/internal/metrics/model.go +++ b/internal/metrics/model.go @@ -1,8 +1,8 @@ package metrics type Metrics struct { - ID string `json:"id"` // имя метрики - MType string `json:"type"` // параметр, принимающий значение gauge или counter Delta *int64 `json:"delta,omitempty"` // значение метрики в случае передачи counter Value *float64 `json:"value,omitempty"` // значение метрики в случае передачи gauge + ID string `json:"id"` // имя метрики + MType string `json:"type"` // параметр, принимающий значение gauge или counter } diff --git a/internal/server/handler/handler.go b/internal/server/handler/handler.go index 632952c..11bc81c 100644 --- a/internal/server/handler/handler.go +++ b/internal/server/handler/handler.go @@ -6,6 +6,7 @@ import ( "strconv" "strings" + "github.com/ElizavetaFirst/go-metrics-alerts/internal/constants" "github.com/ElizavetaFirst/go-metrics-alerts/internal/logger" "github.com/ElizavetaFirst/go-metrics-alerts/internal/metrics" "github.com/ElizavetaFirst/go-metrics-alerts/internal/server/storage" @@ -28,14 +29,14 @@ func NewHandler(s storage.Storage) *Handler { func (h *Handler) RegisterRoutes(r *gin.Engine) { r.POST(updateURL, logger.LogRequest(), h.handleUpdate) - r.POST("/update", logger.LogRequest(), h.handleJsonUpdate) + r.POST("/update", logger.LogRequest(), h.handleJSONUpdate) r.GET(updateURL, h.handleNotAllowed) r.GET("/value/:metricType/:metricName", logger.LogResponse(), h.handleGetValue) - r.POST("/value/", logger.LogRequest(), h.handleJsonGetValue) + r.POST("/value/", logger.LogRequest(), h.handleJSONGetValue) r.GET("/", logger.LogResponse(), h.handleGetAllValues) } -func (h *Handler) handleJsonUpdate(c *gin.Context) { +func (h *Handler) handleJSONUpdate(c *gin.Context) { var metricType string var metricName string var metricValue any @@ -49,9 +50,9 @@ func (h *Handler) handleJsonUpdate(c *gin.Context) { metricType = metrics.MType switch metricType { - case "gauge": + case constants.Gauge: metricValue = *metrics.Value - case "counter": + case constants.Counter: metricValue = *metrics.Delta default: c.JSON(http.StatusBadRequest, gin.H{"error": "Bad Request: metricType should be gauge or counter"}) @@ -79,9 +80,9 @@ func (h *Handler) handleUpdate(c *gin.Context) { var err error switch metricType { - case "gauge": + case constants.Gauge: metricValue, err = strconv.ParseFloat(metricValueParam, 64) - case "counter": + case constants.Counter: metricValue, err = strconv.ParseInt(metricValueParam, 10, 64) default: c.JSON(http.StatusBadRequest, gin.H{"error": "Bad Request"}) @@ -105,7 +106,7 @@ func (h *Handler) handleNotAllowed(c *gin.Context) { c.JSON(http.StatusMethodNotAllowed, gin.H{"error": "Method Not Allowed"}) } -func (h *Handler) handleJsonGetValue(c *gin.Context) { +func (h *Handler) handleJSONGetValue(c *gin.Context) { var metrics metrics.Metrics if err := c.ShouldBindJSON(&metrics); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) @@ -124,7 +125,7 @@ func (h *Handler) handleJsonGetValue(c *gin.Context) { return } metrics.Delta = &delta - } else if metrics.MType == "gauge" { + } else if metrics.MType == constants.Gauge { val, ok := value.Value.(float64) if !ok { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid data type for Value"}) diff --git a/internal/server/handler/handler_test.go b/internal/server/handler/handler_test.go index 233c30b..07815c3 100644 --- a/internal/server/handler/handler_test.go +++ b/internal/server/handler/handler_test.go @@ -5,6 +5,7 @@ import ( "net/http/httptest" "testing" + "github.com/ElizavetaFirst/go-metrics-alerts/internal/constants" "github.com/ElizavetaFirst/go-metrics-alerts/internal/server/storage" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" @@ -22,7 +23,7 @@ func (ms *mockStorage) Get(name string, metricType string) (storage.Metric, bool func (ms *mockStorage) GetAll() map[string]storage.Metric { return map[string]storage.Metric{ - "test": {Type: "gauge", Value: 123}, + "test": {Type: constants.Gauge, Value: 123}, } } diff --git a/internal/server/storage/storage.go b/internal/server/storage/storage.go index 03f6d51..a1187f7 100644 --- a/internal/server/storage/storage.go +++ b/internal/server/storage/storage.go @@ -4,13 +4,15 @@ import ( "errors" "fmt" "sync" + + "github.com/ElizavetaFirst/go-metrics-alerts/internal/constants" ) type MetricType string const ( - Gauge MetricType = "gauge" - Counter MetricType = "counter" + Gauge MetricType = constants.Gauge + Counter MetricType = constants.Counter ) type Storage interface { diff --git a/internal/server/storage/storage_test.go b/internal/server/storage/storage_test.go index 5da403f..0bdac10 100644 --- a/internal/server/storage/storage_test.go +++ b/internal/server/storage/storage_test.go @@ -3,12 +3,14 @@ package storage import ( "fmt" "testing" + + "github.com/ElizavetaFirst/go-metrics-alerts/internal/constants" ) func TestGetGauge(t *testing.T) { ms := NewMemStorage() - _, ok := ms.Get("nonexistent", "gauge") + _, ok := ms.Get("nonexistent", constants.Gauge) if ok == true { t.Errorf("expected false for nonexistent metric, got %v", ok) } @@ -21,7 +23,7 @@ func TestGetGauge(t *testing.T) { fmt.Printf("can't update testMetric %v", err) } - metric, ok := ms.Get("testMetric", "gauge") + metric, ok := ms.Get("testMetric", constants.Gauge) if ok == false { t.Errorf("expected true for existent metric, got %v", ok) } else if metric.Value != 23.5 { @@ -32,7 +34,7 @@ func TestGetGauge(t *testing.T) { func TestGetCounter(t *testing.T) { ms := NewMemStorage() - _, ok := ms.Get("nonexistent", "counter") + _, ok := ms.Get("nonexistent", constants.Counter) if ok == true { t.Errorf("expected false for nonexistent metric, got %v", ok) } @@ -53,7 +55,7 @@ func TestGetCounter(t *testing.T) { fmt.Printf("can't update testMetric %v", err) } - metric, ok := ms.Get("testMetric", "counter") + metric, ok := ms.Get("testMetric", constants.Counter) if ok == false { t.Errorf("expected true for existent metric, got %v", ok) } else if metric.Value != int64(10) { From fee765b2864e446423fc8cb5764f4fe719a64409 Mon Sep 17 00:00:00 2001 From: "e.tsibereva" Date: Sun, 26 Nov 2023 00:21:09 +0300 Subject: [PATCH 07/12] added gzip encoding/decoding --- cmd/server/root/root.go | 13 +++++++++++ internal/agent/uploader/uploader.go | 36 +++++++++++++++++++++++++++-- internal/compressor/compressor.go | 22 ++++++++++++++++++ internal/constants/constants.go | 1 + internal/server/storage/storage.go | 1 - 5 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 internal/compressor/compressor.go diff --git a/cmd/server/root/root.go b/cmd/server/root/root.go index edb7a9d..0ff4fd4 100644 --- a/cmd/server/root/root.go +++ b/cmd/server/root/root.go @@ -4,9 +4,12 @@ import ( "fmt" "strings" + "github.com/ElizavetaFirst/go-metrics-alerts/internal/compressor" + "github.com/ElizavetaFirst/go-metrics-alerts/internal/constants" "github.com/ElizavetaFirst/go-metrics-alerts/internal/logger" "github.com/ElizavetaFirst/go-metrics-alerts/internal/server/handler" "github.com/ElizavetaFirst/go-metrics-alerts/internal/server/storage" + "github.com/gin-contrib/gzip" "github.com/gin-gonic/gin" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -37,6 +40,16 @@ var RootCmd = &cobra.Command{ } r := gin.Default() + r.Use(compressor.GzipGinRequestMiddleware) + + r.Use(func(c *gin.Context) { + acceptEncoding := c.GetHeader("Accept-Encoding") + if strings.Contains(c.ContentType(), "application/json") || + strings.Contains(c.ContentType(), "text/html") || + strings.Contains(acceptEncoding, constants.Gzip) { + gzip.Gzip(gzip.DefaultCompression)(c) + } + }) storage := storage.NewMemStorage() diff --git a/internal/agent/uploader/uploader.go b/internal/agent/uploader/uploader.go index 9170d6f..9aac56e 100644 --- a/internal/agent/uploader/uploader.go +++ b/internal/agent/uploader/uploader.go @@ -2,8 +2,10 @@ package uploader import ( "bytes" + "compress/gzip" "encoding/json" "fmt" + "io" "net/http" "time" @@ -128,13 +130,43 @@ func (u *Uploader) sendMetricsJSON(url string, metrics metrics.Metrics) error { req, _ := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(metricsJSON)) req.Header.Set(contentTypeStr, "application/json") + req.Header.Set("Accept-Encoding", constants.Gzip) resp, err := client.Do(req) if err != nil { return errors.Wrap(err, cantSendUpdate) } - if err = resp.Body.Close(); err != nil { - return errors.Wrap(err, cantCloseBody) + defer func() { + closeErr := resp.Body.Close() + if closeErr != nil { + closeErr = errors.Wrap(closeErr, "Failed to close the body of the response") + if err == nil { + err = closeErr + } + } + }() + + var reader io.ReadCloser + defer func() { + closeErr := reader.Close() + if closeErr != nil { + closeErr = errors.Wrap(closeErr, "Failed to close io.ReadCloser") + if err == nil { + err = closeErr + } + } + }() + + switch resp.Header.Get("Content-Encoding") { + case constants.Gzip: + reader, err = gzip.NewReader(resp.Body) + if err != nil { + return errors.Wrap(err, "can't create gzip.NewReader") + } + default: + reader = resp.Body + fmt.Println(reader) } + return nil } diff --git a/internal/compressor/compressor.go b/internal/compressor/compressor.go new file mode 100644 index 0000000..515aaf4 --- /dev/null +++ b/internal/compressor/compressor.go @@ -0,0 +1,22 @@ +package compressor + +import ( + "compress/gzip" + "net/http" + "strings" + + "github.com/gin-gonic/gin" +) + +func GzipGinRequestMiddleware(c *gin.Context) { + if strings.Contains(c.GetHeader("Content-Encoding"), "gzip") { + reader, err := gzip.NewReader(c.Request.Body) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Failed to decode gzip request"}) + c.Abort() + return + } + c.Request.Body = reader + } + c.Next() +} diff --git a/internal/constants/constants.go b/internal/constants/constants.go index 3c153c0..73da6a0 100644 --- a/internal/constants/constants.go +++ b/internal/constants/constants.go @@ -3,4 +3,5 @@ package constants const ( Gauge = "gauge" Counter = "counter" + Gzip = "gzip" ) diff --git a/internal/server/storage/storage.go b/internal/server/storage/storage.go index a1187f7..1648280 100644 --- a/internal/server/storage/storage.go +++ b/internal/server/storage/storage.go @@ -31,7 +31,6 @@ func NewMemStorage() *memStorage { func (ms *memStorage) Update(metricName string, update Metric) error { uniqueID := metricName + string(update.Type) - fmt.Println(uniqueID) m, exists := ms.Data.Load(uniqueID) if !exists { ms.Data.Store(uniqueID, update) From 4b09f59b8719bb39e582ce55762aa6604d483b3b Mon Sep 17 00:00:00 2001 From: "e.tsibereva" Date: Tue, 28 Nov 2023 00:24:16 +0300 Subject: [PATCH 08/12] added saving and loading metrics to/from file --- cmd/server/main.go | 11 ++ cmd/server/root/root.go | 24 +++- go.mod | 1 + go.sum | 45 +++++++- internal/agent/uploader/uploader.go | 5 +- internal/constants/constants.go | 7 +- internal/env/env.go | 10 ++ internal/server/handler/handler.go | 2 + internal/server/handler/handler_test.go | 2 + internal/server/saver/saver.go | 141 ++++++++++++++++++++++++ internal/server/storage/storage.go | 12 ++ 11 files changed, 252 insertions(+), 8 deletions(-) create mode 100644 internal/server/saver/saver.go diff --git a/cmd/server/main.go b/cmd/server/main.go index 5917814..487bb7c 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -7,10 +7,21 @@ import ( "github.com/ElizavetaFirst/go-metrics-alerts/internal/env" ) +const defaultStoringMetrics = 300 + func main() { var addr string + var storeInterval int + var fileStoragePath string + var restore bool root.RootCmd.PersistentFlags().StringVarP(&addr, "addr", "a", env.GetEnvString("ADDRESS", "localhost:8080"), "the address of the endpoint") + root.RootCmd.PersistentFlags().IntVarP(&storeInterval, "storeInterval", "i", + env.GetEnvDuration("STORE_INTERVAL", defaultStoringMetrics), "the frequency of storing metrics") + root.RootCmd.PersistentFlags().StringVarP(&fileStoragePath, "fileStoragePath", "f", + env.GetEnvString("FILE_STORAGE_PATH", "/tmp/metrics-db.json"), "the file storage path for storing metrics") + root.RootCmd.PersistentFlags().BoolVarP(&restore, "restore", "r", + env.GetEnvBool("RESTORE", true), "the flag to decide restore metrics from disc") if err := root.RootCmd.Execute(); err != nil { fmt.Println(err) diff --git a/cmd/server/root/root.go b/cmd/server/root/root.go index 0ff4fd4..a8dd75c 100644 --- a/cmd/server/root/root.go +++ b/cmd/server/root/root.go @@ -2,17 +2,20 @@ package root import ( "fmt" + "os" "strings" "github.com/ElizavetaFirst/go-metrics-alerts/internal/compressor" "github.com/ElizavetaFirst/go-metrics-alerts/internal/constants" "github.com/ElizavetaFirst/go-metrics-alerts/internal/logger" "github.com/ElizavetaFirst/go-metrics-alerts/internal/server/handler" + "github.com/ElizavetaFirst/go-metrics-alerts/internal/server/saver" "github.com/ElizavetaFirst/go-metrics-alerts/internal/server/storage" "github.com/gin-contrib/gzip" "github.com/gin-gonic/gin" "github.com/pkg/errors" "github.com/spf13/cobra" + "go.uber.org/zap" ) var RootCmd = &cobra.Command{ @@ -38,6 +41,18 @@ var RootCmd = &cobra.Command{ if len(parts) < 2 || parts[1] == "" { return fmt.Errorf("you must provide a non-empty port number") } + storeInterval, err := cmd.Flags().GetInt("storeInterval") + if err != nil { + return errors.Wrap(err, "can't get storeInterval flag") + } + fileStoragePath, err := cmd.Flags().GetString("fileStoragePath") + if err != nil { + return errors.Wrap(err, "can't get fileStoragePath flag") + } + restore, err := cmd.Flags().GetBool("restore") + if err != nil { + return errors.Wrap(err, "can't get restore flag") + } r := gin.Default() r.Use(compressor.GzipGinRequestMiddleware) @@ -54,9 +69,16 @@ var RootCmd = &cobra.Command{ storage := storage.NewMemStorage() handler := handler.NewHandler(storage) - handler.RegisterRoutes(r) + saver := saver.NewSaver(storeInterval, fileStoragePath, restore, storage) + go func() { + if err := saver.Run(); err != nil { + logger.GetLogger().Error("Error running saver", zap.Error(err)) + os.Exit(0) + } + }() + err = r.Run(addr) if err != nil { return fmt.Errorf("run addr %s error %w", addr, err) diff --git a/go.mod b/go.mod index 8c7a887..ab7d0ca 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/ElizavetaFirst/go-metrics-alerts go 1.21.0 require ( + github.com/gin-contrib/gzip v0.0.6 github.com/gin-gonic/gin v1.9.1 github.com/pkg/errors v0.9.1 github.com/spf13/cobra v1.8.0 diff --git a/go.sum b/go.sum index f2f2145..7e196c0 100644 --- a/go.sum +++ b/go.sum @@ -5,23 +5,32 @@ github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +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/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= +github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= +github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= @@ -35,8 +44,18 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -44,12 +63,17 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= @@ -59,6 +83,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -68,6 +93,8 @@ github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gt github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= +github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= @@ -79,24 +106,40 @@ go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 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.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 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= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/internal/agent/uploader/uploader.go b/internal/agent/uploader/uploader.go index 9aac56e..40c8e74 100644 --- a/internal/agent/uploader/uploader.go +++ b/internal/agent/uploader/uploader.go @@ -19,7 +19,6 @@ import ( const ( contentTypeStr = "Content-Type" textPlainStr = "text/plain" - maxErrors = 1000 maxTimeout = 30 cantSendUpdate = "can't send update request" cantCloseBody = "can't close update request resp.Body" @@ -62,7 +61,7 @@ func (u *Uploader) Run() { if err := u.SendGaugeMetricsJSON(u.gaugeMetricsFunc()); err != nil { logger.GetLogger().Warn("SendGaugeMetricsJson return error", zap.Error(err)) errorCount++ - if errorCount >= maxErrors { + if errorCount >= constants.MaxErrors { u.errorChan <- err return } @@ -71,7 +70,7 @@ func (u *Uploader) Run() { if err := u.SendCounterMetricsJSON(u.counterMetricsFunc()); err != nil { logger.GetLogger().Warn("SendCounterMetricsJson return error", zap.Error(err)) errorCount++ - if errorCount >= maxErrors { + if errorCount >= constants.MaxErrors { u.errorChan <- err return } diff --git a/internal/constants/constants.go b/internal/constants/constants.go index 73da6a0..3c9afc1 100644 --- a/internal/constants/constants.go +++ b/internal/constants/constants.go @@ -1,7 +1,8 @@ package constants const ( - Gauge = "gauge" - Counter = "counter" - Gzip = "gzip" + Gauge = "gauge" + Counter = "counter" + Gzip = "gzip" + MaxErrors = 1000 ) diff --git a/internal/env/env.go b/internal/env/env.go index d475fb0..4669be6 100644 --- a/internal/env/env.go +++ b/internal/env/env.go @@ -20,3 +20,13 @@ func GetEnvString(key, defaultVal string) string { } return defaultVal } + +func GetEnvBool(key string, defaultVal bool) bool { + if value, exists := os.LookupEnv(key); exists { + boolVal, err := strconv.ParseBool(value) + if err == nil { + return boolVal + } + } + return defaultVal +} diff --git a/internal/server/handler/handler.go b/internal/server/handler/handler.go index 11bc81c..8f6ce8a 100644 --- a/internal/server/handler/handler.go +++ b/internal/server/handler/handler.go @@ -3,6 +3,7 @@ package handler import ( "fmt" "net/http" + "reflect" "strconv" "strings" @@ -119,6 +120,7 @@ func (h *Handler) handleJSONGetValue(c *gin.Context) { } if metrics.MType == "counter" { + fmt.Println(reflect.TypeOf(value.Value).Kind() == reflect.Int64) delta, ok := value.Value.(int64) if !ok { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid data type for Delta"}) diff --git a/internal/server/handler/handler_test.go b/internal/server/handler/handler_test.go index 07815c3..4f95a47 100644 --- a/internal/server/handler/handler_test.go +++ b/internal/server/handler/handler_test.go @@ -27,6 +27,8 @@ func (ms *mockStorage) GetAll() map[string]storage.Metric { } } +func (ms *mockStorage) SetAll(metrics map[string]storage.Metric) {} + func TestHandler_ServeHTTP(t *testing.T) { tests := []struct { name string diff --git a/internal/server/saver/saver.go b/internal/server/saver/saver.go new file mode 100644 index 0000000..87527a8 --- /dev/null +++ b/internal/server/saver/saver.go @@ -0,0 +1,141 @@ +package saver + +import ( + "encoding/json" + "errors" + "fmt" + "os" + "os/signal" + "syscall" + "time" + + "github.com/ElizavetaFirst/go-metrics-alerts/internal/constants" + "github.com/ElizavetaFirst/go-metrics-alerts/internal/logger" + "github.com/ElizavetaFirst/go-metrics-alerts/internal/server/storage" + "go.uber.org/zap" +) + +const failedCloseFile = "Failed to close file: %v" + +type Saver struct { + storage storage.Storage + fileStoragePath string + storeInterval time.Duration + restore bool +} + +func NewSaver(storeInterval int, + fileStoragePath string, + restore bool, + storage storage.Storage, +) *Saver { + return &Saver{ + storeInterval: time.Duration(storeInterval) * time.Second, + fileStoragePath: fileStoragePath, + restore: restore, + storage: storage, + } +} + +func (s *Saver) getAndSaveMetrics() error { + metrics := s.storage.GetAll() + if len(metrics) == 0 { + return nil + } + + if err := saveMetricsToFile(metrics, s.fileStoragePath); err != nil { + logger.GetLogger().Warn("can't save metrics to file", zap.String("fileStoragePath", s.fileStoragePath)) + return err + } + return nil +} + +func (s *Saver) Run() error { + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt, syscall.SIGTERM) + + go func() { + for range c { + if err := s.getAndSaveMetrics(); err != nil { + logger.GetLogger().Error(fmt.Sprintf("can't save metrics on interrupt signal: %v", err)) + } + os.Exit(0) + } + }() + + if s.restore { + metrics, err := loadMetricsFromFile(s.fileStoragePath) + if err != nil { + return fmt.Errorf("cannot load metrics from file: %w", err) + } + fmt.Println("load", metrics) + s.storage.SetAll(metrics) + } + + ticker := time.NewTicker(s.storeInterval) + + errorCount := 0 + for range ticker.C { + err := s.getAndSaveMetrics() + if err != nil { + logger.GetLogger().Error(fmt.Sprintf("can't save metrics on timer tick: %v", err)) + errorCount++ + } + if errorCount > constants.MaxErrors { + return errors.New("too many errors in Saver:Run") + } + } + + if err := s.getAndSaveMetrics(); err != nil { + logger.GetLogger().Error(fmt.Sprintf("can't save metrics when closing Saver: %v", err)) + } + return nil +} + +func saveMetricsToFile(metrics map[string]storage.Metric, filePath string) error { + fmt.Println(metrics) + file, err := os.Create(filePath) + if err != nil { + return fmt.Errorf("failed to create file: %w", err) + } + + defer func() { + closeErr := file.Close() + if closeErr != nil { + logger.GetLogger().Error(fmt.Sprintf(failedCloseFile, closeErr)) + } + }() + + encoder := json.NewEncoder(file) + if err := encoder.Encode(metrics); err != nil { + return fmt.Errorf("failed to encode metrics: %w", err) + } + + return nil +} + +func loadMetricsFromFile(filePath string) (map[string]storage.Metric, error) { + metrics := make(map[string]storage.Metric) + + file, err := os.Open(filePath) + if err != nil { + if os.IsNotExist(err) { + return metrics, nil + } else { + return nil, fmt.Errorf("failed to open file: %w", err) + } + } + + defer func() { + closeErr := file.Close() + if closeErr != nil { + logger.GetLogger().Error(fmt.Sprintf(failedCloseFile, closeErr)) + } + }() + + decoder := json.NewDecoder(file) + if err := decoder.Decode(&metrics); err != nil { + return nil, fmt.Errorf("failed to decode file: %w", err) + } + return metrics, nil +} diff --git a/internal/server/storage/storage.go b/internal/server/storage/storage.go index 1648280..8c7e6fd 100644 --- a/internal/server/storage/storage.go +++ b/internal/server/storage/storage.go @@ -19,6 +19,7 @@ type Storage interface { Update(metricName string, update Metric) error Get(metricName string, metricType string) (Metric, bool) GetAll() map[string]Metric + SetAll(metrics map[string]Metric) } type memStorage struct { @@ -85,3 +86,14 @@ func (ms *memStorage) GetAll() map[string]Metric { }) return result } + +func (ms *memStorage) SetAll(metrics map[string]Metric) { + for key, metric := range metrics { + if metric.Type == "counter" && metric.Value != nil { + if value, ok := metric.Value.(float64); ok { + metric.Value = int64(value) + } + } + ms.Data.Store(key, metric) + } +} From ea237ae034521c7237166cbe629053ad57a5e772 Mon Sep 17 00:00:00 2001 From: "e.tsibereva" Date: Thu, 30 Nov 2023 23:24:50 +0300 Subject: [PATCH 09/12] compressor->middleware, use logger as context logger --- cmd/agent/root/root.go | 4 -- cmd/server/root/root.go | 44 ++-------------- internal/agent/uploader/uploader.go | 6 +-- internal/logger/logger.go | 41 +++++++++------ .../{compressor => middleware}/compressor.go | 2 +- internal/server/saver/saver.go | 15 +++--- internal/server/webserver/webserver.go | 52 +++++++++++++++++++ 7 files changed, 90 insertions(+), 74 deletions(-) rename internal/{compressor => middleware}/compressor.go (95%) create mode 100644 internal/server/webserver/webserver.go diff --git a/cmd/agent/root/root.go b/cmd/agent/root/root.go index eba7764..77e3398 100644 --- a/cmd/agent/root/root.go +++ b/cmd/agent/root/root.go @@ -7,7 +7,6 @@ import ( "github.com/ElizavetaFirst/go-metrics-alerts/internal/agent/collector" "github.com/ElizavetaFirst/go-metrics-alerts/internal/agent/uploader" - "github.com/ElizavetaFirst/go-metrics-alerts/internal/logger" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -24,9 +23,6 @@ var RootCmd = &cobra.Command{ return nil }, RunE: func(cmd *cobra.Command, args []string) error { - logger.Init() - defer logger.Sync() - addr, err := cmd.Flags().GetString("addr") if err != nil { return errors.Wrap(err, "can't get addr flag") diff --git a/cmd/server/root/root.go b/cmd/server/root/root.go index a8dd75c..5e1ede7 100644 --- a/cmd/server/root/root.go +++ b/cmd/server/root/root.go @@ -5,34 +5,18 @@ import ( "os" "strings" - "github.com/ElizavetaFirst/go-metrics-alerts/internal/compressor" - "github.com/ElizavetaFirst/go-metrics-alerts/internal/constants" - "github.com/ElizavetaFirst/go-metrics-alerts/internal/logger" - "github.com/ElizavetaFirst/go-metrics-alerts/internal/server/handler" "github.com/ElizavetaFirst/go-metrics-alerts/internal/server/saver" "github.com/ElizavetaFirst/go-metrics-alerts/internal/server/storage" - "github.com/gin-contrib/gzip" - "github.com/gin-gonic/gin" + "github.com/ElizavetaFirst/go-metrics-alerts/internal/server/webserver" "github.com/pkg/errors" "github.com/spf13/cobra" - "go.uber.org/zap" ) var RootCmd = &cobra.Command{ Use: "app", Short: "This is my application", Long: "This is my application and it's has some long description", - Args: func(cmd *cobra.Command, args []string) error { - if len(args) > 0 { - fmt.Printf("Unknown flags: %s\n", args) - return fmt.Errorf("unknown flags: %s", args) - } - return nil - }, RunE: func(cmd *cobra.Command, args []string) error { - logger.Init() - defer logger.Sync() - addr, err := cmd.Flags().GetString("addr") if err != nil { return errors.Wrap(err, "can't get addr flag") @@ -54,36 +38,18 @@ var RootCmd = &cobra.Command{ return errors.Wrap(err, "can't get restore flag") } - r := gin.Default() - r.Use(compressor.GzipGinRequestMiddleware) - - r.Use(func(c *gin.Context) { - acceptEncoding := c.GetHeader("Accept-Encoding") - if strings.Contains(c.ContentType(), "application/json") || - strings.Contains(c.ContentType(), "text/html") || - strings.Contains(acceptEncoding, constants.Gzip) { - gzip.Gzip(gzip.DefaultCompression)(c) - } - }) - storage := storage.NewMemStorage() + saver := saver.NewSaver(storeInterval, fileStoragePath, restore, storage) - handler := handler.NewHandler(storage) - handler.RegisterRoutes(r) + server := webserver.NewWebserver(storage) - saver := saver.NewSaver(storeInterval, fileStoragePath, restore, storage) go func() { if err := saver.Run(); err != nil { - logger.GetLogger().Error("Error running saver", zap.Error(err)) + fmt.Printf("error while saver Run %v", err) os.Exit(0) } }() - err = r.Run(addr) - if err != nil { - return fmt.Errorf("run addr %s error %w", addr, err) - } - - return nil + return errors.Wrap(server.Run(addr), "error while server Run") }, } diff --git a/internal/agent/uploader/uploader.go b/internal/agent/uploader/uploader.go index 40c8e74..af48a08 100644 --- a/internal/agent/uploader/uploader.go +++ b/internal/agent/uploader/uploader.go @@ -10,10 +10,8 @@ import ( "time" "github.com/ElizavetaFirst/go-metrics-alerts/internal/constants" - "github.com/ElizavetaFirst/go-metrics-alerts/internal/logger" "github.com/ElizavetaFirst/go-metrics-alerts/internal/metrics" "github.com/pkg/errors" - "go.uber.org/zap" ) const ( @@ -59,7 +57,7 @@ func (u *Uploader) Run() { for range ticker.C { for { if err := u.SendGaugeMetricsJSON(u.gaugeMetricsFunc()); err != nil { - logger.GetLogger().Warn("SendGaugeMetricsJson return error", zap.Error(err)) + fmt.Printf("SendGaugeMetricsJson return error %v", err) errorCount++ if errorCount >= constants.MaxErrors { u.errorChan <- err @@ -68,7 +66,7 @@ func (u *Uploader) Run() { continue } if err := u.SendCounterMetricsJSON(u.counterMetricsFunc()); err != nil { - logger.GetLogger().Warn("SendCounterMetricsJson return error", zap.Error(err)) + fmt.Printf("SendCounterMetricsJson return error %v", err) errorCount++ if errorCount >= constants.MaxErrors { u.errorChan <- err diff --git a/internal/logger/logger.go b/internal/logger/logger.go index 25af473..d425445 100644 --- a/internal/logger/logger.go +++ b/internal/logger/logger.go @@ -10,41 +10,49 @@ import ( "go.uber.org/zap" ) -var log *zap.Logger +const logger = "Logger" -func Init() { +func InitLogger() gin.HandlerFunc { + var log *zap.Logger var err error log, err = zap.NewProduction() if err != nil { panic(fmt.Sprintf("can't initialize zap logger: %v", err)) } -} -func Sync() { - if err := log.Sync(); err != nil && (!errors.Is(err, syscall.EBADF) && !errors.Is(err, syscall.ENOTTY)) { - panic(fmt.Sprintf("Can't sync zap logger: %v", err)) + return func(c *gin.Context) { + c.Set(logger, log) + c.Next() + + // Call Sync at the end of every request + if err := log.Sync(); err != nil && (!errors.Is(err, syscall.EBADF) && !errors.Is(err, syscall.ENOTTY)) { + panic(fmt.Sprintf("can't sync zap logger: %v", err)) + } } } -func GetLogger() *zap.Logger { - return log +func GetLogger(c *gin.Context) *zap.Logger { + if v, ok := c.Get(logger); ok { + if log, ok := v.(*zap.Logger); ok { + return log + } + } + return nil } -// LogRequest is a middleware that logs HTTP requests. func LogRequest() gin.HandlerFunc { return func(c *gin.Context) { - if GetLogger() == nil { + log := GetLogger(c) + if log == nil { return } start := time.Now() - // Pass to the next middleware/handler c.Next() - // Calculate request duration duration := time.Since(start) - GetLogger().Info("HTTP Request", + log.Info("HTTP Request", zap.String("method", c.Request.Method), zap.String("url", c.Request.URL.String()), zap.String("duration", duration.String()), @@ -54,14 +62,13 @@ func LogRequest() gin.HandlerFunc { func LogResponse() gin.HandlerFunc { return func(c *gin.Context) { - if GetLogger() == nil { + log := GetLogger(c) + if log == nil { return } - // Call the next middleware or handler c.Next() - // Log the information about the response - GetLogger().Info("HTTP Response", + log.Info("HTTP Response", zap.Int("status", c.Writer.Status()), zap.Int("response_size", c.Writer.Size()), ) diff --git a/internal/compressor/compressor.go b/internal/middleware/compressor.go similarity index 95% rename from internal/compressor/compressor.go rename to internal/middleware/compressor.go index 515aaf4..91ec2fe 100644 --- a/internal/compressor/compressor.go +++ b/internal/middleware/compressor.go @@ -1,4 +1,4 @@ -package compressor +package middleware import ( "compress/gzip" diff --git a/internal/server/saver/saver.go b/internal/server/saver/saver.go index 87527a8..d13c4a2 100644 --- a/internal/server/saver/saver.go +++ b/internal/server/saver/saver.go @@ -10,9 +10,7 @@ import ( "time" "github.com/ElizavetaFirst/go-metrics-alerts/internal/constants" - "github.com/ElizavetaFirst/go-metrics-alerts/internal/logger" "github.com/ElizavetaFirst/go-metrics-alerts/internal/server/storage" - "go.uber.org/zap" ) const failedCloseFile = "Failed to close file: %v" @@ -44,7 +42,7 @@ func (s *Saver) getAndSaveMetrics() error { } if err := saveMetricsToFile(metrics, s.fileStoragePath); err != nil { - logger.GetLogger().Warn("can't save metrics to file", zap.String("fileStoragePath", s.fileStoragePath)) + fmt.Printf("can't save metrics to file %s", s.fileStoragePath) return err } return nil @@ -57,7 +55,7 @@ func (s *Saver) Run() error { go func() { for range c { if err := s.getAndSaveMetrics(); err != nil { - logger.GetLogger().Error(fmt.Sprintf("can't save metrics on interrupt signal: %v", err)) + fmt.Printf("can't save metrics on interrupt signal: %v", err) } os.Exit(0) } @@ -68,7 +66,6 @@ func (s *Saver) Run() error { if err != nil { return fmt.Errorf("cannot load metrics from file: %w", err) } - fmt.Println("load", metrics) s.storage.SetAll(metrics) } @@ -78,7 +75,7 @@ func (s *Saver) Run() error { for range ticker.C { err := s.getAndSaveMetrics() if err != nil { - logger.GetLogger().Error(fmt.Sprintf("can't save metrics on timer tick: %v", err)) + fmt.Printf("can't save metrics on timer tick: %v", err) errorCount++ } if errorCount > constants.MaxErrors { @@ -87,7 +84,7 @@ func (s *Saver) Run() error { } if err := s.getAndSaveMetrics(); err != nil { - logger.GetLogger().Error(fmt.Sprintf("can't save metrics when closing Saver: %v", err)) + fmt.Printf("can't save metrics when closing Saver: %v", err) } return nil } @@ -102,7 +99,7 @@ func saveMetricsToFile(metrics map[string]storage.Metric, filePath string) error defer func() { closeErr := file.Close() if closeErr != nil { - logger.GetLogger().Error(fmt.Sprintf(failedCloseFile, closeErr)) + fmt.Printf(failedCloseFile, closeErr) } }() @@ -129,7 +126,7 @@ func loadMetricsFromFile(filePath string) (map[string]storage.Metric, error) { defer func() { closeErr := file.Close() if closeErr != nil { - logger.GetLogger().Error(fmt.Sprintf(failedCloseFile, closeErr)) + fmt.Printf(failedCloseFile, closeErr) } }() diff --git a/internal/server/webserver/webserver.go b/internal/server/webserver/webserver.go new file mode 100644 index 0000000..ee38556 --- /dev/null +++ b/internal/server/webserver/webserver.go @@ -0,0 +1,52 @@ +package webserver + +import ( + "strings" + + "github.com/ElizavetaFirst/go-metrics-alerts/internal/constants" + "github.com/ElizavetaFirst/go-metrics-alerts/internal/logger" + "github.com/ElizavetaFirst/go-metrics-alerts/internal/middleware" + "github.com/ElizavetaFirst/go-metrics-alerts/internal/server/handler" + "github.com/ElizavetaFirst/go-metrics-alerts/internal/server/storage" + "github.com/gin-contrib/gzip" + "github.com/gin-gonic/gin" + "github.com/pkg/errors" +) + +type Webserver struct { + Router *gin.Engine +} + +func NewWebserver( + storage storage.Storage, +) *Webserver { + router := setupRouter(storage) + + return &Webserver{ + Router: router, + } +} + +func (ws *Webserver) Run(addr string) error { + return errors.Wrap(ws.Router.Run(addr), "error while Webserver Run") +} + +func setupRouter(storage storage.Storage) *gin.Engine { + handler := handler.NewHandler(storage) + + r := gin.Default() + r.Use(logger.InitLogger()) + r.Use(middleware.GzipGinRequestMiddleware) + r.Use(func(c *gin.Context) { + acceptEncoding := c.GetHeader("Accept-Encoding") + if strings.Contains(c.ContentType(), "application/json") || + strings.Contains(c.ContentType(), "text/html") || + strings.Contains(acceptEncoding, constants.Gzip) { + gzip.Gzip(gzip.DefaultCompression)(c) + } + }) + + handler.RegisterRoutes(r) + + return r +} From 29afd67f16348cc7d8a3e51b216ce6489c22776b Mon Sep 17 00:00:00 2001 From: "e.tsibereva" Date: Thu, 30 Nov 2023 23:38:24 +0300 Subject: [PATCH 10/12] remove panic --- internal/logger/logger.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/internal/logger/logger.go b/internal/logger/logger.go index d425445..3e33a4c 100644 --- a/internal/logger/logger.go +++ b/internal/logger/logger.go @@ -17,16 +17,15 @@ func InitLogger() gin.HandlerFunc { var err error log, err = zap.NewProduction() if err != nil { - panic(fmt.Sprintf("can't initialize zap logger: %v", err)) + fmt.Printf("can't initialize zap logger: %v", err) } return func(c *gin.Context) { c.Set(logger, log) c.Next() - // Call Sync at the end of every request if err := log.Sync(); err != nil && (!errors.Is(err, syscall.EBADF) && !errors.Is(err, syscall.ENOTTY)) { - panic(fmt.Sprintf("can't sync zap logger: %v", err)) + fmt.Printf("can't sync zap logger: %v", err) } } } From f353e93256a163f60e7895aaa4b0d6d3614fe8cc Mon Sep 17 00:00:00 2001 From: "e.tsibereva" Date: Sun, 3 Dec 2023 16:50:56 +0300 Subject: [PATCH 11/12] change param for send metrics from metrics to []byte, add middleware for uoloader for client json requests --- internal/agent/uploader/middleware.go | 49 ++++++++++++++++++++ internal/agent/uploader/uploader.go | 65 ++++++++------------------- 2 files changed, 67 insertions(+), 47 deletions(-) create mode 100644 internal/agent/uploader/middleware.go diff --git a/internal/agent/uploader/middleware.go b/internal/agent/uploader/middleware.go new file mode 100644 index 0000000..486a2d3 --- /dev/null +++ b/internal/agent/uploader/middleware.go @@ -0,0 +1,49 @@ +package uploader + +import ( + "compress/gzip" + "fmt" + "io" + "net/http" + + "github.com/ElizavetaFirst/go-metrics-alerts/internal/constants" + "github.com/pkg/errors" +) + +type ClientWithMiddleware struct { + HTTPClient *http.Client +} + +func (c *ClientWithMiddleware) Do(req *http.Request) (*http.Response, error) { + req.Header.Set(contentTypeStr, "application/json") + req.Header.Set("Accept-Encoding", constants.Gzip) + + resp, err := c.HTTPClient.Do(req) + if err != nil { + return nil, err + } + + defer func() { + closeErr := resp.Body.Close() + if closeErr != nil { + closeErr = errors.Wrap(closeErr, "Failed to close the body of the response") + if err == nil { + err = closeErr + } + } + }() + + var reader io.ReadCloser + switch resp.Header.Get("Content-Encoding") { + case constants.Gzip: + reader, err = gzip.NewReader(resp.Body) + if err != nil { + return nil, errors.Wrap(err, "can't create gzip.NewReader") + } + default: + reader = resp.Body + fmt.Println(reader) + } + + return resp, nil +} diff --git a/internal/agent/uploader/uploader.go b/internal/agent/uploader/uploader.go index af48a08..97d629c 100644 --- a/internal/agent/uploader/uploader.go +++ b/internal/agent/uploader/uploader.go @@ -2,10 +2,8 @@ package uploader import ( "bytes" - "compress/gzip" "encoding/json" "fmt" - "io" "net/http" "time" @@ -115,55 +113,20 @@ func (u *Uploader) SendCounterMetrics(metrics map[string]int64) error { return nil } -func (u *Uploader) sendMetricsJSON(url string, metrics metrics.Metrics) error { - client := &http.Client{ - Timeout: time.Second * maxTimeout, +func (u *Uploader) sendMetricsJSON(url string, metrics []byte) error { + client := &ClientWithMiddleware{ + HTTPClient: &http.Client{ + Timeout: time.Second * maxTimeout, + }, } - - metricsJSON, err := json.Marshal(metrics) + req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(metrics)) if err != nil { - return errors.Wrap(err, "can't marshal metrics to JSON") + return errors.Wrap(err, "can't make request") } - - req, _ := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(metricsJSON)) - req.Header.Set(contentTypeStr, "application/json") - req.Header.Set("Accept-Encoding", constants.Gzip) - resp, err := client.Do(req) + _, err = client.Do(req) if err != nil { return errors.Wrap(err, cantSendUpdate) } - defer func() { - closeErr := resp.Body.Close() - if closeErr != nil { - closeErr = errors.Wrap(closeErr, "Failed to close the body of the response") - if err == nil { - err = closeErr - } - } - }() - - var reader io.ReadCloser - defer func() { - closeErr := reader.Close() - if closeErr != nil { - closeErr = errors.Wrap(closeErr, "Failed to close io.ReadCloser") - if err == nil { - err = closeErr - } - } - }() - - switch resp.Header.Get("Content-Encoding") { - case constants.Gzip: - reader, err = gzip.NewReader(resp.Body) - if err != nil { - return errors.Wrap(err, "can't create gzip.NewReader") - } - default: - reader = resp.Body - fmt.Println(reader) - } - return nil } @@ -177,7 +140,11 @@ func (u *Uploader) SendGaugeMetricsJSON(metricsMap map[string]float64) error { } url := fmt.Sprintf(updateReqFormat, u.addr) - if err := u.sendMetricsJSON(url, metric); err != nil { + metricsJSON, err := json.Marshal(metric) + if err != nil { + return errors.Wrap(err, "can't marshal metrics to JSON") + } + if err := u.sendMetricsJSON(url, metricsJSON); err != nil { return err } } @@ -193,8 +160,12 @@ func (u *Uploader) SendCounterMetricsJSON(metricsMap map[string]int64) error { Delta: &v, } + metricsJSON, err := json.Marshal(metric) + if err != nil { + return errors.Wrap(err, "can't marshal metrics to JSON") + } url := fmt.Sprintf(updateReqFormat, u.addr) - if err := u.sendMetricsJSON(url, metric); err != nil { + if err := u.sendMetricsJSON(url, metricsJSON); err != nil { return err } } From bf2910a71dfb0b69fbc9ed3addd3c14777546731 Mon Sep 17 00:00:00 2001 From: "e.tsibereva" Date: Sun, 3 Dec 2023 17:13:55 +0300 Subject: [PATCH 12/12] fix linter --- internal/agent/uploader/middleware.go | 14 ++------------ internal/agent/uploader/uploader.go | 16 +++++++++++++--- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/internal/agent/uploader/middleware.go b/internal/agent/uploader/middleware.go index 486a2d3..7d6ace9 100644 --- a/internal/agent/uploader/middleware.go +++ b/internal/agent/uploader/middleware.go @@ -20,23 +20,13 @@ func (c *ClientWithMiddleware) Do(req *http.Request) (*http.Response, error) { resp, err := c.HTTPClient.Do(req) if err != nil { - return nil, err + return nil, errors.Wrap(err, "can't do client response") } - defer func() { - closeErr := resp.Body.Close() - if closeErr != nil { - closeErr = errors.Wrap(closeErr, "Failed to close the body of the response") - if err == nil { - err = closeErr - } - } - }() - var reader io.ReadCloser switch resp.Header.Get("Content-Encoding") { case constants.Gzip: - reader, err = gzip.NewReader(resp.Body) + _, err = gzip.NewReader(resp.Body) if err != nil { return nil, errors.Wrap(err, "can't create gzip.NewReader") } diff --git a/internal/agent/uploader/uploader.go b/internal/agent/uploader/uploader.go index 97d629c..2609e5b 100644 --- a/internal/agent/uploader/uploader.go +++ b/internal/agent/uploader/uploader.go @@ -18,6 +18,7 @@ const ( maxTimeout = 30 cantSendUpdate = "can't send update request" cantCloseBody = "can't close update request resp.Body" + cantMarshalJSON = "can't marshal metrics to JSON" updateReqFormat = "http://%s/update" ) @@ -123,10 +124,19 @@ func (u *Uploader) sendMetricsJSON(url string, metrics []byte) error { if err != nil { return errors.Wrap(err, "can't make request") } - _, err = client.Do(req) + resp, err := client.Do(req) if err != nil { return errors.Wrap(err, cantSendUpdate) } + defer func() { + closeErr := resp.Body.Close() + if closeErr != nil { + closeErr = errors.Wrap(closeErr, "Failed to close the body of the response") + if err == nil { + err = closeErr + } + } + }() return nil } @@ -142,7 +152,7 @@ func (u *Uploader) SendGaugeMetricsJSON(metricsMap map[string]float64) error { url := fmt.Sprintf(updateReqFormat, u.addr) metricsJSON, err := json.Marshal(metric) if err != nil { - return errors.Wrap(err, "can't marshal metrics to JSON") + return errors.Wrap(err, cantMarshalJSON) } if err := u.sendMetricsJSON(url, metricsJSON); err != nil { return err @@ -162,7 +172,7 @@ func (u *Uploader) SendCounterMetricsJSON(metricsMap map[string]int64) error { metricsJSON, err := json.Marshal(metric) if err != nil { - return errors.Wrap(err, "can't marshal metrics to JSON") + return errors.Wrap(err, cantMarshalJSON) } url := fmt.Sprintf(updateReqFormat, u.addr) if err := u.sendMetricsJSON(url, metricsJSON); err != nil {