diff --git a/log/middleware/error_logging_middleware.go b/log/middleware/error_logging_middleware.go
new file mode 100644
index 0000000000000000000000000000000000000000..de13dcc4fa1d3f8edde99896cdfc24fe1a728128
--- /dev/null
+++ b/log/middleware/error_logging_middleware.go
@@ -0,0 +1,61 @@
+// Code generated by gowrap. DO NOT EDIT.
+// template: ../../assets/templates/middleware/error_log
+// gowrap: http://github.com/hexdigest/gowrap
+
+package middleware
+
+//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/log -i Service -t ../../assets/templates/middleware/error_log -o error_logging_middleware.go -l ""
+
+import (
+	"context"
+
+	"git.perx.ru/perxis/perxis-go/log"
+	"git.perx.ru/perxis/perxis-go/pkg/options"
+	"go.uber.org/zap"
+)
+
+// errorLoggingMiddleware implements log.Service that is instrumented with logging
+type errorLoggingMiddleware struct {
+	logger *zap.Logger
+	next   log.Service
+}
+
+// ErrorLoggingMiddleware instruments an implementation of the log.Service with simple logging
+func ErrorLoggingMiddleware(logger *zap.Logger) Middleware {
+	return func(next log.Service) log.Service {
+		return &errorLoggingMiddleware{
+			next:   next,
+			logger: logger,
+		}
+	}
+}
+
+func (m *errorLoggingMiddleware) Delete(ctx context.Context, filter *log.Filter) (err error) {
+	logger := m.logger
+	defer func() {
+		if err != nil {
+			logger.Warn("response error", zap.Error(err))
+		}
+	}()
+	return m.next.Delete(ctx, filter)
+}
+
+func (m *errorLoggingMiddleware) Find(ctx context.Context, filter *log.Filter, options *options.FindOptions) (fp1 *log.FindResult, err error) {
+	logger := m.logger
+	defer func() {
+		if err != nil {
+			logger.Warn("response error", zap.Error(err))
+		}
+	}()
+	return m.next.Find(ctx, filter, options)
+}
+
+func (m *errorLoggingMiddleware) Log(ctx context.Context, entries []*log.Entry) (err error) {
+	logger := m.logger
+	defer func() {
+		if err != nil {
+			logger.Warn("response error", zap.Error(err))
+		}
+	}()
+	return m.next.Log(ctx, entries)
+}
diff --git a/log/middleware/logging_middleware.go b/log/middleware/logging_middleware.go
new file mode 100644
index 0000000000000000000000000000000000000000..f693795ba6b45bc915212f968c6347a0aa7aed61
--- /dev/null
+++ b/log/middleware/logging_middleware.go
@@ -0,0 +1,145 @@
+// Code generated by gowrap. DO NOT EDIT.
+// template: ../../assets/templates/middleware/access_log
+// gowrap: http://github.com/hexdigest/gowrap
+
+package middleware
+
+//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/log -i Service -t ../../assets/templates/middleware/access_log -o logging_middleware.go -l ""
+
+import (
+	"context"
+	"fmt"
+	"time"
+
+	"git.perx.ru/perxis/perxis-go/log"
+	"git.perx.ru/perxis/perxis-go/pkg/auth"
+	"git.perx.ru/perxis/perxis-go/pkg/options"
+	"go.uber.org/zap"
+	"go.uber.org/zap/zapcore"
+)
+
+// loggingMiddleware implements log.Service that is instrumented with logging
+type loggingMiddleware struct {
+	logger *zap.Logger
+	next   log.Service
+}
+
+// LoggingMiddleware instruments an implementation of the log.Service with simple logging
+func LoggingMiddleware(logger *zap.Logger) Middleware {
+	return func(next log.Service) log.Service {
+		return &loggingMiddleware{
+			next:   next,
+			logger: logger,
+		}
+	}
+}
+
+func (m *loggingMiddleware) Delete(ctx context.Context, filter *log.Filter) (err error) {
+	begin := time.Now()
+	var fields []zapcore.Field
+	for k, v := range map[string]interface{}{
+		"ctx":    ctx,
+		"filter": filter} {
+		if k == "ctx" {
+			fields = append(fields, zap.String("principal", fmt.Sprint(auth.GetPrincipal(ctx))))
+			continue
+		}
+		fields = append(fields, zap.Reflect(k, v))
+	}
+
+	m.logger.Debug("Delete.Request", fields...)
+
+	err = m.next.Delete(ctx, filter)
+
+	fields = []zapcore.Field{
+		zap.Duration("time", time.Since(begin)),
+	}
+
+	for k, v := range map[string]interface{}{
+		"err": err} {
+		if k == "err" {
+			err, _ := v.(error)
+			fields = append(fields, zap.Error(err))
+			continue
+		}
+		fields = append(fields, zap.Reflect(k, v))
+	}
+
+	m.logger.Debug("Delete.Response", fields...)
+
+	return err
+}
+
+func (m *loggingMiddleware) Find(ctx context.Context, filter *log.Filter, options *options.FindOptions) (fp1 *log.FindResult, err error) {
+	begin := time.Now()
+	var fields []zapcore.Field
+	for k, v := range map[string]interface{}{
+		"ctx":     ctx,
+		"filter":  filter,
+		"options": options} {
+		if k == "ctx" {
+			fields = append(fields, zap.String("principal", fmt.Sprint(auth.GetPrincipal(ctx))))
+			continue
+		}
+		fields = append(fields, zap.Reflect(k, v))
+	}
+
+	m.logger.Debug("Find.Request", fields...)
+
+	fp1, err = m.next.Find(ctx, filter, options)
+
+	fields = []zapcore.Field{
+		zap.Duration("time", time.Since(begin)),
+	}
+
+	for k, v := range map[string]interface{}{
+		"fp1": fp1,
+		"err": err} {
+		if k == "err" {
+			err, _ := v.(error)
+			fields = append(fields, zap.Error(err))
+			continue
+		}
+		fields = append(fields, zap.Reflect(k, v))
+	}
+
+	m.logger.Debug("Find.Response", fields...)
+
+	return fp1, err
+}
+
+func (m *loggingMiddleware) Log(ctx context.Context, entries []*log.Entry) (err error) {
+	begin := time.Now()
+	var fields []zapcore.Field
+	for k, v := range map[string]interface{}{
+		"ctx":     ctx,
+		"entries": entries} {
+		if k == "ctx" {
+			fields = append(fields, zap.String("principal", fmt.Sprint(auth.GetPrincipal(ctx))))
+			continue
+		}
+		fields = append(fields, zap.Reflect(k, v))
+	}
+
+	m.logger.Debug("Log.Request", fields...)
+
+	err = m.next.Log(ctx, entries)
+
+	fields = []zapcore.Field{
+		zap.Duration("time", time.Since(begin)),
+	}
+
+	for k, v := range map[string]interface{}{
+		"err": err} {
+		if k == "err" {
+			err, _ := v.(error)
+			fields = append(fields, zap.Error(err))
+			continue
+		}
+		fields = append(fields, zap.Reflect(k, v))
+	}
+
+	m.logger.Debug("Log.Response", fields...)
+
+	return err
+}
diff --git a/log/middleware/middleware.go b/log/middleware/middleware.go
new file mode 100644
index 0000000000000000000000000000000000000000..2fa83f77f251603392126319e61e27158b0dc3cc
--- /dev/null
+++ b/log/middleware/middleware.go
@@ -0,0 +1,28 @@
+// Code generated by gowrap. DO NOT EDIT.
+// template: ../../assets/templates/middleware/middleware
+// gowrap: http://github.com/hexdigest/gowrap
+
+package middleware
+
+//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/log -i Service -t ../../assets/templates/middleware/middleware -o middleware.go -l ""
+
+import (
+	"git.perx.ru/perxis/perxis-go/log"
+	"go.uber.org/zap"
+)
+
+type Middleware func(log.Service) log.Service
+
+func WithLog(s log.Service, logger *zap.Logger, log_access bool) log.Service {
+	if logger == nil {
+		logger = zap.NewNop()
+	}
+
+	logger = logger.Named("Service")
+	s = ErrorLoggingMiddleware(logger)(s)
+	if log_access {
+		s = LoggingMiddleware(logger)(s)
+	}
+	s = RecoveringMiddleware(logger)(s)
+	return s
+}
diff --git a/log/middleware/recovering_middleware.go b/log/middleware/recovering_middleware.go
new file mode 100644
index 0000000000000000000000000000000000000000..b8b3de39570f3fdece8969edb24c8f4a9dbcb0cb
--- /dev/null
+++ b/log/middleware/recovering_middleware.go
@@ -0,0 +1,68 @@
+// Code generated by gowrap. DO NOT EDIT.
+// template: ../../assets/templates/middleware/recovery
+// gowrap: http://github.com/hexdigest/gowrap
+
+package middleware
+
+//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/log -i Service -t ../../assets/templates/middleware/recovery -o recovering_middleware.go -l ""
+
+import (
+	"context"
+	"fmt"
+
+	"git.perx.ru/perxis/perxis-go/log"
+	"git.perx.ru/perxis/perxis-go/pkg/options"
+	"go.uber.org/zap"
+)
+
+// recoveringMiddleware implements log.Service that is instrumented with logging
+type recoveringMiddleware struct {
+	logger *zap.Logger
+	next   log.Service
+}
+
+// RecoveringMiddleware instruments an implementation of the log.Service with simple logging
+func RecoveringMiddleware(logger *zap.Logger) Middleware {
+	return func(next log.Service) log.Service {
+		return &recoveringMiddleware{
+			next:   next,
+			logger: logger,
+		}
+	}
+}
+
+func (m *recoveringMiddleware) Delete(ctx context.Context, filter *log.Filter) (err error) {
+	logger := m.logger
+	defer func() {
+		if r := recover(); r != nil {
+			logger.Error("panic", zap.Error(fmt.Errorf("%v", r)))
+			err = fmt.Errorf("%v", r)
+		}
+	}()
+
+	return m.next.Delete(ctx, filter)
+}
+
+func (m *recoveringMiddleware) Find(ctx context.Context, filter *log.Filter, options *options.FindOptions) (fp1 *log.FindResult, err error) {
+	logger := m.logger
+	defer func() {
+		if r := recover(); r != nil {
+			logger.Error("panic", zap.Error(fmt.Errorf("%v", r)))
+			err = fmt.Errorf("%v", r)
+		}
+	}()
+
+	return m.next.Find(ctx, filter, options)
+}
+
+func (m *recoveringMiddleware) Log(ctx context.Context, entries []*log.Entry) (err error) {
+	logger := m.logger
+	defer func() {
+		if r := recover(); r != nil {
+			logger.Error("panic", zap.Error(fmt.Errorf("%v", r)))
+			err = fmt.Errorf("%v", r)
+		}
+	}()
+
+	return m.next.Log(ctx, entries)
+}
diff --git a/log/middleware/telemetry_middleware.go b/log/middleware/telemetry_middleware.go
new file mode 100644
index 0000000000000000000000000000000000000000..f5ba0676cf727ae7d70809b789002b61afe70bf3
--- /dev/null
+++ b/log/middleware/telemetry_middleware.go
@@ -0,0 +1,221 @@
+// Code generated by gowrap. DO NOT EDIT.
+// template: ..\..\..\assets\templates\middleware\telemetry
+// gowrap: http://github.com/hexdigest/gowrap
+
+package middleware
+
+//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/collaborators -i log -i Service -t ../../assets/templates/middleware/telemetry -o telemetry_middleware.go -l ""
+
+// source template: https://github.com/hexdigest/gowrap/blob/master/templates/opentelemetry
+
+import (
+	"context"
+	"time"
+
+	"git.perx.ru/perxis/perxis-go/pkg/collaborators"
+	"git.perx.ru/perxis/perxis-go/pkg/telemetry/metrics"
+	"go.opentelemetry.io/otel"
+	"go.opentelemetry.io/otel/attribute"
+	otelmetric "go.opentelemetry.io/otel/metric"
+	"go.opentelemetry.io/otel/trace"
+)
+
+// telemetryMiddleware implements collaborators.Collaborators interface instrumented with opentracing spans
+type telemetryMiddleware struct {
+	collaborators.Collaborators
+	_instance      string
+	requestMetrics *metrics.RequestMetrics
+	_spanDecorator func(span trace.Span, params, results map[string]interface{})
+}
+
+// TelemetryMiddleware returns telemetryMiddleware
+func TelemetryMiddleware(base collaborators.Collaborators, instance string, spanDecorator ...func(span trace.Span, params, results map[string]interface{})) telemetryMiddleware {
+	requestMetrics, err := metrics.GetRequestMetrics()
+	if err != nil {
+		panic(err)
+	}
+
+	d := telemetryMiddleware{
+		Collaborators:  base,
+		_instance:      instance,
+		requestMetrics: requestMetrics,
+	}
+
+	if len(spanDecorator) > 0 && spanDecorator[0] != nil {
+		d._spanDecorator = spanDecorator[0]
+	}
+
+	return d
+}
+
+// Get implements collaborators.Collaborators
+func (_d telemetryMiddleware) Get(ctx context.Context, spaceId string, subject string) (role string, err error) {
+	attributes := otelmetric.WithAttributeSet(attribute.NewSet(
+		attribute.String("service", "Collaborators"),
+		attribute.String("method", "Get"),
+	))
+
+	_d.requestMetrics.Total.Add(ctx, 1, attributes)
+
+	start := time.Now()
+	ctx, _span := otel.Tracer(_d._instance).Start(ctx, "Collaborators.Get")
+
+	defer func() {
+		_d.requestMetrics.DurationMilliseconds.Record(ctx, time.Since(start).Milliseconds(), attributes)
+
+		if _d._spanDecorator != nil {
+			_d._spanDecorator(_span, map[string]interface{}{
+				"ctx":     ctx,
+				"spaceId": spaceId,
+				"subject": subject}, map[string]interface{}{
+				"role": role,
+				"err":  err})
+		} else if err != nil {
+			_d.requestMetrics.FailedTotal.Add(ctx, 1, attributes)
+
+			_span.RecordError(err)
+			_span.SetAttributes(attribute.String("event", "error"))
+			_span.SetAttributes(attribute.String("message", err.Error()))
+		}
+
+		_span.End()
+	}()
+	return _d.Collaborators.Get(ctx, spaceId, subject)
+}
+
+// ListCollaborators implements collaborators.Collaborators
+func (_d telemetryMiddleware) ListCollaborators(ctx context.Context, spaceId string) (collaborators []*collaborators.Collaborator, err error) {
+	attributes := otelmetric.WithAttributeSet(attribute.NewSet(
+		attribute.String("service", "Collaborators"),
+		attribute.String("method", "ListCollaborators"),
+	))
+
+	_d.requestMetrics.Total.Add(ctx, 1, attributes)
+
+	start := time.Now()
+	ctx, _span := otel.Tracer(_d._instance).Start(ctx, "Collaborators.ListCollaborators")
+
+	defer func() {
+		_d.requestMetrics.DurationMilliseconds.Record(ctx, time.Since(start).Milliseconds(), attributes)
+
+		if _d._spanDecorator != nil {
+			_d._spanDecorator(_span, map[string]interface{}{
+				"ctx":     ctx,
+				"spaceId": spaceId}, map[string]interface{}{
+				"collaborators": collaborators,
+				"err":           err})
+		} else if err != nil {
+			_d.requestMetrics.FailedTotal.Add(ctx, 1, attributes)
+
+			_span.RecordError(err)
+			_span.SetAttributes(attribute.String("event", "error"))
+			_span.SetAttributes(attribute.String("message", err.Error()))
+		}
+
+		_span.End()
+	}()
+	return _d.Collaborators.ListCollaborators(ctx, spaceId)
+}
+
+// ListSpaces implements collaborators.Collaborators
+func (_d telemetryMiddleware) ListSpaces(ctx context.Context, subject string) (spaces []*collaborators.Collaborator, err error) {
+	attributes := otelmetric.WithAttributeSet(attribute.NewSet(
+		attribute.String("service", "Collaborators"),
+		attribute.String("method", "ListSpaces"),
+	))
+
+	_d.requestMetrics.Total.Add(ctx, 1, attributes)
+
+	start := time.Now()
+	ctx, _span := otel.Tracer(_d._instance).Start(ctx, "Collaborators.ListSpaces")
+
+	defer func() {
+		_d.requestMetrics.DurationMilliseconds.Record(ctx, time.Since(start).Milliseconds(), attributes)
+
+		if _d._spanDecorator != nil {
+			_d._spanDecorator(_span, map[string]interface{}{
+				"ctx":     ctx,
+				"subject": subject}, map[string]interface{}{
+				"spaces": spaces,
+				"err":    err})
+		} else if err != nil {
+			_d.requestMetrics.FailedTotal.Add(ctx, 1, attributes)
+
+			_span.RecordError(err)
+			_span.SetAttributes(attribute.String("event", "error"))
+			_span.SetAttributes(attribute.String("message", err.Error()))
+		}
+
+		_span.End()
+	}()
+	return _d.Collaborators.ListSpaces(ctx, subject)
+}
+
+// Remove implements collaborators.Collaborators
+func (_d telemetryMiddleware) Remove(ctx context.Context, spaceId string, subject string) (err error) {
+	attributes := otelmetric.WithAttributeSet(attribute.NewSet(
+		attribute.String("service", "Collaborators"),
+		attribute.String("method", "Remove"),
+	))
+
+	_d.requestMetrics.Total.Add(ctx, 1, attributes)
+
+	start := time.Now()
+	ctx, _span := otel.Tracer(_d._instance).Start(ctx, "Collaborators.Remove")
+
+	defer func() {
+		_d.requestMetrics.DurationMilliseconds.Record(ctx, time.Since(start).Milliseconds(), attributes)
+
+		if _d._spanDecorator != nil {
+			_d._spanDecorator(_span, map[string]interface{}{
+				"ctx":     ctx,
+				"spaceId": spaceId,
+				"subject": subject}, map[string]interface{}{
+				"err": err})
+		} else if err != nil {
+			_d.requestMetrics.FailedTotal.Add(ctx, 1, attributes)
+
+			_span.RecordError(err)
+			_span.SetAttributes(attribute.String("event", "error"))
+			_span.SetAttributes(attribute.String("message", err.Error()))
+		}
+
+		_span.End()
+	}()
+	return _d.Collaborators.Remove(ctx, spaceId, subject)
+}
+
+// Set implements collaborators.Collaborators
+func (_d telemetryMiddleware) Set(ctx context.Context, spaceId string, subject string, role string) (err error) {
+	attributes := otelmetric.WithAttributeSet(attribute.NewSet(
+		attribute.String("service", "Collaborators"),
+		attribute.String("method", "Set"),
+	))
+
+	_d.requestMetrics.Total.Add(ctx, 1, attributes)
+
+	start := time.Now()
+	ctx, _span := otel.Tracer(_d._instance).Start(ctx, "Collaborators.Set")
+
+	defer func() {
+		_d.requestMetrics.DurationMilliseconds.Record(ctx, time.Since(start).Milliseconds(), attributes)
+
+		if _d._spanDecorator != nil {
+			_d._spanDecorator(_span, map[string]interface{}{
+				"ctx":     ctx,
+				"spaceId": spaceId,
+				"subject": subject,
+				"role":    role}, map[string]interface{}{
+				"err": err})
+		} else if err != nil {
+			_d.requestMetrics.FailedTotal.Add(ctx, 1, attributes)
+
+			_span.RecordError(err)
+			_span.SetAttributes(attribute.String("event", "error"))
+			_span.SetAttributes(attribute.String("message", err.Error()))
+		}
+
+		_span.End()
+	}()
+	return _d.Collaborators.Set(ctx, spaceId, subject, role)
+}