From 70ee05ec7690c446a64dddf2d98ba11e47a2866f Mon Sep 17 00:00:00 2001 From: Shashank Shekhar Date: Tue, 17 Oct 2023 15:17:05 +0530 Subject: [PATCH] TP-44002 | Created metric middleware, publishing API metrics. Added support for Custom metrics. (#233) --- cmd/app/server.go | 35 +++++++++++------- cmd/main.go | 2 ++ common/util/json_util.go | 2 +- go.mod | 2 +- internal/metrics/metric_publisher.go | 51 +++++++++++++++++++++++++++ internal/metrics/metrics.go | 8 ++--- log/log.go | 46 ++++++++++++++++++++++++ model/ingester/performance_metrics.go | 24 +++++++++++++ service/readiness_service.go | 18 +++++----- 9 files changed, 161 insertions(+), 27 deletions(-) create mode 100644 internal/metrics/metric_publisher.go create mode 100644 log/log.go create mode 100644 model/ingester/performance_metrics.go diff --git a/cmd/app/server.go b/cmd/app/server.go index aa96bbb..f2807a0 100644 --- a/cmd/app/server.go +++ b/cmd/app/server.go @@ -9,10 +9,10 @@ import ( "houston/cmd/app/handler" "houston/internal/clients" "houston/internal/metrics" + "houston/model/ingester" "houston/pkg/slackbot" "houston/service" "net/http" - "strconv" "strings" "time" ) @@ -35,6 +35,7 @@ func NewServer(gin *gin.Engine, logger *zap.Logger, db *gorm.DB, mjolnirClient * func (s *Server) Handler(houstonGroup *gin.RouterGroup) { s.readinessHandler(houstonGroup) + s.gin.Use(s.metricMiddleware()) s.incidentClientHandler(houstonGroup) s.incidentClientHandlerV2(houstonGroup) s.filtersHandlerV2(houstonGroup) @@ -184,6 +185,27 @@ func (s *Server) Start() { s.gin.Run(fmt.Sprintf(":%v", "8080")) } +func (s *Server) metricMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + startTime := time.Now() + c.Next() + endTime := time.Now() + duration := endTime.Sub(startTime) + + metricsPublisher := metrics.NewMetricPublisher() + apiMetrics := ingester.ApiMetric{ + Url: c.Request.URL.Path, + Method: c.Request.Method, + ResponseCode: c.Writer.Status(), + StartTime: startTime.Unix(), + EndTime: endTime.Unix(), + DurationInMs: duration.Milliseconds(), + BytesSent: c.Writer.Size(), + } + metricsPublisher.PublishMetrics(ingester.MetricAttributes{ApiMetric: apiMetrics}, ingester.API_METRICS) + } +} + func (s *Server) createMiddleware() gin.HandlerFunc { return func(c *gin.Context) { @@ -213,17 +235,6 @@ func (s *Server) createMiddleware() gin.HandlerFunc { c.AbortWithStatus(http.StatusOK) return } - - startTime := time.Now() - c.Next() - endTime := float64(time.Since(startTime)) - - //metrics publishing - status := strconv.Itoa(c.Writer.Status()) - metrics.HoustonApiRequestCounter.WithLabelValues(c.Request.URL.Path, c.Request.Method, status).Inc() - metrics.HoustonApiRequestLatencySum.WithLabelValues(c.Request.URL.Path, c.Request.Method, status).Add(endTime) - metrics.HoustonApiRequestLatencyHistogram.WithLabelValues(c.Request.URL.Path, c.Request.Method, status).Observe(endTime) - metrics.HoustonApiRequestLatencySummary.WithLabelValues(c.Request.URL.Path, c.Request.Method, status).Observe(endTime) } } diff --git a/cmd/main.go b/cmd/main.go index 3aeb09b..9430e57 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -4,6 +4,7 @@ import ( "houston/cmd/app" "houston/config" "houston/internal/clients" + "houston/log" "houston/pkg/postgres" "os" "time" @@ -18,6 +19,7 @@ import ( ) func main() { + log.InitLogger() logger, _ := zap.NewProduction() config.LoadHoustonConfig(logger) godotenv.Load() diff --git a/common/util/json_util.go b/common/util/json_util.go index 735226b..fb57ae3 100644 --- a/common/util/json_util.go +++ b/common/util/json_util.go @@ -4,7 +4,7 @@ import ( "encoding/json" ) -func JsonToStruct[JsonObject any, Struct any](obj JsonObject, result *Struct) error { +func Convert[T any, V any](obj T, result *V) error { messageBytes, err := json.Marshal(obj) if err != nil { return err diff --git a/go.mod b/go.mod index 9ca9764..24b63b3 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/gin-contrib/zap v0.1.0 github.com/gin-gonic/gin v1.9.1 github.com/google/uuid v1.3.0 + github.com/jackc/pgx/v5 v5.3.1 github.com/joho/godotenv v1.5.1 github.com/lib/pq v1.10.7 github.com/slack-go/slack v0.12.1 @@ -80,7 +81,6 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect - github.com/jackc/pgx/v5 v5.3.1 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect diff --git a/internal/metrics/metric_publisher.go b/internal/metrics/metric_publisher.go new file mode 100644 index 0000000..685ba6a --- /dev/null +++ b/internal/metrics/metric_publisher.go @@ -0,0 +1,51 @@ +package metrics + +import ( + "go.uber.org/zap" + "houston/log" + "houston/model/ingester" + "strconv" +) + +type Publisher interface { + PublishMetrics(metricAttributes map[string]interface{}, metricType ingester.MetricType) +} + +type PublisherImpl struct { +} + +func NewMetricPublisher() *PublisherImpl { + return &PublisherImpl{} +} + +func (amp *PublisherImpl) PublishMetrics(metricAttributes ingester.MetricAttributes, metricType ingester.MetricType) { + switch metricType { + case ingester.API_METRICS: + { + if err := publishApiMetric(metricAttributes.ApiMetric); err != nil { + log.Error("error while publishing api metricAttributes", zap.Error(err)) + } + return + } + default: + { + return + } + } +} + +func publishApiMetric(apiMetrics ingester.ApiMetric) (err error) { + defer func() { + if r := recover(); r != nil { + err = r.(error) + } + }() + status := strconv.Itoa(apiMetrics.ResponseCode) + duration := float64(apiMetrics.DurationInMs) + ApiRequestCounter.WithLabelValues(apiMetrics.Url, apiMetrics.Method, status) + ApiRequestCounter.WithLabelValues(apiMetrics.Url, apiMetrics.Method, status).Inc() + ApiRequestLatencySum.WithLabelValues(apiMetrics.Url, apiMetrics.Method, status).Add(duration) + ApiRequestLatencyHistogram.WithLabelValues(apiMetrics.Url, apiMetrics.Method, status).Observe(duration) + ApiRequestLatencySummary.WithLabelValues(apiMetrics.Url, apiMetrics.Method, status).Observe(duration) + return +} diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go index 570d883..4845011 100644 --- a/internal/metrics/metrics.go +++ b/internal/metrics/metrics.go @@ -5,7 +5,7 @@ import ( "github.com/prometheus/client_golang/prometheus/promauto" ) -var HoustonApiRequestCounter = promauto.NewCounterVec( +var ApiRequestCounter = promauto.NewCounterVec( prometheus.CounterOpts{ Name: "houston_api_request_total", Help: "api request counter", @@ -13,7 +13,7 @@ var HoustonApiRequestCounter = promauto.NewCounterVec( []string{"url", "method", "response_code"}, ) -var HoustonApiRequestLatencySum = promauto.NewCounterVec( +var ApiRequestLatencySum = promauto.NewCounterVec( prometheus.CounterOpts{ Name: "houston_api_request_latency_sum", Help: "api request latency sum", @@ -21,7 +21,7 @@ var HoustonApiRequestLatencySum = promauto.NewCounterVec( []string{"url", "method", "response_code"}, ) -var HoustonApiRequestLatencyHistogram = promauto.NewHistogramVec( +var ApiRequestLatencyHistogram = promauto.NewHistogramVec( prometheus.HistogramOpts{ Name: "houston_api_request_latency_histogram", Help: "api latency histogram", @@ -29,7 +29,7 @@ var HoustonApiRequestLatencyHistogram = promauto.NewHistogramVec( []string{"url", "method", "response_code"}, ) -var HoustonApiRequestLatencySummary = promauto.NewSummaryVec( +var ApiRequestLatencySummary = promauto.NewSummaryVec( prometheus.SummaryOpts{ Name: "houston_api_request_latency_summary", Help: "api latency summary", diff --git a/log/log.go b/log/log.go new file mode 100644 index 0000000..68e6142 --- /dev/null +++ b/log/log.go @@ -0,0 +1,46 @@ +package log + +import ( + "go.uber.org/zap" +) + +type Logger struct { + log *zap.Logger +} + +var Log *Logger + +func InitLogger() { + log, err := zap.NewProduction() + if err != nil { + panic(err) + } + + Log = &Logger{ + log: log, + } +} + +func GetLogger() *zap.Logger { + return Log.log +} + +func Error(message string, fields ...zap.Field) { + GetLogger().Error(message, fields...) +} + +func Warn(message string, fields ...zap.Field) { + GetLogger().Warn(message, fields...) +} + +func Info(message string, fields ...zap.Field) { + GetLogger().Info(message, fields...) +} + +func Fatal(message string, fields ...zap.Field) { + GetLogger().Fatal(message, fields...) +} + +func Panic(message string, fields ...zap.Field) { + GetLogger().Panic(message, fields...) +} diff --git a/model/ingester/performance_metrics.go b/model/ingester/performance_metrics.go new file mode 100644 index 0000000..6ef2e42 --- /dev/null +++ b/model/ingester/performance_metrics.go @@ -0,0 +1,24 @@ +package ingester + +type MetricType string + +const ( + API_METRICS MetricType = "API_METRICS" +) + +type ApiMetric struct { + Url string `json:"url,omitempty"` + Method string `json:"method,omitempty"` + ResponseCode int `json:"response_code,omitempty"` + BytesSent int `json:"bytes_sent,omitempty"` + BytesReceived int64 `json:"bytes_received,omitempty"` + StartTime int64 `json:"start_time,omitempty"` + EndTime int64 `json:"end_time,omitempty"` + DurationInMs int64 `json:"duration_in_ms,omitempty"` + ErrorType string `json:"error_type,omitempty"` + ErrorMessage string `json:"error_message,omitempty"` +} + +type MetricAttributes struct { + ApiMetric +} diff --git a/service/readiness_service.go b/service/readiness_service.go index 395d957..7c7eef1 100644 --- a/service/readiness_service.go +++ b/service/readiness_service.go @@ -6,16 +6,16 @@ import ( "github.com/gin-gonic/gin" ) -type readinessService struct { - gin *gin.Engine +type ReadinessService struct { + gin *gin.Engine } -func NewReadinessService(gin *gin.Engine) *readinessService { - return &readinessService{ - gin: gin, - } +func NewReadinessService(gin *gin.Engine) *ReadinessService { + return &ReadinessService{ + gin: gin, + } } -func (i *readinessService) Ping(c *gin.Context) { - c.JSON(http.StatusOK, "pong") -} \ No newline at end of file +func (i *ReadinessService) Ping(c *gin.Context) { + c.JSON(http.StatusOK, "pong") +}