From 2d7c6605da06ce66d4ede086c7d2d9851781d117 Mon Sep 17 00:00:00 2001
From: ensiouel <ensiouel@gmail.com>
Date: Sun, 17 Dec 2023 08:43:13 +0300
Subject: [PATCH] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?=
 =?UTF-8?q?=D0=BD=D0=B0=20=D0=BC=D0=B5=D1=82=D1=80=D0=B8=D0=BA=D0=B0=20?=
 =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20=D0=B7=D0=B0=D0=BF=D1=80=D0=BE=D1=81=D0=BE?=
 =?UTF-8?q?=D0=B2=20=D1=81=D0=B5=D1=80=D0=B2=D0=B8=D1=81=D0=BE=D0=B2?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 assets/templates/middleware/metrics        |  39 +++
 go.mod                                     |   1 +
 go.sum                                     |   2 +
 pkg/items/middleware/metrics_middleware.go | 266 +++++++++++++++++++++
 pkg/metrics/request.go                     |  40 ++++
 5 files changed, 348 insertions(+)
 create mode 100644 assets/templates/middleware/metrics
 create mode 100644 pkg/items/middleware/metrics_middleware.go
 create mode 100644 pkg/metrics/request.go

diff --git a/assets/templates/middleware/metrics b/assets/templates/middleware/metrics
new file mode 100644
index 00000000..f55ffd51
--- /dev/null
+++ b/assets/templates/middleware/metrics
@@ -0,0 +1,39 @@
+import (
+    "context"
+
+    "github.com/prometheus/client_golang/prometheus"
+)
+
+{{ $decorator := (or .Vars.DecoratorName "metricsMiddleware") }}
+{{ $funcName := (or .Vars.FuncName ("MetricsMiddleware")) }}
+
+// {{ $decorator }} implements {{ .Interface.Type }} that is instrumented with metrics
+type {{ $decorator }} struct {
+    requestMetrics *metrics.RequestMetrics
+    next {{ .Interface.Type }}
+}
+
+// {{ $funcName }} instruments an implementation of the {{ $decorator }} with metrics
+func {{ $funcName }} (requestMetrics *metrics.RequestMetrics) Middleware {
+    return func(next {{ .Interface.Type }}) {{ .Interface.Type }} {
+        return &{{ $decorator }}{
+            requestMetrics: requestMetrics,
+            next: next,
+        }
+    }
+}
+
+{{ range $method := .Interface.Methods }}
+    // {{ $method.Name }} implements {{ $.Interface.Type }}
+    func (m {{ $decorator }}) {{ $method.Declaration }} {
+        timer := prometheus.NewTimer(m.requestMetrics.DurationSeconds.WithLabelValues("{{ $.Interface.Name }}", "{{ $method.Name }}"))
+        defer func() {
+                timer.ObserveDuration()
+        		m.requestMetrics.Total.WithLabelValues("{{ $.Interface.Name }}", "{{ $method.Name }}").Inc()
+        		if err != nil {
+        			m.requestMetrics.FailedTotal.WithLabelValues("{{ $.Interface.Name }}", "{{ $method.Name }}").Inc()
+        		}
+        	}()
+        {{ $method.Pass "m.next." }}
+    }
+{{ end }}
\ No newline at end of file
diff --git a/go.mod b/go.mod
index 86d2b2e6..c99f9da9 100644
--- a/go.mod
+++ b/go.mod
@@ -48,6 +48,7 @@ require (
 	github.com/nats-io/nkeys v0.4.6 // indirect
 	github.com/nats-io/nuid v1.0.1 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
+	github.com/prometheus/client_golang v1.17.0 // indirect
 	github.com/stretchr/objx v0.5.1 // indirect
 	github.com/xdg-go/pbkdf2 v1.0.0 // indirect
 	github.com/xdg-go/scram v1.1.2 // indirect
diff --git a/go.sum b/go.sum
index d11a9be6..c7cd2871 100644
--- a/go.sum
+++ b/go.sum
@@ -79,6 +79,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
 github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q=
+github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY=
 github.com/rivo/tview v0.0.0-20200219210816-cd38d7432498/go.mod h1:6lkG1x+13OShEf0EaOCaTQYyB7d5nSbb181KtjlS+84=
 github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
diff --git a/pkg/items/middleware/metrics_middleware.go b/pkg/items/middleware/metrics_middleware.go
new file mode 100644
index 00000000..489f39a6
--- /dev/null
+++ b/pkg/items/middleware/metrics_middleware.go
@@ -0,0 +1,266 @@
+// Code generated by gowrap. DO NOT EDIT.
+// template: ../../assets/templates/middleware/metrics
+// gowrap: http://github.com/hexdigest/gowrap
+
+package middleware
+
+//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/items -i Items -t ../../assets/templates/middleware/metrics -o metrics_middleware.go -l ""
+
+import (
+	"context"
+
+	"git.perx.ru/perxis/perxis-go/pkg/items"
+	"git.perx.ru/perxis/perxis-go/pkg/metrics"
+	"git.perx.ru/perxis/perxis-go/pkg/schema"
+	"github.com/prometheus/client_golang/prometheus"
+)
+
+// metricsMiddleware implements items.Items that is instrumented with metrics
+type metricsMiddleware struct {
+	requestMetrics *metrics.RequestMetrics
+	next           items.Items
+}
+
+// MetricsMiddleware instruments an implementation of the items.Items with metrics
+func MetricsMiddleware(requestMetrics *metrics.RequestMetrics) Middleware {
+	return func(next items.Items) items.Items {
+		return &metricsMiddleware{
+			requestMetrics: requestMetrics,
+			next:           next,
+		}
+	}
+}
+
+// Aggregate implements Items
+func (m metricsMiddleware) Aggregate(ctx context.Context, spaceId string, envId string, collectionId string, filter *items.Filter, options ...*items.AggregateOptions) (result map[string]interface{}, err error) {
+	timer := prometheus.NewTimer(m.requestMetrics.DurationSeconds.WithLabelValues("Items", "Aggregate"))
+	defer func() {
+		timer.ObserveDuration()
+		m.requestMetrics.Total.WithLabelValues("Items", "Aggregate").Inc()
+		if err != nil {
+			m.requestMetrics.FailedTotal.WithLabelValues("Items", "Aggregate").Inc()
+		}
+	}()
+	return m.next.Aggregate(ctx, spaceId, envId, collectionId, filter, options...)
+}
+
+// AggregatePublished implements Items
+func (m metricsMiddleware) AggregatePublished(ctx context.Context, spaceId string, envId string, collectionId string, filter *items.Filter, options ...*items.AggregatePublishedOptions) (result map[string]interface{}, err error) {
+	timer := prometheus.NewTimer(m.requestMetrics.DurationSeconds.WithLabelValues("Items", "AggregatePublished"))
+	defer func() {
+		timer.ObserveDuration()
+		m.requestMetrics.Total.WithLabelValues("Items", "AggregatePublished").Inc()
+		if err != nil {
+			m.requestMetrics.FailedTotal.WithLabelValues("Items", "AggregatePublished").Inc()
+		}
+	}()
+	return m.next.AggregatePublished(ctx, spaceId, envId, collectionId, filter, options...)
+}
+
+// Archive implements Items
+func (m metricsMiddleware) Archive(ctx context.Context, item *items.Item, options ...*items.ArchiveOptions) (err error) {
+	timer := prometheus.NewTimer(m.requestMetrics.DurationSeconds.WithLabelValues("Items", "Archive"))
+	defer func() {
+		timer.ObserveDuration()
+		m.requestMetrics.Total.WithLabelValues("Items", "Archive").Inc()
+		if err != nil {
+			m.requestMetrics.FailedTotal.WithLabelValues("Items", "Archive").Inc()
+		}
+	}()
+	return m.next.Archive(ctx, item, options...)
+}
+
+// Create implements Items
+func (m metricsMiddleware) Create(ctx context.Context, item *items.Item, opts ...*items.CreateOptions) (created *items.Item, err error) {
+	timer := prometheus.NewTimer(m.requestMetrics.DurationSeconds.WithLabelValues("Items", "Create"))
+	defer func() {
+		timer.ObserveDuration()
+		m.requestMetrics.Total.WithLabelValues("Items", "Create").Inc()
+		if err != nil {
+			m.requestMetrics.FailedTotal.WithLabelValues("Items", "Create").Inc()
+		}
+	}()
+	return m.next.Create(ctx, item, opts...)
+}
+
+// Delete implements Items
+func (m metricsMiddleware) Delete(ctx context.Context, item *items.Item, options ...*items.DeleteOptions) (err error) {
+	timer := prometheus.NewTimer(m.requestMetrics.DurationSeconds.WithLabelValues("Items", "Delete"))
+	defer func() {
+		timer.ObserveDuration()
+		m.requestMetrics.Total.WithLabelValues("Items", "Delete").Inc()
+		if err != nil {
+			m.requestMetrics.FailedTotal.WithLabelValues("Items", "Delete").Inc()
+		}
+	}()
+	return m.next.Delete(ctx, item, options...)
+}
+
+// Find implements Items
+func (m metricsMiddleware) Find(ctx context.Context, spaceId string, envId string, collectionId string, filter *items.Filter, options ...*items.FindOptions) (items []*items.Item, total int, err error) {
+	timer := prometheus.NewTimer(m.requestMetrics.DurationSeconds.WithLabelValues("Items", "Find"))
+	defer func() {
+		timer.ObserveDuration()
+		m.requestMetrics.Total.WithLabelValues("Items", "Find").Inc()
+		if err != nil {
+			m.requestMetrics.FailedTotal.WithLabelValues("Items", "Find").Inc()
+		}
+	}()
+	return m.next.Find(ctx, spaceId, envId, collectionId, filter, options...)
+}
+
+// FindArchived implements Items
+func (m metricsMiddleware) FindArchived(ctx context.Context, spaceId string, envId string, collectionId string, filter *items.Filter, options ...*items.FindArchivedOptions) (items []*items.Item, total int, err error) {
+	timer := prometheus.NewTimer(m.requestMetrics.DurationSeconds.WithLabelValues("Items", "FindArchived"))
+	defer func() {
+		timer.ObserveDuration()
+		m.requestMetrics.Total.WithLabelValues("Items", "FindArchived").Inc()
+		if err != nil {
+			m.requestMetrics.FailedTotal.WithLabelValues("Items", "FindArchived").Inc()
+		}
+	}()
+	return m.next.FindArchived(ctx, spaceId, envId, collectionId, filter, options...)
+}
+
+// FindPublished implements Items
+func (m metricsMiddleware) FindPublished(ctx context.Context, spaceId string, envId string, collectionId string, filter *items.Filter, options ...*items.FindPublishedOptions) (items []*items.Item, total int, err error) {
+	timer := prometheus.NewTimer(m.requestMetrics.DurationSeconds.WithLabelValues("Items", "FindPublished"))
+	defer func() {
+		timer.ObserveDuration()
+		m.requestMetrics.Total.WithLabelValues("Items", "FindPublished").Inc()
+		if err != nil {
+			m.requestMetrics.FailedTotal.WithLabelValues("Items", "FindPublished").Inc()
+		}
+	}()
+	return m.next.FindPublished(ctx, spaceId, envId, collectionId, filter, options...)
+}
+
+// Get implements Items
+func (m metricsMiddleware) Get(ctx context.Context, spaceId string, envId string, collectionId string, itemId string, options ...*items.GetOptions) (item *items.Item, err error) {
+	timer := prometheus.NewTimer(m.requestMetrics.DurationSeconds.WithLabelValues("Items", "Get"))
+	defer func() {
+		timer.ObserveDuration()
+		m.requestMetrics.Total.WithLabelValues("Items", "Get").Inc()
+		if err != nil {
+			m.requestMetrics.FailedTotal.WithLabelValues("Items", "Get").Inc()
+		}
+	}()
+	return m.next.Get(ctx, spaceId, envId, collectionId, itemId, options...)
+}
+
+// GetPublished implements Items
+func (m metricsMiddleware) GetPublished(ctx context.Context, spaceId string, envId string, collectionId string, itemId string, options ...*items.GetPublishedOptions) (item *items.Item, err error) {
+	timer := prometheus.NewTimer(m.requestMetrics.DurationSeconds.WithLabelValues("Items", "GetPublished"))
+	defer func() {
+		timer.ObserveDuration()
+		m.requestMetrics.Total.WithLabelValues("Items", "GetPublished").Inc()
+		if err != nil {
+			m.requestMetrics.FailedTotal.WithLabelValues("Items", "GetPublished").Inc()
+		}
+	}()
+	return m.next.GetPublished(ctx, spaceId, envId, collectionId, itemId, options...)
+}
+
+// GetRevision implements Items
+func (m metricsMiddleware) GetRevision(ctx context.Context, spaceId string, envId string, collectionId string, itemId string, revisionId string, options ...*items.GetRevisionOptions) (item *items.Item, err error) {
+	timer := prometheus.NewTimer(m.requestMetrics.DurationSeconds.WithLabelValues("Items", "GetRevision"))
+	defer func() {
+		timer.ObserveDuration()
+		m.requestMetrics.Total.WithLabelValues("Items", "GetRevision").Inc()
+		if err != nil {
+			m.requestMetrics.FailedTotal.WithLabelValues("Items", "GetRevision").Inc()
+		}
+	}()
+	return m.next.GetRevision(ctx, spaceId, envId, collectionId, itemId, revisionId, options...)
+}
+
+// Introspect implements Items
+func (m metricsMiddleware) Introspect(ctx context.Context, item *items.Item, opts ...*items.IntrospectOptions) (itm *items.Item, sch *schema.Schema, err error) {
+	timer := prometheus.NewTimer(m.requestMetrics.DurationSeconds.WithLabelValues("Items", "Introspect"))
+	defer func() {
+		timer.ObserveDuration()
+		m.requestMetrics.Total.WithLabelValues("Items", "Introspect").Inc()
+		if err != nil {
+			m.requestMetrics.FailedTotal.WithLabelValues("Items", "Introspect").Inc()
+		}
+	}()
+	return m.next.Introspect(ctx, item, opts...)
+}
+
+// ListRevisions implements Items
+func (m metricsMiddleware) ListRevisions(ctx context.Context, spaceId string, envId string, collectionId string, itemId string, options ...*items.ListRevisionsOptions) (items []*items.Item, err error) {
+	timer := prometheus.NewTimer(m.requestMetrics.DurationSeconds.WithLabelValues("Items", "ListRevisions"))
+	defer func() {
+		timer.ObserveDuration()
+		m.requestMetrics.Total.WithLabelValues("Items", "ListRevisions").Inc()
+		if err != nil {
+			m.requestMetrics.FailedTotal.WithLabelValues("Items", "ListRevisions").Inc()
+		}
+	}()
+	return m.next.ListRevisions(ctx, spaceId, envId, collectionId, itemId, options...)
+}
+
+// Publish implements Items
+func (m metricsMiddleware) Publish(ctx context.Context, item *items.Item, options ...*items.PublishOptions) (err error) {
+	timer := prometheus.NewTimer(m.requestMetrics.DurationSeconds.WithLabelValues("Items", "Publish"))
+	defer func() {
+		timer.ObserveDuration()
+		m.requestMetrics.Total.WithLabelValues("Items", "Publish").Inc()
+		if err != nil {
+			m.requestMetrics.FailedTotal.WithLabelValues("Items", "Publish").Inc()
+		}
+	}()
+	return m.next.Publish(ctx, item, options...)
+}
+
+// Unarchive implements Items
+func (m metricsMiddleware) Unarchive(ctx context.Context, item *items.Item, options ...*items.UnarchiveOptions) (err error) {
+	timer := prometheus.NewTimer(m.requestMetrics.DurationSeconds.WithLabelValues("Items", "Unarchive"))
+	defer func() {
+		timer.ObserveDuration()
+		m.requestMetrics.Total.WithLabelValues("Items", "Unarchive").Inc()
+		if err != nil {
+			m.requestMetrics.FailedTotal.WithLabelValues("Items", "Unarchive").Inc()
+		}
+	}()
+	return m.next.Unarchive(ctx, item, options...)
+}
+
+// Undelete implements Items
+func (m metricsMiddleware) Undelete(ctx context.Context, item *items.Item, options ...*items.UndeleteOptions) (err error) {
+	timer := prometheus.NewTimer(m.requestMetrics.DurationSeconds.WithLabelValues("Items", "Undelete"))
+	defer func() {
+		timer.ObserveDuration()
+		m.requestMetrics.Total.WithLabelValues("Items", "Undelete").Inc()
+		if err != nil {
+			m.requestMetrics.FailedTotal.WithLabelValues("Items", "Undelete").Inc()
+		}
+	}()
+	return m.next.Undelete(ctx, item, options...)
+}
+
+// Unpublish implements Items
+func (m metricsMiddleware) Unpublish(ctx context.Context, item *items.Item, options ...*items.UnpublishOptions) (err error) {
+	timer := prometheus.NewTimer(m.requestMetrics.DurationSeconds.WithLabelValues("Items", "Unpublish"))
+	defer func() {
+		timer.ObserveDuration()
+		m.requestMetrics.Total.WithLabelValues("Items", "Unpublish").Inc()
+		if err != nil {
+			m.requestMetrics.FailedTotal.WithLabelValues("Items", "Unpublish").Inc()
+		}
+	}()
+	return m.next.Unpublish(ctx, item, options...)
+}
+
+// Update implements Items
+func (m metricsMiddleware) Update(ctx context.Context, item *items.Item, options ...*items.UpdateOptions) (err error) {
+	timer := prometheus.NewTimer(m.requestMetrics.DurationSeconds.WithLabelValues("Items", "Update"))
+	defer func() {
+		timer.ObserveDuration()
+		m.requestMetrics.Total.WithLabelValues("Items", "Update").Inc()
+		if err != nil {
+			m.requestMetrics.FailedTotal.WithLabelValues("Items", "Update").Inc()
+		}
+	}()
+	return m.next.Update(ctx, item, options...)
+}
diff --git a/pkg/metrics/request.go b/pkg/metrics/request.go
new file mode 100644
index 00000000..4b7ac85c
--- /dev/null
+++ b/pkg/metrics/request.go
@@ -0,0 +1,40 @@
+package metrics
+
+import (
+	"github.com/prometheus/client_golang/prometheus"
+	"github.com/prometheus/client_golang/prometheus/promauto"
+)
+
+type RequestMetrics struct {
+	Total           *prometheus.CounterVec
+	FailedTotal     *prometheus.CounterVec
+	DurationSeconds *prometheus.HistogramVec
+}
+
+// NewRequestMetrics
+//
+// # Example metrics
+//
+// svc_request_duration_seconds_bucket{service="Collections",method="Get",le="+Inf"} 2
+//
+// svc_request_duration_seconds_sum{service="Collections",method="Get"} 0.711158607
+//
+// svc_request_duration_seconds_count{service="Collections",method="Get"} 2
+//
+// svc_requests_failed_total{service="Collections",method="Get",error="not found"} 1
+//
+// svc_requests_total{service="Collections",method="Get"} 2
+func NewRequestMetrics(registry prometheus.Registerer, buckets []float64) *RequestMetrics {
+	return &RequestMetrics{
+		Total: promauto.With(registry).NewCounterVec(prometheus.CounterOpts{
+			Name: "svc_requests_total",
+		}, []string{"service", "method"}),
+		FailedTotal: promauto.With(registry).NewCounterVec(prometheus.CounterOpts{
+			Name: "svc_requests_failed_total",
+		}, []string{"service", "method", "error"}),
+		DurationSeconds: promauto.With(registry).NewHistogramVec(prometheus.HistogramOpts{
+			Name:    "svc_request_duration_seconds",
+			Buckets: buckets,
+		}, []string{"service", "method"}),
+	}
+}
-- 
GitLab