diff --git a/assets/templates/middleware/metrics b/assets/templates/middleware/metrics
new file mode 100644
index 0000000000000000000000000000000000000000..f55ffd51fa92201139b77a45c897813a4f975cc0
--- /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 86d2b2e66603975e24e4f78b4650a3100b17ece5..c99f9da977227600eb9af434e6c73c86487b5f49 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 d11a9be6ed599fe2754ece384aef33f07f3ec19d..c7cd2871b25f08cdb5cc33c6686bad148881bfbc 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 0000000000000000000000000000000000000000..489f39a66166c5db9da09c4fa2554aa6a4a1fc5d
--- /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 0000000000000000000000000000000000000000..4b7ac85c380596db3f1ec412d98676fb677be8ec
--- /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"}),
+	}
+}