diff --git a/assets/templates/middleware/access_log b/assets/templates/middleware/access_log
deleted file mode 100755
index 86b4fe25ef6247f78dd491578f5b6bf05fa8e992..0000000000000000000000000000000000000000
--- a/assets/templates/middleware/access_log
+++ /dev/null
@@ -1,65 +0,0 @@
-import (
-  "fmt"
-  "time"
-  "context"
-
-  "go.uber.org/zap"
-)
-
-{{ $funcName := (or .Vars.FuncName ("LoggingMiddleware")) }}
-{{ $decorator := (or .Vars.DecoratorName ("loggingMiddleware")) }}
-
-// {{$decorator}} implements {{.Interface.Type}} that is instrumented with logging
-type {{$decorator}} struct {
-  logger *zap.Logger
-  next {{.Interface.Type}}
-}
-
-// {{$funcName}} instruments an implementation of the {{.Interface.Type}} with simple logging
-func {{$funcName}}(logger *zap.Logger) Middleware {
-  return func(next {{.Interface.Type}}) {{.Interface.Type}} {
-    return &{{$decorator}}{
-      next: next,
-      logger: logger,
-    }
-  }
-}
-
-{{range $method := .Interface.Methods}}
-    func (m *{{$decorator}}) {{$method.Declaration}} {
-        begin := time.Now()
-        var fields []zapcore.Field
-        {{- if $method.HasParams}}
-        for k, v := range {{$method.ParamsMap}} {
-            if k == "ctx" {
-			    fields = append(fields, zap.String("principal", fmt.Sprint(auth.GetPrincipal(ctx))))
-                continue
-            }
-            fields = append(fields, zap.Reflect(k,v))
-        }
-        {{end}}
-
-		m.logger.Debug("{{$method.Name}}.Request",fields...)
-
-        {{ $method.ResultsNames }} = m.next.{{ $method.Call }}
-
-        fields = []zapcore.Field{
-			zap.Duration("time", time.Since(begin)),
-        }
-
-        {{ if $method.HasResults}}
-        for k, v := range {{$method.ResultsMap}} {
-            if k == "err" {
-                err, _ := v.(error)
-                fields = append(fields, zap.Error(err))
-                continue
-            }
-            fields = append(fields, zap.Reflect(k,v))
-        }
-        {{end}}
-
-		m.logger.Debug("{{$method.Name}}.Response", fields...)
-
-        return {{ $method.ResultsNames }}
-    }
-{{end}}
diff --git a/assets/templates/middleware/access_log.tmpl b/assets/templates/middleware/access_log.tmpl
new file mode 100755
index 0000000000000000000000000000000000000000..9f9899b835a1ec46652da008035f3a8931dc472d
--- /dev/null
+++ b/assets/templates/middleware/access_log.tmpl
@@ -0,0 +1,58 @@
+import (
+  "fmt"
+  "time"
+  "context"
+
+  "go.uber.org/zap"
+)
+
+{{ $funcName := (or .Vars.FuncName ("AccessLoggingMiddleware")) }}
+{{ $decorator := (or .Vars.DecoratorName ("accessLoggingMiddleware")) }}
+{{ $objectName := (trimSuffix "s" (split "." .Interface.Type)._1) }}
+
+// {{$decorator}} implements {{.Interface.Type}} that is instrumented with logging
+type {{$decorator}} struct {
+  logger *zap.Logger
+  next {{.Interface.Type}}
+}
+
+// {{$funcName}} instruments an implementation of the {{.Interface.Type}} with simple logging
+func {{$funcName}}(logger *zap.Logger) Middleware {
+  return func(next {{.Interface.Type}}) {{.Interface.Type}} {
+    return &{{$decorator}}{
+      next: next,
+      logger: logger,
+    }
+  }
+}
+
+{{range $method := .Interface.Methods}}
+    func (m *{{$decorator}}) {{$method.Declaration}} {
+        begin := time.Now()
+
+        m.logger.Debug("{{$method.Name}}.Request",
+        {{- range $param := $method.Params -}}
+            {{- if (eq $param.Name "ctx") }}
+                zap.Reflect("principal", auth.GetPrincipal(ctx)),
+            {{- else }}
+                zap.Reflect("{{$param.Name}}", {{$param.Name}}),
+            {{- end -}}
+        {{ end }}
+        )
+
+        {{ $method.ResultsNames }} = m.next.{{ $method.Call }}
+
+        m.logger.Debug("{{$method.Name}}.Response",
+            zap.Duration("time", time.Since(begin)),
+            {{- range $param := $method.Results -}}
+                {{- if (eq $param.Name "err") }}
+                zap.Error(err),
+                {{- else }}
+                zap.Reflect("{{$param.Name}}", {{$param.Name}}),
+                {{- end -}}
+            {{ end }}
+        )
+
+        return {{ $method.ResultsNames }}
+    }
+{{end}}
diff --git a/assets/templates/middleware/log.tmpl b/assets/templates/middleware/log.tmpl
new file mode 100644
index 0000000000000000000000000000000000000000..ab91909fb61f1044fe74810390a64d469a6cb614
--- /dev/null
+++ b/assets/templates/middleware/log.tmpl
@@ -0,0 +1,73 @@
+{{/*
+Этот шаблон предназначен только для первичной генерации LoggingMiddleware,
+поскольку он не может учесть все сигнатуры логгируемых методов. После генерации
+необходимо внести правки в код в местах, помеченных 'TODO'
+
+Сгенерировать middleware:
+```shell
+gowrap gen -p git.perx.ru/perxis/perxis-go/<package_name> -i <interface> -t ../../../assets/templates/middleware/logging.tmpl -o info_logging_middleware.go -g
+```
+ */}}
+
+import (
+    "fmt"
+    "time"
+    "context"
+
+    logzap "git.perx.ru/perxis/perxis-go/zap"
+    "go.uber.org/zap"
+    "go.uber.org/zap/zapcore"
+)
+
+{{ $funcName := (or .Vars.FuncName ("LoggingMiddleware")) }}
+{{ $decorator := (or .Vars.DecoratorName ("loggingMiddleware")) }}
+{{ $packageName := (split "." .Interface.Type)._0 }}
+{{ $serviceName := (split "." .Interface.Type)._1 }}
+{{ $objectName := (trimSuffix "s" (split "." .Interface.Type)._1) }}
+{{ $writeMethods :=  list "Archive" "Create" "Delete" "Publish" "Unarchive" "Undelete" "Unpublish" "Update" "SetSchema" "Migrate" }}
+
+type {{ $decorator }} struct {
+    logger *zap.Logger
+    next {{ .Interface.Type }}
+}
+
+func {{ $funcName }} (logger *zap.Logger) Middleware {
+    return func(next {{ .Interface.Type }}) {{ .Interface.Type }} {
+        return &{{ $decorator }}{
+            next: next,
+            logger: logger.With(logzap.Component("{{ (lower $serviceName ) }}")),
+        }
+    }
+}
+
+{{ range $method := .Interface.Methods }}
+func (m *{{ $decorator }}) {{ $method.Declaration }} {
+    logger := m.logger.With(
+        {{- if $method.AcceptsContext }}
+        logzap.CallerFromContext(ctx),
+        {{ end -}}
+        {{- if has $method.Name $writeMethods -}}
+        logzap.Event({{ $packageName }}.Event{{ $objectName }}{{ $method.Name }}),
+        logzap.Object(TODO),
+        {{ end -}}
+    )
+
+    {{ $method.ResultsNames }} = m.next.{{ $method.Call }}
+
+    {{- if $method.ReturnsError }}
+    if err != nil {
+        logger.Error("Failed to {{ (lower $method.Name) }}", zap.Error(err)
+            {{- if has $method.Name $writeMethods -}}
+            , logzap.Channels(logzap.Userlog, logzap.Syslog)
+            {{- end -}})
+        return
+    }
+    {{ end }}
+
+    {{ if has $method.Name $writeMethods }}
+    logger.Info("Successfully {{ (lower (trimSuffix "e" $method.Name)) }}ed",  logzap.Channels(logzap.Userlog))
+    {{ end -}}
+
+    return {{ $method.ResultsNames }}
+}
+{{ end }}
diff --git a/assets/templates/middleware/middleware b/assets/templates/middleware/middleware.tmpl
similarity index 57%
rename from assets/templates/middleware/middleware
rename to assets/templates/middleware/middleware.tmpl
index 89877774c933840c2bdd569f2beed8105588aae2..c717d577e89331ed386cf21a1b8ba5486cd54714 100755
--- a/assets/templates/middleware/middleware
+++ b/assets/templates/middleware/middleware.tmpl
@@ -2,20 +2,26 @@ import (
 	"go.uber.org/zap"
 )
 
-type Middleware func({{.Interface.Type}}) {{.Interface.Type}}
+{{ $serviceName := (split "." .Interface.Type)._1 }}
 
+type Middleware func({{.Interface.Type}}) {{.Interface.Type}}
 
 func WithLog(s {{.Interface.Type}}, logger *zap.Logger, log_access bool) {{.Interface.Type}} {
 	if logger == nil {
 		logger = zap.NewNop()
 	}
-
-	logger = logger.Named("{{ .Interface.Name }}")
+    logger = logger.Named("{{ .Interface.Name }}")
+    {{- if (has $serviceName (list "Items" "Collections") ) }}
+	if log_access {
+		s = AccessLoggingMiddleware(logger)(s)
+	}
+	s = LoggingMiddleware(logger)(s)
+	{{ else }}
 	s = ErrorLoggingMiddleware(logger)(s)
 	if log_access {
 		s = LoggingMiddleware(logger)(s)
 	}
+	{{ end -}}
 	s = RecoveringMiddleware(logger)(s)
 	return s
 }
-
diff --git a/log/service.go b/log/service.go
index 8a39ca53e0573ac4ed0bed8bba619837d1fdf030..46e306077d8ee3476dc1e83aa30a7501494325d5 100644
--- a/log/service.go
+++ b/log/service.go
@@ -8,6 +8,8 @@ import (
 	pb "git.perx.ru/perxis/perxis-go/proto/log"
 )
 
+const ServiceName = "logs"
+
 type Service interface {
 
 	// Log метод записи логов
diff --git a/log/zap/buffered_write_syncer_test.go b/log/zap/buffered_write_syncer_test.go
index 58e40098bb2370b7899d31fc42c2ea569ca81cf7..58d64fa9f6a612f58701e5284262b505ec190cc5 100644
--- a/log/zap/buffered_write_syncer_test.go
+++ b/log/zap/buffered_write_syncer_test.go
@@ -53,16 +53,12 @@ func TestBufferedWriteSyncer_Write_Concurrent(t *testing.T) {
 		wg.Add(1)
 		go func(wg *sync.WaitGroup) {
 			defer wg.Done()
-
-			err := ws.Write(&log.Entry{Message: "log message"})
-			require.NoError(t, err)
+			require.NoError(t, ws.Write(&log.Entry{Message: "log message"}))
 		}(&wg)
 	}
 
 	wg.Wait()
-
-	err := ws.Stop()
-	require.NoError(t, err)
+	require.NoError(t, ws.Stop())
 
 	service.AssertExpectations(t)
 }
@@ -81,15 +77,12 @@ func TestBufferedWriteSyncer_Flush(t *testing.T) {
 
 	for i := 0; i < 10; i++ {
 		for j := 0; j < 10; j++ {
-			err := ws.Write(&log.Entry{Message: "log message"})
-			require.NoError(t, err)
+			require.NoError(t, ws.Write(&log.Entry{Message: "log message"}))
 		}
-		err := ws.Sync()
-		require.NoError(t, err)
+		require.NoError(t, ws.Sync())
 	}
 
-	err := ws.Stop()
-	require.NoError(t, err)
+	require.NoError(t, ws.Stop())
 
 	service.AssertExpectations(t)
 }
@@ -107,12 +100,10 @@ func TestBufferedWriteSyncer_MaxBufferSize(t *testing.T) {
 	ws := &BufferedWriteSyncer{Service: service, MaxBufferSize: 10}
 
 	for i := 0; i < 100; i++ {
-		err := ws.Write(&log.Entry{Message: "log message"})
-		require.NoError(t, err)
+		require.NoError(t, ws.Write(&log.Entry{Message: "log message"}))
 	}
 
-	err := ws.Stop()
-	require.NoError(t, err)
+	require.NoError(t, ws.Stop())
 
 	service.AssertExpectations(t)
 }
@@ -130,14 +121,11 @@ func TestBufferedWriteSyncer_FlushInterval(t *testing.T) {
 	ws := &BufferedWriteSyncer{Service: service, FlushInterval: time.Second}
 
 	for j := 0; j < 10; j++ {
-		err := ws.Write(&log.Entry{Message: "log message"})
-		require.NoError(t, err)
+		require.NoError(t, ws.Write(&log.Entry{Message: "log message"}))
 	}
 
 	time.Sleep(3 * time.Second) // ждем, пока сработает интервал
-
-	err := ws.Stop()
-	require.NoError(t, err)
+	require.NoError(t, ws.Stop())
 
 	service.AssertExpectations(t)
 }
diff --git a/log/zap/core.go b/log/zap/core.go
index f2b220eab78a6cd2416408385b521c743bcefb94..070e6fb84eb3ca74cd59262eb5244054967e9307 100644
--- a/log/zap/core.go
+++ b/log/zap/core.go
@@ -1,6 +1,8 @@
 package zap
 
 import (
+	"fmt"
+
 	oid "git.perx.ru/perxis/perxis-go/id"
 	"git.perx.ru/perxis/perxis-go/log"
 	"git.perx.ru/perxis/perxis-go/pkg/id"
@@ -76,6 +78,10 @@ func (core *Core) getEntry(entry zapcore.Entry, fields []zapcore.Field) *log.Ent
 	ent.CallerID, _ = enc.Fields["caller"].(*oid.ObjectId)
 	ent.Attr = enc.Fields["attr"]
 
+	if err, _ := enc.Fields["error"].(error); err != nil {
+		ent.Message = fmt.Sprintf("%s. Error: %s", ent.Message, err.Error())
+	}
+
 	if tags, ok := enc.Fields["tags"].([]any); ok {
 		for _, item := range tags {
 			if tag, ok := item.(string); ok {
diff --git a/log/zap/core_test.go b/log/zap/core_test.go
index 51b222bdc002c6ed74bb2e81fc8c84be59f95cba..8b2d64d75c737164f38b911fcbd4a9a0e0c56101 100644
--- a/log/zap/core_test.go
+++ b/log/zap/core_test.go
@@ -5,7 +5,7 @@ import (
 
 	"git.perx.ru/perxis/perxis-go/id"
 	"git.perx.ru/perxis/perxis-go/log"
-	zap2 "git.perx.ru/perxis/perxis-go/zap"
+	logzap "git.perx.ru/perxis/perxis-go/zap"
 	"github.com/stretchr/testify/require"
 	"go.uber.org/zap"
 	"go.uber.org/zap/zapcore"
@@ -31,13 +31,13 @@ func TestCore_getEntry(t *testing.T) {
 				entry: zapcore.Entry{Level: zapcore.InfoLevel, Message: "создан элемент коллекции"},
 				fields: []zapcore.Field{
 					zap.String("key", "val"), // будет проигнорировано
-					zap2.Category("create"),
-					zap2.Component("Items.Service"),
-					zap2.Event("Items.Create"),
-					zap2.Object("/spaces/WPNN/envs/9VGP/cols/GxNv/items/W0fl"),
-					zap2.Caller("/users/PHVz"),
-					zap2.Attr("any"),
-					zap2.Tags("tag1", "tag2", "tag3"),
+					logzap.Category("create"),
+					logzap.Component("Items.Service"),
+					logzap.Event("Items.Create"),
+					logzap.Object("/spaces/WPNN/envs/9VGP/cols/GxNv/items/W0fl"),
+					logzap.Caller("/users/PHVz"),
+					logzap.Attr("any"),
+					logzap.Tags("tag1", "tag2", "tag3"),
 				},
 			},
 			want: &log.Entry{
diff --git a/log/zap/example_test.go b/log/zap/example_test.go
index ee51ef0345b01a88993e30c24a38f7e89bbe5929..0ffb282a6c2c91cff4c203e8500773c3630fdfa8 100644
--- a/log/zap/example_test.go
+++ b/log/zap/example_test.go
@@ -13,7 +13,7 @@ import (
 	"git.perx.ru/perxis/perxis-go/pkg/items"
 	"git.perx.ru/perxis/perxis-go/pkg/users"
 	usersmocks "git.perx.ru/perxis/perxis-go/pkg/users/mocks"
-	zap2 "git.perx.ru/perxis/perxis-go/zap"
+	logzap "git.perx.ru/perxis/perxis-go/zap"
 	"github.com/stretchr/testify/mock"
 	"github.com/stretchr/testify/require"
 	"go.uber.org/zap"
@@ -29,7 +29,7 @@ func TestExample(t *testing.T) {
 			LogLevel:  log.Level(zapcore.InfoLevel),
 			Message:   "Successfully created",
 			Component: "Items",
-			Event:     items.EventCreateItem,
+			Event:     items.EventItemCreate,
 			ObjectID:  id.MustObjectId(item),
 			CallerID:  id.MustObjectId(user),
 			Tags:      []string{"tag1", "tag2", "tag3"},
@@ -38,7 +38,7 @@ func TestExample(t *testing.T) {
 			LogLevel:  log.Level(zapcore.WarnLevel),
 			Message:   "Successfully updated",
 			Component: "Items",
-			Event:     items.EventUpdateItem,
+			Event:     items.EventItemUpdate,
 			ObjectID:  id.MustObjectId(item),
 			CallerID:  id.MustObjectId(user),
 			Attr:      map[string]map[string]any{"title": {"old": "old title", "new": "new title"}},
@@ -70,23 +70,23 @@ func TestExample(t *testing.T) {
 
 	// Пример отправки логов для сервиса Items
 	{
-		logger := logger.With(zap2.Component("Items"))
+		logger := logger.With(logzap.Component("Items"))
 		ctx := auth.WithPrincipal(context.Background(), factory.User("74d90aaf"))
 
 		// Отправка лога при создании item
 		logger.Info("Successfully created",
-			zap2.Event(items.EventCreateItem),
-			zap2.Object(item),
-			zap2.CallerFromContext(ctx),
-			zap2.Tags("tag1", "tag2", "tag3"),
+			logzap.Event(items.EventItemCreate),
+			logzap.Object(item),
+			logzap.CallerFromContext(ctx),
+			logzap.Tags("tag1", "tag2", "tag3"),
 		)
 
 		// Отправка лога при обновлении item
 		logger.Warn("Successfully updated",
-			zap2.Event(items.EventUpdateItem),
-			zap2.Object(item),
-			zap2.CallerFromContext(ctx),
-			zap2.Attr(map[string]map[string]any{"title": {"old": "old title", "new": "new title"}}),
+			logzap.Event(items.EventItemUpdate),
+			logzap.Object(item),
+			logzap.CallerFromContext(ctx),
+			logzap.Attr(map[string]map[string]any{"title": {"old": "old title", "new": "new title"}}),
 		)
 	}
 
diff --git a/pkg/collections/events.go b/pkg/collections/events.go
new file mode 100644
index 0000000000000000000000000000000000000000..bdc92e7769a7cd7ffc880825caaf3098fb5495fb
--- /dev/null
+++ b/pkg/collections/events.go
@@ -0,0 +1,8 @@
+package collections
+
+const (
+	EventCollectionCreate    = "collection_create"
+	EventCollectionUpdate    = "collection_update"
+	EventCollectionDelete    = "collection_delete"
+	EventCollectionSetSchema = "collection_set_schema"
+)
diff --git a/pkg/collections/middleware/access_logging_middleware.go b/pkg/collections/middleware/access_logging_middleware.go
new file mode 100644
index 0000000000000000000000000000000000000000..3624955b5d3ef6201704932e623b4e8e00cc6192
--- /dev/null
+++ b/pkg/collections/middleware/access_logging_middleware.go
@@ -0,0 +1,175 @@
+// Code generated by gowrap. DO NOT EDIT.
+// template: ../../../assets/templates/middleware/access_log.tmpl
+// gowrap: http://github.com/hexdigest/gowrap
+
+package middleware
+
+//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/collections -i Collections -t ../../../assets/templates/middleware/access_log.tmpl -o access_logging_middleware.go -l ""
+
+import (
+	"context"
+	"time"
+
+	"git.perx.ru/perxis/perxis-go/pkg/auth"
+	"git.perx.ru/perxis/perxis-go/pkg/collections"
+	"git.perx.ru/perxis/perxis-go/pkg/schema"
+	"go.uber.org/zap"
+)
+
+// accessLoggingMiddleware implements collections.Collections that is instrumented with logging
+type accessLoggingMiddleware struct {
+	logger *zap.Logger
+	next   collections.Collections
+}
+
+// AccessLoggingMiddleware instruments an implementation of the collections.Collections with simple logging
+func AccessLoggingMiddleware(logger *zap.Logger) Middleware {
+	return func(next collections.Collections) collections.Collections {
+		return &accessLoggingMiddleware{
+			next:   next,
+			logger: logger,
+		}
+	}
+}
+
+func (m *accessLoggingMiddleware) Create(ctx context.Context, collection *collections.Collection) (created *collections.Collection, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Create.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("collection", collection),
+	)
+
+	created, err = m.next.Create(ctx, collection)
+
+	m.logger.Debug("Create.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("created", created),
+		zap.Error(err),
+	)
+
+	return created, err
+}
+
+func (m *accessLoggingMiddleware) Delete(ctx context.Context, spaceId string, envId string, collectionId string) (err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Delete.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("spaceId", spaceId),
+		zap.Reflect("envId", envId),
+		zap.Reflect("collectionId", collectionId),
+	)
+
+	err = m.next.Delete(ctx, spaceId, envId, collectionId)
+
+	m.logger.Debug("Delete.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	)
+
+	return err
+}
+
+func (m *accessLoggingMiddleware) Get(ctx context.Context, spaceId string, envId string, collectionId string, options ...*collections.GetOptions) (collection *collections.Collection, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Get.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("spaceId", spaceId),
+		zap.Reflect("envId", envId),
+		zap.Reflect("collectionId", collectionId),
+		zap.Reflect("options", options),
+	)
+
+	collection, err = m.next.Get(ctx, spaceId, envId, collectionId, options...)
+
+	m.logger.Debug("Get.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("collection", collection),
+		zap.Error(err),
+	)
+
+	return collection, err
+}
+
+func (m *accessLoggingMiddleware) List(ctx context.Context, spaceId string, envId string, filter *collections.Filter) (collections []*collections.Collection, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("List.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("spaceId", spaceId),
+		zap.Reflect("envId", envId),
+		zap.Reflect("filter", filter),
+	)
+
+	collections, err = m.next.List(ctx, spaceId, envId, filter)
+
+	m.logger.Debug("List.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("collections", collections),
+		zap.Error(err),
+	)
+
+	return collections, err
+}
+
+func (m *accessLoggingMiddleware) SetSchema(ctx context.Context, spaceId string, envId string, collectionId string, schema *schema.Schema) (err error) {
+	begin := time.Now()
+
+	m.logger.Debug("SetSchema.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("spaceId", spaceId),
+		zap.Reflect("envId", envId),
+		zap.Reflect("collectionId", collectionId),
+		zap.Reflect("schema", schema),
+	)
+
+	err = m.next.SetSchema(ctx, spaceId, envId, collectionId, schema)
+
+	m.logger.Debug("SetSchema.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	)
+
+	return err
+}
+
+func (m *accessLoggingMiddleware) SetState(ctx context.Context, spaceId string, envId string, collectionId string, state *collections.StateInfo) (err error) {
+	begin := time.Now()
+
+	m.logger.Debug("SetState.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("spaceId", spaceId),
+		zap.Reflect("envId", envId),
+		zap.Reflect("collectionId", collectionId),
+		zap.Reflect("state", state),
+	)
+
+	err = m.next.SetState(ctx, spaceId, envId, collectionId, state)
+
+	m.logger.Debug("SetState.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	)
+
+	return err
+}
+
+func (m *accessLoggingMiddleware) Update(ctx context.Context, coll *collections.Collection) (err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Update.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("coll", coll),
+	)
+
+	err = m.next.Update(ctx, coll)
+
+	m.logger.Debug("Update.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	)
+
+	return err
+}
diff --git a/pkg/collections/middleware/error_logging_middleware.go b/pkg/collections/middleware/error_logging_middleware.go
deleted file mode 100644
index 0491250639063a0e4d229c711c19b36223b9b2e4..0000000000000000000000000000000000000000
--- a/pkg/collections/middleware/error_logging_middleware.go
+++ /dev/null
@@ -1,101 +0,0 @@
-package middleware
-
-// Code generated by gowrap. DO NOT EDIT.
-// template: ../../../assets/templates/middleware/error_log
-// gowrap: http://github.com/hexdigest/gowrap
-
-//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/collections -i Collections -t ../../../assets/templates/middleware/error_log -o error_logging_middleware.go -l ""
-
-import (
-	"context"
-
-	"git.perx.ru/perxis/perxis-go/pkg/collections"
-	"git.perx.ru/perxis/perxis-go/pkg/schema"
-	"go.uber.org/zap"
-)
-
-// errorLoggingMiddleware implements collections.Collections that is instrumented with logging
-type errorLoggingMiddleware struct {
-	logger *zap.Logger
-	next   collections.Collections
-}
-
-// ErrorLoggingMiddleware instruments an implementation of the collections.Collections with simple logging
-func ErrorLoggingMiddleware(logger *zap.Logger) Middleware {
-	return func(next collections.Collections) collections.Collections {
-		return &errorLoggingMiddleware{
-			next:   next,
-			logger: logger,
-		}
-	}
-}
-
-func (m *errorLoggingMiddleware) Create(ctx context.Context, collection *collections.Collection) (created *collections.Collection, err error) {
-	logger := m.logger
-	defer func() {
-		if err != nil {
-			logger.Warn("response error", zap.Error(err))
-		}
-	}()
-	return m.next.Create(ctx, collection)
-}
-
-func (m *errorLoggingMiddleware) Delete(ctx context.Context, spaceId string, envId string, collectionId string) (err error) {
-	logger := m.logger
-	defer func() {
-		if err != nil {
-			logger.Warn("response error", zap.Error(err))
-		}
-	}()
-	return m.next.Delete(ctx, spaceId, envId, collectionId)
-}
-
-func (m *errorLoggingMiddleware) Get(ctx context.Context, spaceId string, envId string, collectionId string, options ...*collections.GetOptions) (collection *collections.Collection, err error) {
-	logger := m.logger
-	defer func() {
-		if err != nil {
-			logger.Warn("response error", zap.Error(err))
-		}
-	}()
-	return m.next.Get(ctx, spaceId, envId, collectionId, options...)
-}
-
-func (m *errorLoggingMiddleware) List(ctx context.Context, spaceId string, envId string, filter *collections.Filter) (collections []*collections.Collection, err error) {
-	logger := m.logger
-	defer func() {
-		if err != nil {
-			logger.Warn("response error", zap.Error(err))
-		}
-	}()
-	return m.next.List(ctx, spaceId, envId, filter)
-}
-
-func (m *errorLoggingMiddleware) SetSchema(ctx context.Context, spaceId string, envId string, collectionId string, schema *schema.Schema) (err error) {
-	logger := m.logger
-	defer func() {
-		if err != nil {
-			logger.Warn("response error", zap.Error(err))
-		}
-	}()
-	return m.next.SetSchema(ctx, spaceId, envId, collectionId, schema)
-}
-
-func (m *errorLoggingMiddleware) SetState(ctx context.Context, spaceId string, envId string, collectionId string, state *collections.StateInfo) (err error) {
-	logger := m.logger
-	defer func() {
-		if err != nil {
-			logger.Warn("response error", zap.Error(err))
-		}
-	}()
-	return m.next.SetState(ctx, spaceId, envId, collectionId, state)
-}
-
-func (m *errorLoggingMiddleware) Update(ctx context.Context, coll *collections.Collection) (err error) {
-	logger := m.logger
-	defer func() {
-		if err != nil {
-			logger.Warn("response error", zap.Error(err))
-		}
-	}()
-	return m.next.Update(ctx, coll)
-}
diff --git a/pkg/collections/middleware/logging_middleware.go b/pkg/collections/middleware/logging_middleware.go
index bc3e41070fc56e0a4ec2a75f073f9696a88560fb..c5e2e2fb3e5bf604fdb57c8484f4a20289029da6 100644
--- a/pkg/collections/middleware/logging_middleware.go
+++ b/pkg/collections/middleware/logging_middleware.go
@@ -1,303 +1,140 @@
-package middleware
-
 // Code generated by gowrap. DO NOT EDIT.
-// template: ../../../assets/templates/middleware/access_log
+// template: ../../../assets/templates/middleware/log.tmpl
 // gowrap: http://github.com/hexdigest/gowrap
 
-//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/collections -i Collections -t ../../../assets/templates/middleware/access_log -o logging_middleware.go -l ""
+package middleware
+
+//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/collections -i Collections -t ../../../assets/templates/middleware/log.tmpl -o logging_middleware.go -l ""
 
 import (
 	"context"
-	"fmt"
-	"time"
 
-	"git.perx.ru/perxis/perxis-go/pkg/auth"
+	"git.perx.ru/perxis/perxis-go/id"
 	"git.perx.ru/perxis/perxis-go/pkg/collections"
 	"git.perx.ru/perxis/perxis-go/pkg/schema"
+	logzap "git.perx.ru/perxis/perxis-go/zap"
 	"go.uber.org/zap"
-	"go.uber.org/zap/zapcore"
 )
 
-// loggingMiddleware implements collections.Collections that is instrumented with logging
 type loggingMiddleware struct {
 	logger *zap.Logger
 	next   collections.Collections
 }
 
-// LoggingMiddleware instruments an implementation of the collections.Collections with simple logging
 func LoggingMiddleware(logger *zap.Logger) Middleware {
 	return func(next collections.Collections) collections.Collections {
 		return &loggingMiddleware{
 			next:   next,
-			logger: logger,
+			logger: logger.With(logzap.Component("collections")),
 		}
 	}
 }
 
 func (m *loggingMiddleware) Create(ctx context.Context, collection *collections.Collection) (created *collections.Collection, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":        ctx,
-		"collection": collection} {
-		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("Create.Request", fields...)
+	logger := m.logger.With(
+		logzap.CallerFromContext(ctx),
+		logzap.Event(collections.EventCollectionCreate),
+	)
 
 	created, err = m.next.Create(ctx, collection)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
+	if err != nil {
+		logger.Error("Failed to create", zap.Error(err), logzap.Object(collection), logzap.Channels(logzap.Userlog, logzap.Syslog))
+		return
 	}
 
-	for k, v := range map[string]interface{}{
-		"created": created,
-		"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("Create.Response", fields...)
-
+	logger.Info("Successfully created", logzap.Object(created), logzap.Channels(logzap.Userlog))
 	return created, err
 }
 
 func (m *loggingMiddleware) Delete(ctx context.Context, spaceId string, envId string, collectionId string) (err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":          ctx,
-		"spaceId":      spaceId,
-		"envId":        envId,
-		"collectionId": collectionId} {
-		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...)
+	logger := m.logger.With(
+		logzap.CallerFromContext(ctx),
+		logzap.Event(collections.EventCollectionDelete),
+		logzap.Object(id.NewCollectionId(spaceId, envId, collectionId)),
+	)
 
 	err = m.next.Delete(ctx, spaceId, envId, collectionId)
-
-	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))
+	if err != nil {
+		logger.Error("Failed to delete", zap.Error(err), logzap.Channels(logzap.Userlog, logzap.Syslog))
+		return
 	}
 
-	m.logger.Debug("Delete.Response", fields...)
-
+	logger.Info("Successfully deleted", logzap.Channels(logzap.Userlog))
 	return err
 }
 
 func (m *loggingMiddleware) Get(ctx context.Context, spaceId string, envId string, collectionId string, options ...*collections.GetOptions) (collection *collections.Collection, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":          ctx,
-		"spaceId":      spaceId,
-		"envId":        envId,
-		"collectionId": collectionId,
-		"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("Get.Request", fields...)
+	logger := m.logger.With(
+		logzap.CallerFromContext(ctx),
+	)
 
 	collection, err = m.next.Get(ctx, spaceId, envId, collectionId, options...)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"collection": collection,
-		"err":        err} {
-		if k == "err" {
-			err, _ := v.(error)
-			fields = append(fields, zap.Error(err))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
+	if err != nil {
+		logger.Error("Failed to get", zap.Error(err))
+		return
 	}
 
-	m.logger.Debug("Get.Response", fields...)
-
 	return collection, err
 }
 
 func (m *loggingMiddleware) List(ctx context.Context, spaceId string, envId string, filter *collections.Filter) (collections []*collections.Collection, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":     ctx,
-		"spaceId": spaceId,
-		"envId":   envId,
-		"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("List.Request", fields...)
+	logger := m.logger.With(
+		logzap.CallerFromContext(ctx),
+	)
 
 	collections, err = m.next.List(ctx, spaceId, envId, filter)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"collections": collections,
-		"err":         err} {
-		if k == "err" {
-			err, _ := v.(error)
-			fields = append(fields, zap.Error(err))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
+	if err != nil {
+		logger.Error("Failed to list", zap.Error(err))
+		return
 	}
 
-	m.logger.Debug("List.Response", fields...)
-
 	return collections, err
 }
 
 func (m *loggingMiddleware) SetSchema(ctx context.Context, spaceId string, envId string, collectionId string, schema *schema.Schema) (err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":          ctx,
-		"spaceId":      spaceId,
-		"envId":        envId,
-		"collectionId": collectionId,
-		"schema":       schema} {
-		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("SetSchema.Request", fields...)
+	logger := m.logger.With(
+		logzap.CallerFromContext(ctx),
+		logzap.Event(collections.EventCollectionSetSchema),
+		logzap.Object(id.NewCollectionId(spaceId, envId, collectionId)),
+	)
 
 	err = m.next.SetSchema(ctx, spaceId, envId, collectionId, schema)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
+	if err != nil {
+		logger.Error("Failed to setschema", zap.Error(err), logzap.Channels(logzap.Userlog, logzap.Syslog))
+		return
 	}
 
-	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("SetSchema.Response", fields...)
-
+	logger.Info("Successfully setschemaed", logzap.Channels(logzap.Userlog))
 	return err
 }
 
 func (m *loggingMiddleware) SetState(ctx context.Context, spaceId string, envId string, collectionId string, state *collections.StateInfo) (err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":          ctx,
-		"spaceId":      spaceId,
-		"envId":        envId,
-		"collectionId": collectionId,
-		"state":        state} {
-		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("SetState.Request", fields...)
+	logger := m.logger.With(
+		logzap.CallerFromContext(ctx),
+	)
 
 	err = m.next.SetState(ctx, spaceId, envId, collectionId, state)
-
-	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))
+	if err != nil {
+		logger.Error("Failed to setstate", zap.Error(err))
+		return
 	}
 
-	m.logger.Debug("SetState.Response", fields...)
-
 	return err
 }
 
 func (m *loggingMiddleware) Update(ctx context.Context, coll *collections.Collection) (err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":  ctx,
-		"coll": coll} {
-		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("Update.Request", fields...)
+	logger := m.logger.With(
+		logzap.CallerFromContext(ctx),
+		logzap.Event(collections.EventCollectionUpdate),
+		logzap.Object(coll),
+	)
 
 	err = m.next.Update(ctx, coll)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
+	if err != nil {
+		logger.Error("Failed to update", zap.Error(err), logzap.Channels(logzap.Userlog, logzap.Syslog))
+		return
 	}
 
-	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("Update.Response", fields...)
-
+	logger.Info("Successfully updated", logzap.Channels(logzap.Userlog))
 	return err
 }
diff --git a/pkg/collections/middleware/middleware.go b/pkg/collections/middleware/middleware.go
index b581ce74b6071c707cc96fe9f11656bece37eb99..22514e7c93ec032847590821bfb2ba7467462f63 100644
--- a/pkg/collections/middleware/middleware.go
+++ b/pkg/collections/middleware/middleware.go
@@ -1,10 +1,10 @@
 // Code generated by gowrap. DO NOT EDIT.
-// template: ../../../assets/templates/middleware/middleware
+// template: ../../../assets/templates/middleware/middleware.tmpl
 // gowrap: http://github.com/hexdigest/gowrap
 
 package middleware
 
-//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/collections -i Collections -t ../../../assets/templates/middleware/middleware -o middleware.go -l ""
+//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/collections -i Collections -t ../../../assets/templates/middleware/middleware.tmpl -o middleware.go -l ""
 
 import (
 	"git.perx.ru/perxis/perxis-go/pkg/collections"
@@ -17,12 +17,11 @@ func WithLog(s collections.Collections, logger *zap.Logger, log_access bool) col
 	if logger == nil {
 		logger = zap.NewNop()
 	}
-
 	logger = logger.Named("Collections")
-	s = ErrorLoggingMiddleware(logger)(s)
 	if log_access {
-		s = LoggingMiddleware(logger)(s)
+		s = AccessLoggingMiddleware(logger)(s)
 	}
+	s = LoggingMiddleware(logger)(s)
 	s = RecoveringMiddleware(logger)(s)
 	return s
 }
diff --git a/pkg/delivery/transport/grpc/protobuf_type_converters.microgen.go b/pkg/delivery/transport/grpc/protobuf_type_converters.microgen.go
index aedb4ff8a7612702413760f2a1276153c1571c9e..6961c709da405a34710e76ff6c1ad6f34c218f1b 100644
--- a/pkg/delivery/transport/grpc/protobuf_type_converters.microgen.go
+++ b/pkg/delivery/transport/grpc/protobuf_type_converters.microgen.go
@@ -302,39 +302,11 @@ func ProtoToPtrItemsFilter(protoFilter *itemspb.Filter) (*items.Filter, error) {
 }
 
 func PtrServicesFindOptionsToProto(options *services.FindOptions) (*common.FindOptions, error) {
-	if options == nil {
-		return nil, nil
-	}
-	return &common.FindOptions{
-		Sort:          options.Sort,
-		PageNum:       int32(options.PageNum),
-		PageSize:      int32(options.PageSize),
-		Offset:        int32(options.Offset),
-		Limit:         int32(options.Limit),
-		Fields:        options.Fields,
-		ExcludeFields: options.ExcludeFields,
-	}, nil
+	return services.FindOptionsToPB(options), nil
 }
 
 func ProtoToPtrServicesFindOptions(protoOptions *common.FindOptions) (*services.FindOptions, error) {
-	if protoOptions == nil {
-		return nil, nil
-	}
-	return &services.FindOptions{
-		SortOptions: services.SortOptions{
-			Sort: protoOptions.Sort,
-		},
-		PaginationOptions: services.PaginationOptions{
-			PageNum:  int(protoOptions.PageNum),
-			PageSize: int(protoOptions.PageSize),
-			Offset:   int(protoOptions.Offset),
-			Limit:    int(protoOptions.Limit),
-		},
-		FieldOptions: services.FieldOptions{
-			Fields:        protoOptions.Fields,
-			ExcludeFields: protoOptions.ExcludeFields,
-		},
-	}, nil
+	return services.FindOptionsFromPB(protoOptions), nil
 }
 
 func ListPtrItemsItemToProto(itms []*items.Item) ([]*itemspb.Item, error) {
diff --git a/pkg/invitations/transport/grpc/protobuf_type_converters.microgen.go b/pkg/invitations/transport/grpc/protobuf_type_converters.microgen.go
index ee7b788e2bf670b9f885c0fa3d9a52cbff30502e..1b222519b0aacc35feb180e0273ac9758394aefe 100644
--- a/pkg/invitations/transport/grpc/protobuf_type_converters.microgen.go
+++ b/pkg/invitations/transport/grpc/protobuf_type_converters.microgen.go
@@ -104,11 +104,18 @@ func PtrServicesFindOptionsToProto(opts *options.FindOptions) (*pb.FindOptions,
 	if opts == nil {
 		return nil, nil
 	}
-	return &pb.FindOptions{
+
+	fo := &pb.FindOptions{
 		Sort:     opts.Sort,
-		PageNum:  int32(opts.PageNum),
-		PageSize: int32(opts.PageSize),
-	}, nil
+		PageSize: int32(opts.Limit),
+	}
+
+	if opts.Limit != 0 {
+		// Потенциальная ошибка если offset не кратен limit
+		fo.PageNum = int32(opts.Offset / opts.Limit)
+	}
+
+	return fo, nil
 }
 
 func ProtoToPtrServicesFindOptions(protoOpts *pb.FindOptions) (*options.FindOptions, error) {
@@ -120,8 +127,8 @@ func ProtoToPtrServicesFindOptions(protoOpts *pb.FindOptions) (*options.FindOpti
 			Sort: protoOpts.Sort,
 		},
 		PaginationOptions: options.PaginationOptions{
-			PageNum:  int(protoOpts.PageNum),
-			PageSize: int(protoOpts.PageSize),
+			Limit:	int(protoOpts.PageSize),
+			Offset: int(protoOpts.PageNum * protoOpts.PageSize),
 		},
 	}, nil
 }
diff --git a/pkg/items/dummy.go b/pkg/items/dummy.go
deleted file mode 100644
index fc1f725abc3e5f9c16164daca30f3334c16f9f86..0000000000000000000000000000000000000000
--- a/pkg/items/dummy.go
+++ /dev/null
@@ -1,17 +0,0 @@
-package items
-
-import "context"
-
-type FindResultDummy struct {
-	Items []*Item
-	Total int
-	Error error
-}
-type Dummy struct {
-	Items
-	FindResult *FindResultDummy
-}
-
-func (d *Dummy) Find(_ context.Context, _, _, _ string, _ *Filter, _ ...*FindOptions) ([]*Item, int, error) {
-	return d.FindResult.Items, d.FindResult.Total, d.FindResult.Error
-}
diff --git a/pkg/items/events.go b/pkg/items/events.go
index b697d60733031bc259ce0d5b2efc24f2bb63bc94..9a1eb9249ff9224eaa217fdfd8bf1cb12c9d68fc 100644
--- a/pkg/items/events.go
+++ b/pkg/items/events.go
@@ -7,11 +7,14 @@ import (
 )
 
 const (
-	EventCreateItem    = "create_item"
-	EventUpdateItem    = "update_item"
-	EventPublishItem   = "publish_item"
-	EventUnpublishItem = "unpublish_item"
-	EventDeleteItem    = "delete_item"
+	EventItemCreate    = "create_item"
+	EventItemUpdate    = "update_item"
+	EventItemPublish   = "publish_item"
+	EventItemUnpublish = "unpublish_item"
+	EventItemDelete    = "delete_item"
+	EventItemUndelete  = "item_undelete"
+	EventItemArchive   = "item_archive"
+	EventItemUnarchive = "item_unarchive"
 
 	DefaultEventSubject = "content.{{.EventType}}.{{.SpaceID}}.{{.EnvID}}.{{.CollectionID}}.{{.ItemID}}"
 )
diff --git a/pkg/items/middleware/access_logging_middleware.go b/pkg/items/middleware/access_logging_middleware.go
new file mode 100644
index 0000000000000000000000000000000000000000..2342a6d99249f427aa0f4ad6b4220a43ba872a4f
--- /dev/null
+++ b/pkg/items/middleware/access_logging_middleware.go
@@ -0,0 +1,418 @@
+// Code generated by gowrap. DO NOT EDIT.
+// template: ../../../assets/templates/middleware/access_log.tmpl
+// 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/access_log.tmpl -o access_logging_middleware.go -l ""
+
+import (
+	"context"
+	"time"
+
+	"git.perx.ru/perxis/perxis-go/pkg/auth"
+	"git.perx.ru/perxis/perxis-go/pkg/items"
+	"git.perx.ru/perxis/perxis-go/pkg/schema"
+	"go.uber.org/zap"
+)
+
+// accessLoggingMiddleware implements items.Items that is instrumented with logging
+type accessLoggingMiddleware struct {
+	logger *zap.Logger
+	next   items.Items
+}
+
+// AccessLoggingMiddleware instruments an implementation of the items.Items with simple logging
+func AccessLoggingMiddleware(logger *zap.Logger) Middleware {
+	return func(next items.Items) items.Items {
+		return &accessLoggingMiddleware{
+			next:   next,
+			logger: logger,
+		}
+	}
+}
+
+func (m *accessLoggingMiddleware) Aggregate(ctx context.Context, spaceId string, envId string, collectionId string, filter *items.Filter, options ...*items.AggregateOptions) (result map[string]interface{}, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Aggregate.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("spaceId", spaceId),
+		zap.Reflect("envId", envId),
+		zap.Reflect("collectionId", collectionId),
+		zap.Reflect("filter", filter),
+		zap.Reflect("options", options),
+	)
+
+	result, err = m.next.Aggregate(ctx, spaceId, envId, collectionId, filter, options...)
+
+	m.logger.Debug("Aggregate.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("result", result),
+		zap.Error(err),
+	)
+
+	return result, err
+}
+
+func (m *accessLoggingMiddleware) AggregatePublished(ctx context.Context, spaceId string, envId string, collectionId string, filter *items.Filter, options ...*items.AggregatePublishedOptions) (result map[string]interface{}, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("AggregatePublished.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("spaceId", spaceId),
+		zap.Reflect("envId", envId),
+		zap.Reflect("collectionId", collectionId),
+		zap.Reflect("filter", filter),
+		zap.Reflect("options", options),
+	)
+
+	result, err = m.next.AggregatePublished(ctx, spaceId, envId, collectionId, filter, options...)
+
+	m.logger.Debug("AggregatePublished.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("result", result),
+		zap.Error(err),
+	)
+
+	return result, err
+}
+
+func (m *accessLoggingMiddleware) Archive(ctx context.Context, item *items.Item, options ...*items.ArchiveOptions) (err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Archive.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("item", item),
+		zap.Reflect("options", options),
+	)
+
+	err = m.next.Archive(ctx, item, options...)
+
+	m.logger.Debug("Archive.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	)
+
+	return err
+}
+
+func (m *accessLoggingMiddleware) Create(ctx context.Context, item *items.Item, opts ...*items.CreateOptions) (created *items.Item, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Create.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("item", item),
+		zap.Reflect("opts", opts),
+	)
+
+	created, err = m.next.Create(ctx, item, opts...)
+
+	m.logger.Debug("Create.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("created", created),
+		zap.Error(err),
+	)
+
+	return created, err
+}
+
+func (m *accessLoggingMiddleware) Delete(ctx context.Context, item *items.Item, options ...*items.DeleteOptions) (err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Delete.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("item", item),
+		zap.Reflect("options", options),
+	)
+
+	err = m.next.Delete(ctx, item, options...)
+
+	m.logger.Debug("Delete.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	)
+
+	return err
+}
+
+func (m *accessLoggingMiddleware) Find(ctx context.Context, spaceId string, envId string, collectionId string, filter *items.Filter, options ...*items.FindOptions) (items []*items.Item, total int, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Find.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("spaceId", spaceId),
+		zap.Reflect("envId", envId),
+		zap.Reflect("collectionId", collectionId),
+		zap.Reflect("filter", filter),
+		zap.Reflect("options", options),
+	)
+
+	items, total, err = m.next.Find(ctx, spaceId, envId, collectionId, filter, options...)
+
+	m.logger.Debug("Find.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("items", items),
+		zap.Reflect("total", total),
+		zap.Error(err),
+	)
+
+	return items, total, err
+}
+
+func (m *accessLoggingMiddleware) FindArchived(ctx context.Context, spaceId string, envId string, collectionId string, filter *items.Filter, options ...*items.FindArchivedOptions) (items []*items.Item, total int, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("FindArchived.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("spaceId", spaceId),
+		zap.Reflect("envId", envId),
+		zap.Reflect("collectionId", collectionId),
+		zap.Reflect("filter", filter),
+		zap.Reflect("options", options),
+	)
+
+	items, total, err = m.next.FindArchived(ctx, spaceId, envId, collectionId, filter, options...)
+
+	m.logger.Debug("FindArchived.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("items", items),
+		zap.Reflect("total", total),
+		zap.Error(err),
+	)
+
+	return items, total, err
+}
+
+func (m *accessLoggingMiddleware) FindPublished(ctx context.Context, spaceId string, envId string, collectionId string, filter *items.Filter, options ...*items.FindPublishedOptions) (items []*items.Item, total int, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("FindPublished.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("spaceId", spaceId),
+		zap.Reflect("envId", envId),
+		zap.Reflect("collectionId", collectionId),
+		zap.Reflect("filter", filter),
+		zap.Reflect("options", options),
+	)
+
+	items, total, err = m.next.FindPublished(ctx, spaceId, envId, collectionId, filter, options...)
+
+	m.logger.Debug("FindPublished.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("items", items),
+		zap.Reflect("total", total),
+		zap.Error(err),
+	)
+
+	return items, total, err
+}
+
+func (m *accessLoggingMiddleware) Get(ctx context.Context, spaceId string, envId string, collectionId string, itemId string, options ...*items.GetOptions) (item *items.Item, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Get.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("spaceId", spaceId),
+		zap.Reflect("envId", envId),
+		zap.Reflect("collectionId", collectionId),
+		zap.Reflect("itemId", itemId),
+		zap.Reflect("options", options),
+	)
+
+	item, err = m.next.Get(ctx, spaceId, envId, collectionId, itemId, options...)
+
+	m.logger.Debug("Get.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("item", item),
+		zap.Error(err),
+	)
+
+	return item, err
+}
+
+func (m *accessLoggingMiddleware) GetPublished(ctx context.Context, spaceId string, envId string, collectionId string, itemId string, options ...*items.GetPublishedOptions) (item *items.Item, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("GetPublished.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("spaceId", spaceId),
+		zap.Reflect("envId", envId),
+		zap.Reflect("collectionId", collectionId),
+		zap.Reflect("itemId", itemId),
+		zap.Reflect("options", options),
+	)
+
+	item, err = m.next.GetPublished(ctx, spaceId, envId, collectionId, itemId, options...)
+
+	m.logger.Debug("GetPublished.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("item", item),
+		zap.Error(err),
+	)
+
+	return item, err
+}
+
+func (m *accessLoggingMiddleware) GetRevision(ctx context.Context, spaceId string, envId string, collectionId string, itemId string, revisionId string, options ...*items.GetRevisionOptions) (item *items.Item, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("GetRevision.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("spaceId", spaceId),
+		zap.Reflect("envId", envId),
+		zap.Reflect("collectionId", collectionId),
+		zap.Reflect("itemId", itemId),
+		zap.Reflect("revisionId", revisionId),
+		zap.Reflect("options", options),
+	)
+
+	item, err = m.next.GetRevision(ctx, spaceId, envId, collectionId, itemId, revisionId, options...)
+
+	m.logger.Debug("GetRevision.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("item", item),
+		zap.Error(err),
+	)
+
+	return item, err
+}
+
+func (m *accessLoggingMiddleware) Introspect(ctx context.Context, item *items.Item, opts ...*items.IntrospectOptions) (itm *items.Item, sch *schema.Schema, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Introspect.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("item", item),
+		zap.Reflect("opts", opts),
+	)
+
+	itm, sch, err = m.next.Introspect(ctx, item, opts...)
+
+	m.logger.Debug("Introspect.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("itm", itm),
+		zap.Reflect("sch", sch),
+		zap.Error(err),
+	)
+
+	return itm, sch, err
+}
+
+func (m *accessLoggingMiddleware) ListRevisions(ctx context.Context, spaceId string, envId string, collectionId string, itemId string, options ...*items.ListRevisionsOptions) (items []*items.Item, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("ListRevisions.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("spaceId", spaceId),
+		zap.Reflect("envId", envId),
+		zap.Reflect("collectionId", collectionId),
+		zap.Reflect("itemId", itemId),
+		zap.Reflect("options", options),
+	)
+
+	items, err = m.next.ListRevisions(ctx, spaceId, envId, collectionId, itemId, options...)
+
+	m.logger.Debug("ListRevisions.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("items", items),
+		zap.Error(err),
+	)
+
+	return items, err
+}
+
+func (m *accessLoggingMiddleware) Publish(ctx context.Context, item *items.Item, options ...*items.PublishOptions) (err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Publish.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("item", item),
+		zap.Reflect("options", options),
+	)
+
+	err = m.next.Publish(ctx, item, options...)
+
+	m.logger.Debug("Publish.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	)
+
+	return err
+}
+
+func (m *accessLoggingMiddleware) Unarchive(ctx context.Context, item *items.Item, options ...*items.UnarchiveOptions) (err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Unarchive.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("item", item),
+		zap.Reflect("options", options),
+	)
+
+	err = m.next.Unarchive(ctx, item, options...)
+
+	m.logger.Debug("Unarchive.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	)
+
+	return err
+}
+
+func (m *accessLoggingMiddleware) Undelete(ctx context.Context, item *items.Item, options ...*items.UndeleteOptions) (err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Undelete.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("item", item),
+		zap.Reflect("options", options),
+	)
+
+	err = m.next.Undelete(ctx, item, options...)
+
+	m.logger.Debug("Undelete.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	)
+
+	return err
+}
+
+func (m *accessLoggingMiddleware) Unpublish(ctx context.Context, item *items.Item, options ...*items.UnpublishOptions) (err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Unpublish.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("item", item),
+		zap.Reflect("options", options),
+	)
+
+	err = m.next.Unpublish(ctx, item, options...)
+
+	m.logger.Debug("Unpublish.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	)
+
+	return err
+}
+
+func (m *accessLoggingMiddleware) Update(ctx context.Context, item *items.Item, options ...*items.UpdateOptions) (err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Update.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("item", item),
+		zap.Reflect("options", options),
+	)
+
+	err = m.next.Update(ctx, item, options...)
+
+	m.logger.Debug("Update.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	)
+
+	return err
+}
diff --git a/pkg/items/middleware/error_logging_middleware.go b/pkg/items/middleware/error_logging_middleware.go
deleted file mode 100644
index 345d22ca2727d1516e4a7176e885195ab38f1d20..0000000000000000000000000000000000000000
--- a/pkg/items/middleware/error_logging_middleware.go
+++ /dev/null
@@ -1,211 +0,0 @@
-// 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/pkg/items -i Items -t ../../../assets/templates/middleware/error_log -o error_logging_middleware.go -l ""
-
-import (
-	"context"
-
-	"git.perx.ru/perxis/perxis-go/pkg/items"
-	"git.perx.ru/perxis/perxis-go/pkg/schema"
-	"go.uber.org/zap"
-)
-
-// errorLoggingMiddleware implements items.Items that is instrumented with logging
-type errorLoggingMiddleware struct {
-	logger *zap.Logger
-	next   items.Items
-}
-
-// ErrorLoggingMiddleware instruments an implementation of the items.Items with simple logging
-func ErrorLoggingMiddleware(logger *zap.Logger) Middleware {
-	return func(next items.Items) items.Items {
-		return &errorLoggingMiddleware{
-			next:   next,
-			logger: logger,
-		}
-	}
-}
-
-func (m *errorLoggingMiddleware) Aggregate(ctx context.Context, spaceId string, envId string, collectionId string, filter *items.Filter, options ...*items.AggregateOptions) (result map[string]interface{}, err error) {
-	logger := m.logger
-	defer func() {
-		if err != nil {
-			logger.Warn("response error", zap.Error(err))
-		}
-	}()
-	return m.next.Aggregate(ctx, spaceId, envId, collectionId, filter, options...)
-}
-
-func (m *errorLoggingMiddleware) AggregatePublished(ctx context.Context, spaceId string, envId string, collectionId string, filter *items.Filter, options ...*items.AggregatePublishedOptions) (result map[string]interface{}, err error) {
-	logger := m.logger
-	defer func() {
-		if err != nil {
-			logger.Warn("response error", zap.Error(err))
-		}
-	}()
-	return m.next.AggregatePublished(ctx, spaceId, envId, collectionId, filter, options...)
-}
-
-func (m *errorLoggingMiddleware) Archive(ctx context.Context, item *items.Item, options ...*items.ArchiveOptions) (err error) {
-	logger := m.logger
-	defer func() {
-		if err != nil {
-			logger.Warn("response error", zap.Error(err))
-		}
-	}()
-	return m.next.Archive(ctx, item, options...)
-}
-
-func (m *errorLoggingMiddleware) Create(ctx context.Context, item *items.Item, opts ...*items.CreateOptions) (created *items.Item, err error) {
-	logger := m.logger
-	defer func() {
-		if err != nil {
-			logger.Warn("response error", zap.Error(err))
-		}
-	}()
-	return m.next.Create(ctx, item, opts...)
-}
-
-func (m *errorLoggingMiddleware) Delete(ctx context.Context, item *items.Item, options ...*items.DeleteOptions) (err error) {
-	logger := m.logger
-	defer func() {
-		if err != nil {
-			logger.Warn("response error", zap.Error(err))
-		}
-	}()
-	return m.next.Delete(ctx, item, options...)
-}
-
-func (m *errorLoggingMiddleware) Find(ctx context.Context, spaceId string, envId string, collectionId string, filter *items.Filter, options ...*items.FindOptions) (items []*items.Item, total int, err error) {
-	logger := m.logger
-	defer func() {
-		if err != nil {
-			logger.Warn("response error", zap.Error(err))
-		}
-	}()
-	return m.next.Find(ctx, spaceId, envId, collectionId, filter, options...)
-}
-
-func (m *errorLoggingMiddleware) FindArchived(ctx context.Context, spaceId string, envId string, collectionId string, filter *items.Filter, options ...*items.FindArchivedOptions) (items []*items.Item, total int, err error) {
-	logger := m.logger
-	defer func() {
-		if err != nil {
-			logger.Warn("response error", zap.Error(err))
-		}
-	}()
-	return m.next.FindArchived(ctx, spaceId, envId, collectionId, filter, options...)
-}
-
-func (m *errorLoggingMiddleware) FindPublished(ctx context.Context, spaceId string, envId string, collectionId string, filter *items.Filter, options ...*items.FindPublishedOptions) (items []*items.Item, total int, err error) {
-	logger := m.logger
-	defer func() {
-		if err != nil {
-			logger.Warn("response error", zap.Error(err))
-		}
-	}()
-	return m.next.FindPublished(ctx, spaceId, envId, collectionId, filter, options...)
-}
-
-func (m *errorLoggingMiddleware) Get(ctx context.Context, spaceId string, envId string, collectionId string, itemId string, options ...*items.GetOptions) (item *items.Item, err error) {
-	logger := m.logger
-	defer func() {
-		if err != nil {
-			logger.Warn("response error", zap.Error(err))
-		}
-	}()
-	return m.next.Get(ctx, spaceId, envId, collectionId, itemId, options...)
-}
-
-func (m *errorLoggingMiddleware) GetPublished(ctx context.Context, spaceId string, envId string, collectionId string, itemId string, options ...*items.GetPublishedOptions) (item *items.Item, err error) {
-	logger := m.logger
-	defer func() {
-		if err != nil {
-			logger.Warn("response error", zap.Error(err))
-		}
-	}()
-	return m.next.GetPublished(ctx, spaceId, envId, collectionId, itemId, options...)
-}
-
-func (m *errorLoggingMiddleware) GetRevision(ctx context.Context, spaceId string, envId string, collectionId string, itemId string, revisionId string, options ...*items.GetRevisionOptions) (item *items.Item, err error) {
-	logger := m.logger
-	defer func() {
-		if err != nil {
-			logger.Warn("response error", zap.Error(err))
-		}
-	}()
-	return m.next.GetRevision(ctx, spaceId, envId, collectionId, itemId, revisionId, options...)
-}
-
-func (m *errorLoggingMiddleware) Introspect(ctx context.Context, item *items.Item, opts ...*items.IntrospectOptions) (itm *items.Item, sch *schema.Schema, err error) {
-	logger := m.logger
-	defer func() {
-		if err != nil {
-			logger.Warn("response error", zap.Error(err))
-		}
-	}()
-	return m.next.Introspect(ctx, item, opts...)
-}
-
-func (m *errorLoggingMiddleware) ListRevisions(ctx context.Context, spaceId string, envId string, collectionId string, itemId string, options ...*items.ListRevisionsOptions) (items []*items.Item, err error) {
-	logger := m.logger
-	defer func() {
-		if err != nil {
-			logger.Warn("response error", zap.Error(err))
-		}
-	}()
-	return m.next.ListRevisions(ctx, spaceId, envId, collectionId, itemId, options...)
-}
-
-func (m *errorLoggingMiddleware) Publish(ctx context.Context, item *items.Item, options ...*items.PublishOptions) (err error) {
-	logger := m.logger
-	defer func() {
-		if err != nil {
-			logger.Warn("response error", zap.Error(err))
-		}
-	}()
-	return m.next.Publish(ctx, item, options...)
-}
-
-func (m *errorLoggingMiddleware) Unarchive(ctx context.Context, item *items.Item, options ...*items.UnarchiveOptions) (err error) {
-	logger := m.logger
-	defer func() {
-		if err != nil {
-			logger.Warn("response error", zap.Error(err))
-		}
-	}()
-	return m.next.Unarchive(ctx, item, options...)
-}
-
-func (m *errorLoggingMiddleware) Undelete(ctx context.Context, item *items.Item, options ...*items.UndeleteOptions) (err error) {
-	logger := m.logger
-	defer func() {
-		if err != nil {
-			logger.Warn("response error", zap.Error(err))
-		}
-	}()
-	return m.next.Undelete(ctx, item, options...)
-}
-
-func (m *errorLoggingMiddleware) Unpublish(ctx context.Context, item *items.Item, options ...*items.UnpublishOptions) (err error) {
-	logger := m.logger
-	defer func() {
-		if err != nil {
-			logger.Warn("response error", zap.Error(err))
-		}
-	}()
-	return m.next.Unpublish(ctx, item, options...)
-}
-
-func (m *errorLoggingMiddleware) Update(ctx context.Context, item *items.Item, options ...*items.UpdateOptions) (err error) {
-	logger := m.logger
-	defer func() {
-		if err != nil {
-			logger.Warn("response error", zap.Error(err))
-		}
-	}()
-	return m.next.Update(ctx, item, options...)
-}
diff --git a/pkg/items/middleware/logging_middleware.go b/pkg/items/middleware/logging_middleware.go
index 6ed28f90053eab68a44b1e5ac79d5228d7bfe9be..8870912f5ee01671c026431c80fe1e091b36bbdd 100644
--- a/pkg/items/middleware/logging_middleware.go
+++ b/pkg/items/middleware/logging_middleware.go
@@ -1,744 +1,306 @@
-// 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/pkg/items -i Items -t ../../../assets/templates/middleware/access_log -o logging_middleware.go -l ""
-
 import (
 	"context"
-	"fmt"
-	"time"
 
-	"git.perx.ru/perxis/perxis-go/pkg/auth"
+	"git.perx.ru/perxis/perxis-go/id"
 	"git.perx.ru/perxis/perxis-go/pkg/items"
 	"git.perx.ru/perxis/perxis-go/pkg/schema"
+	logzap "git.perx.ru/perxis/perxis-go/zap"
 	"go.uber.org/zap"
-	"go.uber.org/zap/zapcore"
 )
 
-// loggingMiddleware implements items.Items that is instrumented with logging
 type loggingMiddleware struct {
 	logger *zap.Logger
 	next   items.Items
 }
 
-// LoggingMiddleware instruments an implementation of the items.Items with simple logging
 func LoggingMiddleware(logger *zap.Logger) Middleware {
 	return func(next items.Items) items.Items {
 		return &loggingMiddleware{
 			next:   next,
-			logger: logger,
+			logger: logger.With(logzap.Component("items")),
 		}
 	}
 }
 
 func (m *loggingMiddleware) Aggregate(ctx context.Context, spaceId string, envId string, collectionId string, filter *items.Filter, options ...*items.AggregateOptions) (result map[string]interface{}, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":          ctx,
-		"spaceId":      spaceId,
-		"envId":        envId,
-		"collectionId": collectionId,
-		"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("Aggregate.Request", fields...)
+	logger := m.logger.With(
+		logzap.CallerFromContext(ctx),
+	)
 
 	result, err = m.next.Aggregate(ctx, spaceId, envId, collectionId, filter, options...)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
+	if err != nil {
+		logger.Error("Failed to aggregate", zap.Error(err))
+		return
 	}
 
-	for k, v := range map[string]interface{}{
-		"result": result,
-		"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("Aggregate.Response", fields...)
-
 	return result, err
 }
 
 func (m *loggingMiddleware) AggregatePublished(ctx context.Context, spaceId string, envId string, collectionId string, filter *items.Filter, options ...*items.AggregatePublishedOptions) (result map[string]interface{}, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":          ctx,
-		"spaceId":      spaceId,
-		"envId":        envId,
-		"collectionId": collectionId,
-		"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("AggregatePublished.Request", fields...)
+	logger := m.logger.With(
+		logzap.CallerFromContext(ctx),
+	)
 
 	result, err = m.next.AggregatePublished(ctx, spaceId, envId, collectionId, filter, options...)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
+	if err != nil {
+		logger.Error("Failed to aggregate_published", zap.Error(err))
+		return
 	}
 
-	for k, v := range map[string]interface{}{
-		"result": result,
-		"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("AggregatePublished.Response", fields...)
-
 	return result, err
 }
 
 func (m *loggingMiddleware) Archive(ctx context.Context, item *items.Item, options ...*items.ArchiveOptions) (err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":     ctx,
-		"item":    item,
-		"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("Archive.Request", fields...)
+	logger := m.logger.With(
+		logzap.CallerFromContext(ctx),
+		logzap.Event(items.EventItemArchive),
+		logzap.Object(item),
+	)
 
 	err = m.next.Archive(ctx, item, options...)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
+	if err != nil {
+		logger.Error("Failed to archive", zap.Error(err), logzap.Channels(logzap.Userlog, logzap.Syslog))
+		return
 	}
 
-	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("Archive.Response", fields...)
-
+	logger.Info("Successfully archived", logzap.Channels(logzap.Userlog))
 	return err
 }
 
 func (m *loggingMiddleware) Create(ctx context.Context, item *items.Item, opts ...*items.CreateOptions) (created *items.Item, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":  ctx,
-		"item": item,
-		"opts": opts} {
-		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("Create.Request", fields...)
+	logger := m.logger.With(
+		logzap.CallerFromContext(ctx),
+		logzap.Event(items.EventItemCreate),
+	)
 
 	created, err = m.next.Create(ctx, item, opts...)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"created": created,
-		"err":     err} {
-		if k == "err" {
-			err, _ := v.(error)
-			fields = append(fields, zap.Error(err))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
+	if err != nil {
+		logger.Error("Failed to create", zap.Error(err), logzap.Channels(logzap.Userlog, logzap.Syslog), logzap.Object(item))
+		return
 	}
 
-	m.logger.Debug("Create.Response", fields...)
-
+	logger.Info("Successfully created", logzap.Channels(logzap.Userlog), logzap.Object(created))
 	return created, err
 }
 
 func (m *loggingMiddleware) Delete(ctx context.Context, item *items.Item, options ...*items.DeleteOptions) (err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":     ctx,
-		"item":    item,
-		"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("Delete.Request", fields...)
+	logger := m.logger.With(
+		logzap.CallerFromContext(ctx),
+		logzap.Event(items.EventItemDelete),
+		logzap.Object(item),
+	)
 
 	err = m.next.Delete(ctx, item, options...)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
+	if err != nil {
+		logger.Error("Failed to delete", zap.Error(err), logzap.Channels(logzap.Userlog, logzap.Syslog))
+		return
 	}
 
-	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...)
-
+	logger.Info("Successfully deleted", logzap.Channels(logzap.Userlog))
 	return err
 }
 
 func (m *loggingMiddleware) Find(ctx context.Context, spaceId string, envId string, collectionId string, filter *items.Filter, options ...*items.FindOptions) (items []*items.Item, total int, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":          ctx,
-		"spaceId":      spaceId,
-		"envId":        envId,
-		"collectionId": collectionId,
-		"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...)
+	logger := m.logger.With(
+		logzap.CallerFromContext(ctx),
+	)
 
 	items, total, err = m.next.Find(ctx, spaceId, envId, collectionId, filter, options...)
 
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"items": items,
-		"total": total,
-		"err":   err} {
-		if k == "err" {
-			err, _ := v.(error)
-			fields = append(fields, zap.Error(err))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
+	if err != nil {
+		logger.Error("Failed to find", zap.Error(err))
+		return
 	}
 
-	m.logger.Debug("Find.Response", fields...)
-
 	return items, total, err
 }
 
 func (m *loggingMiddleware) FindArchived(ctx context.Context, spaceId string, envId string, collectionId string, filter *items.Filter, options ...*items.FindArchivedOptions) (items []*items.Item, total int, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":          ctx,
-		"spaceId":      spaceId,
-		"envId":        envId,
-		"collectionId": collectionId,
-		"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("FindArchived.Request", fields...)
+	logger := m.logger.With(
+		logzap.CallerFromContext(ctx),
+	)
 
 	items, total, err = m.next.FindArchived(ctx, spaceId, envId, collectionId, filter, options...)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"items": items,
-		"total": total,
-		"err":   err} {
-		if k == "err" {
-			err, _ := v.(error)
-			fields = append(fields, zap.Error(err))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
+	if err != nil {
+		logger.Error("Failed to find_archived", zap.Error(err))
+		return
 	}
 
-	m.logger.Debug("FindArchived.Response", fields...)
-
 	return items, total, err
 }
 
 func (m *loggingMiddleware) FindPublished(ctx context.Context, spaceId string, envId string, collectionId string, filter *items.Filter, options ...*items.FindPublishedOptions) (items []*items.Item, total int, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":          ctx,
-		"spaceId":      spaceId,
-		"envId":        envId,
-		"collectionId": collectionId,
-		"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("FindPublished.Request", fields...)
+	logger := m.logger.With(
+		logzap.CallerFromContext(ctx),
+	)
 
 	items, total, err = m.next.FindPublished(ctx, spaceId, envId, collectionId, filter, options...)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"items": items,
-		"total": total,
-		"err":   err} {
-		if k == "err" {
-			err, _ := v.(error)
-			fields = append(fields, zap.Error(err))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
+	if err != nil {
+		logger.Error("Failed to findpublished", zap.Error(err))
+		return
 	}
 
-	m.logger.Debug("FindPublished.Response", fields...)
-
 	return items, total, err
 }
 
 func (m *loggingMiddleware) Get(ctx context.Context, spaceId string, envId string, collectionId string, itemId string, options ...*items.GetOptions) (item *items.Item, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":          ctx,
-		"spaceId":      spaceId,
-		"envId":        envId,
-		"collectionId": collectionId,
-		"itemId":       itemId,
-		"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("Get.Request", fields...)
+	logger := m.logger.With(
+		logzap.CallerFromContext(ctx),
+		logzap.Object(id.NewItemId(spaceId, envId, collectionId, itemId)),
+	)
 
 	item, err = m.next.Get(ctx, spaceId, envId, collectionId, itemId, options...)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
+	if err != nil {
+		logger.Error("Failed to get", zap.Error(err))
+		return
 	}
 
-	for k, v := range map[string]interface{}{
-		"item": item,
-		"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("Get.Response", fields...)
-
 	return item, err
 }
 
 func (m *loggingMiddleware) GetPublished(ctx context.Context, spaceId string, envId string, collectionId string, itemId string, options ...*items.GetPublishedOptions) (item *items.Item, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":          ctx,
-		"spaceId":      spaceId,
-		"envId":        envId,
-		"collectionId": collectionId,
-		"itemId":       itemId,
-		"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("GetPublished.Request", fields...)
+	logger := m.logger.With(
+		logzap.CallerFromContext(ctx),
+		logzap.Object(id.NewItemId(spaceId, envId, collectionId, itemId)),
+	)
 
 	item, err = m.next.GetPublished(ctx, spaceId, envId, collectionId, itemId, options...)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
+	if err != nil {
+		logger.Error("Failed to get published", zap.Error(err))
+		return
 	}
 
-	for k, v := range map[string]interface{}{
-		"item": item,
-		"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("GetPublished.Response", fields...)
-
 	return item, err
 }
 
 func (m *loggingMiddleware) GetRevision(ctx context.Context, spaceId string, envId string, collectionId string, itemId string, revisionId string, options ...*items.GetRevisionOptions) (item *items.Item, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":          ctx,
-		"spaceId":      spaceId,
-		"envId":        envId,
-		"collectionId": collectionId,
-		"itemId":       itemId,
-		"revisionId":   revisionId,
-		"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("GetRevision.Request", fields...)
+	logger := m.logger.With(
+		logzap.CallerFromContext(ctx),
+		logzap.Object(id.NewItemId(spaceId, envId, collectionId, itemId)),
+	)
 
 	item, err = m.next.GetRevision(ctx, spaceId, envId, collectionId, itemId, revisionId, options...)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
+	if err != nil {
+		logger.Error("Failed to get revision", zap.Error(err))
+		return
 	}
 
-	for k, v := range map[string]interface{}{
-		"item": item,
-		"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("GetRevision.Response", fields...)
-
 	return item, err
 }
 
 func (m *loggingMiddleware) Introspect(ctx context.Context, item *items.Item, opts ...*items.IntrospectOptions) (itm *items.Item, sch *schema.Schema, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":  ctx,
-		"item": item,
-		"opts": opts} {
-		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("Introspect.Request", fields...)
+	logger := m.logger.With(
+		logzap.CallerFromContext(ctx),
+		logzap.Object(item),
+	)
 
 	itm, sch, err = m.next.Introspect(ctx, item, opts...)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"itm": itm,
-		"sch": sch,
-		"err": err} {
-		if k == "err" {
-			err, _ := v.(error)
-			fields = append(fields, zap.Error(err))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
+	if err != nil {
+		logger.Error("Failed to introspect", zap.Error(err))
+		return
 	}
 
-	m.logger.Debug("Introspect.Response", fields...)
-
 	return itm, sch, err
 }
 
 func (m *loggingMiddleware) ListRevisions(ctx context.Context, spaceId string, envId string, collectionId string, itemId string, options ...*items.ListRevisionsOptions) (items []*items.Item, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":          ctx,
-		"spaceId":      spaceId,
-		"envId":        envId,
-		"collectionId": collectionId,
-		"itemId":       itemId,
-		"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("ListRevisions.Request", fields...)
+	logger := m.logger.With(
+		logzap.CallerFromContext(ctx),
+		logzap.Object(id.NewItemId(spaceId, envId, collectionId, itemId)),
+	)
 
 	items, err = m.next.ListRevisions(ctx, spaceId, envId, collectionId, itemId, options...)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
+	if err != nil {
+		logger.Error("Failed to list revisions", zap.Error(err))
+		return
 	}
 
-	for k, v := range map[string]interface{}{
-		"items": items,
-		"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("ListRevisions.Response", fields...)
-
 	return items, err
 }
 
 func (m *loggingMiddleware) Publish(ctx context.Context, item *items.Item, options ...*items.PublishOptions) (err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":     ctx,
-		"item":    item,
-		"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("Publish.Request", fields...)
+	logger := m.logger.With(
+		logzap.CallerFromContext(ctx),
+		logzap.Event(items.EventItemPublish),
+		logzap.Object(item),
+	)
 
 	err = m.next.Publish(ctx, item, options...)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
+	if err != nil {
+		logger.Error("Failed to publish", zap.Error(err), logzap.Channels(logzap.Userlog, logzap.Syslog))
+		return
 	}
 
-	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("Publish.Response", fields...)
-
+	logger.Info("Successfully published", logzap.Channels(logzap.Userlog))
 	return err
 }
 
 func (m *loggingMiddleware) Unarchive(ctx context.Context, item *items.Item, options ...*items.UnarchiveOptions) (err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":     ctx,
-		"item":    item,
-		"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("Unarchive.Request", fields...)
+	logger := m.logger.With(
+		logzap.CallerFromContext(ctx),
+		logzap.Event(items.EventItemUnarchive),
+		logzap.Object(item),
+	)
 
 	err = m.next.Unarchive(ctx, item, options...)
-
-	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))
+	if err != nil {
+		logger.Error("Failed to unarchive", zap.Error(err), logzap.Channels(logzap.Userlog, logzap.Syslog))
+		return
 	}
 
-	m.logger.Debug("Unarchive.Response", fields...)
-
+	logger.Info("Successfully unarchived", logzap.Channels(logzap.Userlog))
 	return err
 }
 
 func (m *loggingMiddleware) Undelete(ctx context.Context, item *items.Item, options ...*items.UndeleteOptions) (err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":     ctx,
-		"item":    item,
-		"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("Undelete.Request", fields...)
+	logger := m.logger.With(
+		logzap.CallerFromContext(ctx),
+		logzap.Event(items.EventItemUndelete),
+		logzap.Object(item),
+	)
 
 	err = m.next.Undelete(ctx, item, options...)
-
-	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))
+	if err != nil {
+		logger.Error("Failed to undelete", zap.Error(err), logzap.Channels(logzap.Userlog, logzap.Syslog))
+		return
 	}
 
-	m.logger.Debug("Undelete.Response", fields...)
-
+	logger.Info("Successfully undeleted", logzap.Channels(logzap.Userlog))
 	return err
 }
 
 func (m *loggingMiddleware) Unpublish(ctx context.Context, item *items.Item, options ...*items.UnpublishOptions) (err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":     ctx,
-		"item":    item,
-		"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("Unpublish.Request", fields...)
+	logger := m.logger.With(
+		logzap.CallerFromContext(ctx),
+		logzap.Event(items.EventItemUnpublish),
+		logzap.Object(item),
+	)
 
 	err = m.next.Unpublish(ctx, item, options...)
-
-	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))
+	if err != nil {
+		logger.Error("Failed to unpublish", zap.Error(err), logzap.Channels(logzap.Userlog, logzap.Syslog))
+		return
 	}
 
-	m.logger.Debug("Unpublish.Response", fields...)
-
+	logger.Info("Successfully unpublished", logzap.Channels(logzap.Userlog))
 	return err
 }
 
 func (m *loggingMiddleware) Update(ctx context.Context, item *items.Item, options ...*items.UpdateOptions) (err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":     ctx,
-		"item":    item,
-		"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("Update.Request", fields...)
+	logger := m.logger.With(
+		logzap.CallerFromContext(ctx),
+		logzap.Event(items.EventItemUpdate),
+		logzap.Object(item),
+	)
 
 	err = m.next.Update(ctx, item, options...)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
+	if err != nil {
+		logger.Error("Failed to update", zap.Error(err), logzap.Channels(logzap.Userlog, logzap.Syslog))
+		return
 	}
 
-	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("Update.Response", fields...)
-
+	logger.Info("Successfully updated", logzap.Channels(logzap.Userlog))
 	return err
 }
diff --git a/pkg/items/middleware/middleware.go b/pkg/items/middleware/middleware.go
index 52a1c1218d1605dc52ae80342684bb0339e63103..2ce07ef94ff532af1ed3353ef5bdba6e5f6871aa 100644
--- a/pkg/items/middleware/middleware.go
+++ b/pkg/items/middleware/middleware.go
@@ -1,10 +1,10 @@
 // Code generated by gowrap. DO NOT EDIT.
-// template: ../../../assets/templates/middleware/middleware
+// template: ../../../assets/templates/middleware/middleware.tmpl
 // 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/middleware -o middleware.go -l ""
+//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/items -i Items -t ../../../assets/templates/middleware/middleware.tmpl -o middleware.go -l ""
 
 import (
 	"git.perx.ru/perxis/perxis-go/pkg/items"
@@ -17,12 +17,11 @@ func WithLog(s items.Items, logger *zap.Logger, log_access bool) items.Items {
 	if logger == nil {
 		logger = zap.NewNop()
 	}
-
 	logger = logger.Named("Items")
-	s = ErrorLoggingMiddleware(logger)(s)
 	if log_access {
-		s = LoggingMiddleware(logger)(s)
+		s = AccessLoggingMiddleware(logger)(s)
 	}
+	s = LoggingMiddleware(logger)(s)
 	s = RecoveringMiddleware(logger)(s)
 	return s
 }
diff --git a/pkg/items/pagination.go b/pkg/items/pagination.go
index 1910a34ba67c21cb1ebf54923ebe750af0fd10d2..c8c4bb16d2da0d2d10e3d98422aaf345ffbf7c21 100644
--- a/pkg/items/pagination.go
+++ b/pkg/items/pagination.go
@@ -16,9 +16,7 @@ type BatchProcessor struct {
 	FindPublishedOptions         *FindPublishedOptions
 	Filter                       *Filter
 
-	// Deprecated использовать offset, limit
-	pageSize, pageNum int
-
+	limit     int
 	sort      []string
 	processed int
 }
@@ -39,7 +37,7 @@ func (b *BatchProcessor) getBatch(ctx context.Context) ([]*Item, bool, error) {
 				Regular:     b.FindPublishedOptions.Regular,
 				Hidden:      b.FindPublishedOptions.Hidden,
 				Templates:   b.FindPublishedOptions.Templates,
-				FindOptions: *options.NewFindOptions(b.pageNum, b.pageSize, b.sort...),
+				FindOptions: *options.New(b.processed, b.limit, b.sort...),
 			},
 		)
 	} else {
@@ -54,14 +52,13 @@ func (b *BatchProcessor) getBatch(ctx context.Context) ([]*Item, bool, error) {
 				Regular:     b.FindOptions.Regular,
 				Hidden:      b.FindOptions.Hidden,
 				Templates:   b.FindOptions.Templates,
-				FindOptions: *options.NewFindOptions(b.pageNum, b.pageSize, b.sort...),
+				FindOptions: *options.New(b.processed, b.limit, b.sort...),
 			},
 		)
 	}
 
 	if err == nil {
 		b.processed += len(res)
-		b.pageNum++
 	}
 
 	return res, b.processed != total, err
@@ -86,32 +83,29 @@ func (b *BatchProcessor) next(ctx context.Context) (res []*Item, next bool, err
 }
 
 func (b *BatchProcessor) reducePageSize() bool {
-	if b.pageSize == 1 {
+	if b.limit == 1 {
 		return false
 	}
 
-	b.pageNum = 2 * b.pageNum
-	b.pageSize = b.pageSize / 2
-
+	b.limit /= 2
 	return true
 }
 
 func (b *BatchProcessor) Do(ctx context.Context, f func(batch []*Item) error) (int, error) {
-
 	if b.FindOptions == nil && b.FindPublishedOptions == nil {
 		b.FindOptions = new(FindOptions)
 	}
 	if b.FindOptions != nil {
-		b.pageSize = b.FindOptions.PageSize
+		b.limit = b.FindOptions.Limit
 		b.sort = b.FindOptions.Sort
 	}
 	if b.FindPublishedOptions != nil {
-		b.pageSize = b.FindPublishedOptions.PageSize
+		b.limit = b.FindPublishedOptions.Limit
 		b.sort = b.FindPublishedOptions.Sort
 	}
 
-	if b.pageSize == 0 {
-		b.pageSize = 128
+	if b.limit == 0 {
+		b.limit = 128
 	}
 
 	if b.Filter != nil && (len(b.Filter.ID) > 0 || len(b.Filter.Q) > 0) && !data.Contains("_id", b.sort) {
diff --git a/pkg/items/pagination_test.go b/pkg/items/pagination_test.go
index bf13af39e00bd19550ddd49bae3e165b5cfa9fff..008b17795cc1c57d4e2dcf6bb3507bee8b3f1646 100644
--- a/pkg/items/pagination_test.go
+++ b/pkg/items/pagination_test.go
@@ -11,25 +11,51 @@ import (
 )
 
 func TestBatchProcessor(t *testing.T) {
+	t.Run("EmptyResults", func(t *testing.T) {
+		itemssvc := &Stub{
+			FindResult: func(req StubFindRequest) StubFindResult {
+				return StubFindResult{Items: nil, Total: 0, Error: nil}
+			},
+		}
 
-	itemssvc := &Dummy{FindResult: &FindResultDummy{Items: nil, Total: 0, Error: nil}}
+		b := &BatchProcessor{
+			Items:        itemssvc,
+			SpaceID:      "sp",
+			EnvID:        environments.DefaultEnvironment,
+			CollectionID: "col",
+			FindOptions: &FindOptions{
+				Regular:     true,
+				Hidden:      true,
+				Templates:   true,
+				FindOptions: *options.NewFindOptions(0, 10),
+			},
+			Filter: NewFilter("a > 5"),
+		}
 
-	b := &BatchProcessor{
-		Items:        itemssvc,
-		SpaceID:      "sp",
-		EnvID:        environments.DefaultEnvironment,
-		CollectionID: "col",
-		FindOptions: &FindOptions{
-			Regular:     true,
-			Hidden:      true,
-			Templates:   true,
-			FindOptions: *options.NewFindOptions(0, 10),
-		},
-		Filter: NewFilter("a > 5"),
-	}
+		var counter int
+		_, err := b.Do(context.Background(), func(batch []*Item) error { counter++; return nil })
+		require.NoError(t, err)
+		assert.Equal(t, 0, counter)
+	})
 
-	var counter int
-	_, err := b.Do(context.Background(), func(batch []*Item) error { counter++; return nil })
-	require.NoError(t, err)
-	assert.Equal(t, 0, counter)
+	t.Run("With FindOptions", func(t *testing.T) {
+		itemssvc := &Stub{
+			FindResult: func(req StubFindRequest) StubFindResult {
+				fo := MergeFindOptions(req.Options...)
+				return StubFindResult{Items: make([]*Item, fo.Limit), Total: 1000, Error: nil}
+			},
+		}
+		b := &BatchProcessor{
+			Items:        itemssvc,
+			SpaceID:      "sp",
+			EnvID:        environments.DefaultEnvironment,
+			CollectionID: "col",
+			FindOptions:  &FindOptions{FindOptions: *options.New(0, 25)},
+		}
+
+		var counter int
+		_, err := b.Do(context.Background(), func(batch []*Item) error { counter++; return nil })
+		require.NoError(t, err)
+		assert.Equal(t, 1000/25, counter)
+	})
 }
diff --git a/pkg/items/stub.go b/pkg/items/stub.go
new file mode 100644
index 0000000000000000000000000000000000000000..33573514b655a6084f636403a581bad46b130172
--- /dev/null
+++ b/pkg/items/stub.go
@@ -0,0 +1,35 @@
+package items
+
+import (
+	"context"
+)
+
+type StubFindRequest struct {
+	Context                context.Context
+	SpaceID, EnvID, CollID string
+	Filter                 *Filter
+	Options                []*FindOptions
+}
+
+type StubFindResult struct {
+	Items []*Item
+	Total int
+	Error error
+}
+
+type Stub struct {
+	Items
+	FindResult func(req StubFindRequest) StubFindResult
+}
+
+func (d *Stub) Find(ctx context.Context, spaceID, envID, collID string, filter *Filter, opts ...*FindOptions) ([]*Item, int, error) {
+	res := d.FindResult(StubFindRequest{
+		Context: ctx,
+		SpaceID: spaceID,
+		EnvID:   envID,
+		CollID:  collID,
+		Filter:  filter,
+		Options: opts,
+	})
+	return res.Items, res.Total, res.Error
+}
diff --git a/pkg/items/transport/grpc/protobuf_type_converters.microgen.go b/pkg/items/transport/grpc/protobuf_type_converters.microgen.go
index 0b214cc93c44392ad1490ced7665dd1b595b76b3..ade05bfffeb4a1c745f80338e7faac2287b067a2 100644
--- a/pkg/items/transport/grpc/protobuf_type_converters.microgen.go
+++ b/pkg/items/transport/grpc/protobuf_type_converters.microgen.go
@@ -144,39 +144,11 @@ func ProtoToPtrFilter(protoFilter *pb.Filter) (*service.Filter, error) {
 }
 
 func PtrServicesFindOptionsToProto(opts *options.FindOptions) (*pbcommon.FindOptions, error) {
-	if opts == nil {
-		return nil, nil
-	}
-	return &pbcommon.FindOptions{
-		Sort:          opts.Sort,
-		PageNum:       int32(opts.PageNum),
-		PageSize:      int32(opts.PageSize),
-		Offset:        int32(opts.Offset),
-		Limit:         int32(opts.Limit),
-		Fields:        opts.Fields,
-		ExcludeFields: opts.ExcludeFields,
-	}, nil
+	return options.FindOptionsToPB(opts), nil
 }
 
 func ProtoToPtrServicesFindOptions(protoOpts *pbcommon.FindOptions) (*options.FindOptions, error) {
-	if protoOpts == nil {
-		return nil, nil
-	}
-	return &options.FindOptions{
-		SortOptions: options.SortOptions{
-			Sort: protoOpts.Sort,
-		},
-		PaginationOptions: options.PaginationOptions{
-			PageNum:  int(protoOpts.PageNum),
-			PageSize: int(protoOpts.PageSize),
-			Offset:   int(protoOpts.Offset),
-			Limit:    int(protoOpts.Limit),
-		},
-		FieldOptions: options.FieldOptions{
-			Fields:        protoOpts.Fields,
-			ExcludeFields: protoOpts.ExcludeFields,
-		},
-	}, nil
+	return options.FindOptionsFromPB(protoOpts), nil
 }
 
 func ListPtrItemToProto(items []*service.Item) ([]*pb.Item, error) {
diff --git a/pkg/options/options.go b/pkg/options/options.go
index 066d3f8aa347a4b08fb901c87e2fc9afee7aa15f..d18ca338c01a7d0e2117a0e09f47d1a21421bdd1 100644
--- a/pkg/options/options.go
+++ b/pkg/options/options.go
@@ -13,10 +13,6 @@ type SortOptions struct {
 
 // PaginationOptions настройки возвращаемых страниц результатов
 type PaginationOptions struct {
-	//Deprecated
-	PageNum  int
-	PageSize int
-
 	Limit  int
 	Offset int
 }
@@ -39,12 +35,18 @@ type FindOptions struct {
 	FieldOptions
 }
 
+// Deprecated использовать New
 // NewFindOptions создает новые результаты поиска
 func NewFindOptions(pageNum, pageSize int, sort ...string) *FindOptions {
+	return New(pageNum*pageSize, pageSize, sort...)
+}
+
+// New создает новые параметры поиска
+func New(offset, limit int, sort ...string) *FindOptions {
 	return &FindOptions{
 		PaginationOptions: PaginationOptions{
-			PageNum:  pageNum,
-			PageSize: pageSize,
+			Offset: offset,
+			Limit:  limit,
 		},
 		SortOptions: SortOptions{
 			Sort: sort,
@@ -95,8 +97,8 @@ func FindOptionsFromPB(protoOpts *commonpb.FindOptions) *FindOptions {
 			Sort: protoOpts.Sort,
 		},
 		PaginationOptions: PaginationOptions{
-			Offset: int(protoOpts.Offset),
-			Limit:  int(protoOpts.Limit),
+			Limit:    int(protoOpts.Limit),
+			Offset:   int(protoOpts.Offset),
 		},
 		FieldOptions: FieldOptions{
 			Fields:        protoOpts.Fields,
@@ -104,9 +106,9 @@ func FindOptionsFromPB(protoOpts *commonpb.FindOptions) *FindOptions {
 		},
 	}
 
-	if fo.Offset == 0 && fo.Limit == 0 && fo.PageSize != 0 {
-		fo.Offset = fo.PageSize * (fo.PageNum - 1)
-		fo.Limit = fo.PageSize
+	if fo.Offset == 0 && fo.Limit == 0 {
+		fo.Offset = int(protoOpts.PageSize * protoOpts.PageNum)
+		fo.Limit = int(protoOpts.PageSize)
 	}
 
 	return fo
@@ -118,21 +120,11 @@ func FindOptionsToPB(opts *FindOptions) *commonpb.FindOptions {
 	}
 	fo := &commonpb.FindOptions{
 		Sort:          opts.Sort,
-		PageNum:       int32(opts.PageNum),
-		PageSize:      int32(opts.PageSize),
 		Offset:        int32(opts.Offset),
 		Limit:         int32(opts.Limit),
 		Fields:        opts.Fields,
 		ExcludeFields: opts.ExcludeFields,
 	}
-	if fo.PageSize != 0 && fo.Limit == 0 {
-		fo.Limit = fo.PageSize
-		fo.Offset = fo.PageSize * (fo.PageNum - 1)
-	}
-	if fo.Limit != 0 && fo.PageSize == 0 {
-		fo.PageSize = fo.Limit
-		fo.PageNum = fo.Offset / fo.Limit
-	}
 	return fo
 }
 
@@ -156,11 +148,9 @@ func MergeSortOptions(options ...SortOptions) SortOptions {
 func MergePaginationOptions(options ...PaginationOptions) PaginationOptions {
 	fo := PaginationOptions{}
 	for _, opt := range options {
-		if opt.PageSize == 0 && opt.PageNum == 0 && opt.Offset == 0 && opt.Limit == 0 {
+		if opt.Offset == 0 && opt.Limit == 0 {
 			continue
 		}
-		fo.PageNum = opt.PageNum
-		fo.PageSize = opt.PageSize
 		fo.Offset = opt.Offset
 		fo.Limit = opt.Limit
 	}
diff --git a/pkg/options/options_test.go b/pkg/options/options_test.go
index 981849a0e2625a8bd7e796a22eaa8529d606ef01..d2227a875a837c67006eab028f235afcbd1efe5e 100644
--- a/pkg/options/options_test.go
+++ b/pkg/options/options_test.go
@@ -7,7 +7,6 @@ import (
 )
 
 func TestOptions_MergePaginationOptions(t *testing.T) {
-
 	var tt = []struct {
 		name     string
 		options  []PaginationOptions
@@ -24,29 +23,29 @@ func TestOptions_MergePaginationOptions(t *testing.T) {
 			expected: PaginationOptions{},
 		},
 		{
-			name:     "One option",
-			options:  []PaginationOptions{{PageNum: 10, PageSize: 100}},
-			expected: PaginationOptions{PageNum: 10, PageSize: 100},
+			name:     "One limit/offset option",
+			options:  []PaginationOptions{{Limit: 10, Offset: 100}},
+			expected: PaginationOptions{Limit: 10, Offset: 100},
 		},
 		{
-			name:     "Merge #1",
-			options:  []PaginationOptions{{PageNum: 0, PageSize: 0}, {PageNum: 10, PageSize: 100}},
-			expected: PaginationOptions{PageNum: 10, PageSize: 100},
+			name:     "Merge limit/offset #1",
+			options:  []PaginationOptions{{Limit: 0, Offset: 0}, {Limit: 10, Offset: 100}},
+			expected: PaginationOptions{Limit: 10, Offset: 100},
 		},
 		{
-			name:     "Merge #2",
-			options:  []PaginationOptions{{PageNum: 10, PageSize: 100}, {PageNum: 0, PageSize: 0}},
-			expected: PaginationOptions{PageNum: 10, PageSize: 100},
+			name:     "Merge limit/offset #2",
+			options:  []PaginationOptions{{Limit: 10, Offset: 100}, {Limit: 0, Offset: 0}},
+			expected: PaginationOptions{Limit: 10, Offset: 100},
 		},
 		{
-			name:     "Merge #3",
-			options:  []PaginationOptions{{PageNum: 0, PageSize: 0}, {PageNum: 10, PageSize: 100}, {PageNum: 0, PageSize: 0}},
-			expected: PaginationOptions{PageNum: 10, PageSize: 100},
+			name:     "Merge limit/offset #3",
+			options:  []PaginationOptions{{Limit: 0, Offset: 0}, {Limit: 10, Offset: 100}, {Limit: 0, Offset: 0}},
+			expected: PaginationOptions{Limit: 10, Offset: 100},
 		},
 		{
-			name:     "Merge #4",
-			options:  []PaginationOptions{{PageNum: 10, PageSize: 100}, {}},
-			expected: PaginationOptions{PageNum: 10, PageSize: 100},
+			name:     "Merge limit/offset #4",
+			options:  []PaginationOptions{{Limit: 10, Offset: 100}, {}},
+			expected: PaginationOptions{Limit: 10, Offset: 100},
 		},
 	}
 
diff --git a/pkg/organizations/transport/grpc/protobuf_type_converters.microgen.go b/pkg/organizations/transport/grpc/protobuf_type_converters.microgen.go
index 2c024a9e6973181ccb5ff683b14be4aaad98792c..9a35f86e871d4e9b7792aa31590fd8f1b1fd0e12 100644
--- a/pkg/organizations/transport/grpc/protobuf_type_converters.microgen.go
+++ b/pkg/organizations/transport/grpc/protobuf_type_converters.microgen.go
@@ -61,33 +61,11 @@ func ProtoToPtrFilter(protoFilter *pb.Filter) (*organizations.Filter, error) {
 }
 
 func PtrServicesFindOptionsToProto(opts *options.FindOptions) (*common.FindOptions, error) {
-	if opts == nil {
-		return nil, nil
-	}
-	return &common.FindOptions{
-		Sort:     opts.Sort,
-		PageNum:  int32(opts.PageNum),
-		PageSize: int32(opts.PageSize),
-		Offset:   int32(opts.Offset),
-		Limit:    int32(opts.Limit),
-	}, nil
+	return options.FindOptionsToPB(opts), nil
 }
 
 func ProtoToPtrServicesFindOptions(protoOpts *common.FindOptions) (*options.FindOptions, error) {
-	if protoOpts == nil {
-		return nil, nil
-	}
-	return &options.FindOptions{
-		SortOptions: options.SortOptions{
-			Sort: protoOpts.Sort,
-		},
-		PaginationOptions: options.PaginationOptions{
-			PageNum:  int(protoOpts.PageNum),
-			PageSize: int(protoOpts.PageSize),
-			Offset:   int(protoOpts.Offset),
-			Limit:    int(protoOpts.Limit),
-		},
-	}, nil
+	return options.FindOptionsFromPB(protoOpts), nil
 }
 
 func ListPtrOrganizationToProto(orgs []*organizations.Organization) ([]*pb.Organization, error) {
diff --git a/pkg/users/transport/grpc/protobuf_type_converters.microgen.go b/pkg/users/transport/grpc/protobuf_type_converters.microgen.go
index 3a73ddd7680439be98f1e8ca6ec0f4dbb70b076a..5f98e2486e50a16049db5ee136cc4d2c943264d7 100644
--- a/pkg/users/transport/grpc/protobuf_type_converters.microgen.go
+++ b/pkg/users/transport/grpc/protobuf_type_converters.microgen.go
@@ -127,27 +127,9 @@ func ProtoToListPtrUser(protoCreates []*pb.User) ([]*service.User, error) {
 }
 
 func PtrServicesFindOptionsToProto(opts *options.FindOptions) (*common.FindOptions, error) {
-	if opts == nil {
-		return nil, nil
-	}
-	return &common.FindOptions{
-		Sort:     opts.Sort,
-		PageNum:  int32(opts.PageNum),
-		PageSize: int32(opts.PageSize),
-	}, nil
+	return options.FindOptionsToPB(opts), nil
 }
 
 func ProtoToPtrServicesFindOptions(protoOpts *common.FindOptions) (*options.FindOptions, error) {
-	if protoOpts == nil {
-		return nil, nil
-	}
-	return &options.FindOptions{
-		SortOptions: options.SortOptions{
-			Sort: protoOpts.Sort,
-		},
-		PaginationOptions: options.PaginationOptions{
-			PageNum:  int(protoOpts.PageNum),
-			PageSize: int(protoOpts.PageSize),
-		},
-	}, nil
+	return options.FindOptionsFromPB(protoOpts), nil
 }
diff --git a/zap/channels.go b/zap/channels.go
index c67d275541c28739dd45b04d8c643534e39dba16..92b3d823e3aef22c30736942c0aa8931c7e19b31 100644
--- a/zap/channels.go
+++ b/zap/channels.go
@@ -1,20 +1,24 @@
 package zap
 
 import (
-	"strings"
-
+	"git.perx.ru/perxis/perxis-go/pkg/data"
 	"go.uber.org/zap/zapcore"
 )
 
-const channelKey = "channel"
+const (
+	channelKey = "channel"
+
+	Syslog  = "syslog"
+	Userlog = "userlog"
+	// ChannelsAll   = "*"
+)
 
-func HasChannel(channel string) FilterFunc {
+func ContainsChannels(channels ...string) FilterFunc {
 	return func(entry zapcore.Entry, fields []zapcore.Field) bool {
 		for _, f := range fields {
-			if f.Key == channelKey && f.Type == zapcore.StringType {
-				list := strings.Split(f.String, ",")
-				for _, v := range list {
-					if v == channel {
+			if f.Key == channelKey && f.Type == zapcore.ArrayMarshalerType {
+				for _, v := range f.Interface.(stringArray) {
+					if data.Contains(v, channels) {
 						return true
 					}
 				}
@@ -24,20 +28,24 @@ func HasChannel(channel string) FilterFunc {
 	}
 }
 
-// NewDefaultChannelCore аналогичен NewChannelCore, но также устанавливает переданный канал в качестве канала по умолчанию.
-// Это означает, что если поле Channel в записи не указано, запись все равно будет передана в zapcore.Core.
-func NewDefaultChannelCore(core zapcore.Core, channel string) zapcore.Core {
-	return RegisterFilters(
-		core,
-		Or(
-			HasChannel(channel),
-			Not(ContainsKey(channelKey)),
-		),
-	)
+// WithDefaultChannel аналогичен WithChannel, но также устанавливает переданный канал в качестве канала по умолчанию.
+// Это означает, что если поле Channels в записи не указано, запись все равно будет передана в zapcore.Core.
+func WithDefaultChannel(core zapcore.Core, channel string) zapcore.Core {
+	return WithChannel(core, channel, true)
+}
+
+// WithChannel добавляет к переданному Core фильтрацию записей по каналам.
+// Это означает, что если запись содержит поле Channels и значение соответствует
+// переданному каналу, то запись будет передана в zapcore.Core.
+func WithChannel(core zapcore.Core, channel string, isDefault ...bool) zapcore.Core {
+	filterFn := ContainsChannels(channel)
+	if len(isDefault) > 0 && isDefault[0] {
+		filterFn = Or(filterFn, Not(ContainsKey(channelKey)))
+	}
+	return WithFilters(core, filterFn)
 }
 
-// NewChannelCore добавляет к переданному Core фильтрацию записей по каналам.
-// Это означает, что если запись содержит поле Channel и значение соответствует переданному каналу, то запись будет передана в zapcore.Core.
-func NewChannelCore(core zapcore.Core, channel string) zapcore.Core {
-	return RegisterFilters(core, HasChannel(channel))
+func WithChannels(core zapcore.Core, channels ...string) zapcore.Core {
+	filterFn := ContainsChannels(channels...)
+	return WithFilters(core, filterFn)
 }
diff --git a/zap/channels_test.go b/zap/channels_test.go
index 51fa290785a258dcb9c5faa372afa5ec1c228072..ac4c5c9a33797413a7744e439a9d31f38191540e 100644
--- a/zap/channels_test.go
+++ b/zap/channels_test.go
@@ -8,46 +8,51 @@ import (
 	"go.uber.org/zap/zaptest/observer"
 )
 
-func TestNewChannelCore_WriteSingleChannel(t *testing.T) {
+func TestWithChannel_WriteSingleChannel(t *testing.T) {
 	core, logs := observer.New(zapcore.InfoLevel)
-	core = NewChannelCore(core, "test")
+	core = WithChannel(core, "test")
 
-	err := core.Write(zapcore.Entry{Message: "msg"}, []zapcore.Field{Channel("test")})
-	require.NoError(t, err)
-
-	err = core.Write(zapcore.Entry{Message: "msg"}, []zapcore.Field{Channel("empty")}) // запись не попадет в лог
-	require.NoError(t, err)
+	require.NoError(t, core.Write(zapcore.Entry{Message: "msg"}, []zapcore.Field{Channels("test")}))
+	require.NoError(t, core.Write(zapcore.Entry{Message: "msg"}, []zapcore.Field{Channels("empty")})) // запись не попадет в лог
 
 	require.Equal(t, 1, logs.Len())
 }
 
-func TestNewChannelCore_WriteMultiplyChannels(t *testing.T) {
+func TestWithChannel_WriteMultiplyChannels(t *testing.T) {
 	core, logs := observer.New(zapcore.InfoLevel)
 
 	core = zapcore.NewTee(
-		NewChannelCore(core, "test1"),
-		NewChannelCore(core, "test2"),
+		WithChannel(core, "test1"),
+		WithChannel(core, "test2"),
 	)
 
-	err := core.Write(zapcore.Entry{Message: "msg"}, []zapcore.Field{Channel("test1", "test2")}) // запись попадет сразу в 2 core
-	require.NoError(t, err)
+	require.NoError(t, core.Write(zapcore.Entry{Message: "msg"}, []zapcore.Field{Channels("test1", "test2")})) // запись попадет сразу в 2 core
 
 	require.Equal(t, 2, logs.Len())
 }
 
-func TestNewDefaultChannelCore(t *testing.T) {
+func TestWithDefaultChannels(t *testing.T) {
 	core, logs := observer.New(zapcore.InfoLevel)
 
-	core = NewDefaultChannelCore(core, "test1")
+	core = zapcore.NewTee(
+		WithChannel(core, "test1", true),
+		WithChannel(core, "test2"),
+	)
+
+	require.NoError(t, core.Write(zapcore.Entry{Message: "msg"}, []zapcore.Field{Channels("test1")}))
+	require.NoError(t, core.Write(zapcore.Entry{Message: "msg"}, []zapcore.Field{Channels("test3")})) // эта запись не попадет в лог
+	require.NoError(t, core.Write(zapcore.Entry{Message: "msg"}, []zapcore.Field{}))
 
-	err := core.Write(zapcore.Entry{Message: "msg"}, []zapcore.Field{Channel("test1")})
-	require.NoError(t, err)
+	require.Equal(t, 2, logs.Len())
+}
+
+func TestWithChannels(t *testing.T) {
+	core, logs := observer.New(zapcore.InfoLevel)
 
-	err = core.Write(zapcore.Entry{Message: "msg"}, []zapcore.Field{Channel("test2")}) // эта запись не попадет в лог
-	require.NoError(t, err)
+	core = WithChannels(core, "test1", "test2")
 
-	err = core.Write(zapcore.Entry{Message: "msg"}, []zapcore.Field{})
-	require.NoError(t, err)
+	require.NoError(t, core.Write(zapcore.Entry{Message: "msg"}, []zapcore.Field{Channels("test1")}))
+	require.NoError(t, core.Write(zapcore.Entry{Message: "msg"}, []zapcore.Field{Channels("test2")}))
 
 	require.Equal(t, 2, logs.Len())
 }
diff --git a/zap/field.go b/zap/field.go
index b439f51b05736b77ff0a991e9b828a2116cfa0e3..60a56235144b064b881d0705bdfe90b8c539719b 100644
--- a/zap/field.go
+++ b/zap/field.go
@@ -2,80 +2,62 @@ package zap
 
 import (
 	"context"
-	"strings"
 
 	"git.perx.ru/perxis/perxis-go/id"
 	_ "git.perx.ru/perxis/perxis-go/id/system" // регистрируем обработчики для системных объектов
 	"git.perx.ru/perxis/perxis-go/pkg/auth"
 	"go.uber.org/zap"
+	"go.uber.org/zap/zapcore"
 )
 
-func Channel(channels ...string) zap.Field {
-	if len(channels) == 0 {
-		return zap.Skip()
+type stringArray []string
+
+func (ss stringArray) MarshalLogArray(arr zapcore.ArrayEncoder) error {
+	for i := range ss {
+		arr.AppendString(ss[i])
 	}
-	return zap.String(channelKey, strings.Join(channels, ","))
+	return nil
+}
+
+func Channels(channels ...string) zap.Field {
+	return zap.Array(channelKey, stringArray(channels))
 }
 
 func Category(category string) zap.Field {
-	if category == "" {
-		return zap.Skip()
-	}
 	return zap.String("category", category)
 }
 
 func Component(component string) zap.Field {
-	if component == "" {
-		return zap.Skip()
-	}
 	return zap.String("component", component)
 }
 
 func Event(event string) zap.Field {
-	if event == "" {
-		return zap.Skip()
-	}
 	return zap.String("event", event)
 }
 
 // Object возвращает поле и устанавливает передаваемый аргумент в качестве идентификатора объекта в формате ObjectId.
 // Поддерживает типы в формате ObjectId: id.Descriptor, string, map[string]any, системные объекты.
 func Object(v any) zap.Field {
-	oid, err := id.NewObjectId(v)
-	if err != nil {
-		return zap.Skip()
-	}
+	oid, _ := id.NewObjectId(v)
 	return zap.Reflect("object", oid)
 }
 
 // Caller возвращает поле и устанавливает передаваемый аргумент в качестве "вызывающего" в формате ObjectId.
 // Поддерживает типы в формате ObjectId: id.Descriptor, string, map[string]any, системные объекты.
 func Caller(v any) zap.Field {
-	oid, err := id.NewObjectId(v)
-	if err != nil {
-		return zap.Skip()
-	}
+	oid, _ := id.NewObjectId(v)
 	return zap.Reflect("caller", oid)
 }
 
 // CallerFromContext извлекает auth.Principal из контекста и устанавливает его в качестве "вызывающего" в формате Object.
 func CallerFromContext(ctx context.Context) zap.Field {
-	if ctx == nil {
-		return zap.Skip()
-	}
 	return Caller(auth.GetPrincipal(ctx))
 }
 
 func Attr(attr any) zap.Field {
-	if attr == nil {
-		return zap.Skip()
-	}
 	return zap.Any("attr", attr)
 }
 
 func Tags(tags ...string) zap.Field {
-	if len(tags) == 0 {
-		return zap.Skip()
-	}
 	return zap.Strings("tags", tags)
 }
diff --git a/zap/field_test.go b/zap/field_test.go
index ead5af2386b5606a3861dc563ba6e49a6ed43f7b..5c107d7bc769c04a066b636e0f7a79ca892420f8 100644
--- a/zap/field_test.go
+++ b/zap/field_test.go
@@ -19,7 +19,7 @@ func TestCategory(t *testing.T) {
 		want  zap.Field
 	}{
 		{name: "ok", field: Category("update"), want: zap.String("category", "update")},
-		{name: "invalid", field: Category(""), want: zap.Skip()},
+		{name: "invalid", field: Category(""), want: zap.String("category", "")},
 	}
 
 	for _, tc := range tests {
@@ -36,7 +36,7 @@ func TestComponent(t *testing.T) {
 		want  zap.Field
 	}{
 		{name: "ok", field: Component("Items"), want: zap.String("component", "Items")},
-		{name: "invalid", field: Component(""), want: zap.Skip()},
+		{name: "invalid", field: Component(""), want: zap.String("component", "")},
 	}
 
 	for _, tc := range tests {
@@ -53,7 +53,7 @@ func TestEvent(t *testing.T) {
 		want  zap.Field
 	}{
 		{name: "ok", field: Event("items.create"), want: zap.String("event", "items.create")},
-		{name: "invalid", field: Event(""), want: zap.Skip()},
+		{name: "invalid", field: Event(""), want: zap.String("event", "")},
 	}
 
 	for _, tc := range tests {
@@ -63,7 +63,7 @@ func TestEvent(t *testing.T) {
 	}
 }
 
-func TestObjectID(t *testing.T) {
+func TestObject(t *testing.T) {
 	item := &items.Item{
 		ID:           "c4ca4238a0b923820dcc509a6f75849b",
 		SpaceID:      "c81e728d9d4c2f636f067f89cc14862c",
@@ -82,21 +82,24 @@ func TestObjectID(t *testing.T) {
 		{name: "system object", field: Object(item), want: zap.Reflect("object", oid)},
 		{name: "object id", field: Object(itemId), want: zap.Reflect("object", oid)},
 		{name: "string", field: Object(oid.String()), want: zap.Reflect("object", oid)},
-		{name: "invalid", field: Object(nil), want: zap.Skip()},
+		{name: "invalid", field: Object(nil), want: zap.Reflect("object", (*id.ObjectId)(nil))},
 	}
 
 	for _, tc := range tests {
 		t.Run(tc.name, func(t *testing.T) {
-			if tc.want.Equals(zap.Skip()) {
-				assert.True(t, tc.want.Equals(tc.field))
-				return
+			wantObjectId, ok1 := tc.want.Interface.(*id.ObjectId)
+			fieldObjectId, ok2 := tc.field.Interface.(*id.ObjectId)
+
+			if ok1 && ok2 && wantObjectId != nil && fieldObjectId != nil {
+				assert.Equal(t, wantObjectId.String(), fieldObjectId.String())
+			} else {
+				assert.Equal(t, tc.want.Interface, tc.field.Interface)
 			}
-			assert.Equal(t, tc.want.Interface.(id.Descriptor).String(), tc.field.Interface.(id.Descriptor).String())
 		})
 	}
 }
 
-func TestCallerID(t *testing.T) {
+func TestCaller(t *testing.T) {
 	user := &users.User{
 		ID: "c4ca4238a0b923820dcc509a6f75849b",
 	}
@@ -112,21 +115,24 @@ func TestCallerID(t *testing.T) {
 		{name: "system object", field: Caller(user), want: zap.Reflect("caller", oid)},
 		{name: "object id", field: Caller(userId), want: zap.Reflect("caller", oid)},
 		{name: "string", field: Caller(oid.String()), want: zap.Reflect("caller", oid)},
-		{name: "invalid", field: Caller(nil), want: zap.Skip()},
+		{name: "invalid", field: Caller(nil), want: zap.Reflect("caller", (*id.ObjectId)(nil))},
 	}
 
 	for _, tc := range tests {
 		t.Run(tc.name, func(t *testing.T) {
-			if tc.want.Equals(zap.Skip()) {
-				assert.True(t, tc.want.Equals(tc.field))
-				return
+			wantObjectId, ok1 := tc.want.Interface.(*id.ObjectId)
+			fieldObjectId, ok2 := tc.field.Interface.(*id.ObjectId)
+
+			if ok1 && ok2 && wantObjectId != nil && fieldObjectId != nil {
+				assert.Equal(t, wantObjectId.String(), fieldObjectId.String())
+			} else {
+				assert.Equal(t, tc.want.Interface, tc.field.Interface)
 			}
-			assert.Equal(t, tc.want.Interface.(id.Descriptor).String(), tc.field.Interface.(id.Descriptor).String())
 		})
 	}
 }
 
-func TestCallerIDFromContext(t *testing.T) {
+func TestCallerFromContext(t *testing.T) {
 	ctx := auth.WithSystem(context.Background())
 	oid := id.MustObjectId(auth.GetPrincipal(ctx))
 
@@ -136,16 +142,19 @@ func TestCallerIDFromContext(t *testing.T) {
 		want  zap.Field
 	}{
 		{name: "ok", field: CallerFromContext(ctx), want: zap.Reflect("caller", oid)},
-		{name: "invalid", field: CallerFromContext(context.TODO()), want: zap.Skip()},
+		{name: "invalid", field: CallerFromContext(context.TODO()), want: zap.Reflect("caller", (*id.ObjectId)(nil))},
 	}
 
 	for _, tc := range tests {
 		t.Run(tc.name, func(t *testing.T) {
-			if tc.want.Equals(zap.Skip()) {
-				assert.True(t, tc.want.Equals(tc.field))
-				return
+			wantObjectId, ok1 := tc.want.Interface.(*id.ObjectId)
+			fieldObjectId, ok2 := tc.field.Interface.(*id.ObjectId)
+
+			if ok1 && ok2 && wantObjectId != nil && fieldObjectId != nil {
+				assert.Equal(t, wantObjectId.String(), fieldObjectId.String())
+			} else {
+				assert.Equal(t, tc.want.Interface, tc.field.Interface)
 			}
-			assert.Equal(t, tc.want.Interface.(id.Descriptor).String(), tc.field.Interface.(id.Descriptor).String())
 		})
 	}
 }
@@ -157,7 +166,7 @@ func TestAttr(t *testing.T) {
 		want  zap.Field
 	}{
 		{name: "ok", field: Attr(map[string]string{"a": "b"}), want: zap.Reflect("attr", map[string]string{"a": "b"})},
-		{name: "invalid", field: Attr(nil), want: zap.Skip()},
+		{name: "invalid", field: Attr(nil), want: zap.Reflect("attr", nil)},
 	}
 
 	for _, tc := range tests {
@@ -174,7 +183,7 @@ func TestTags(t *testing.T) {
 		want  zap.Field
 	}{
 		{name: "ok", field: Tags("a", "b", "c"), want: zap.Strings("tags", []string{"a", "b", "c"})},
-		{name: "invalid", field: Tags(nil...), want: zap.Skip()},
+		{name: "invalid", field: Tags(nil...), want: zap.Strings("tags", nil)},
 	}
 
 	for _, tc := range tests {
diff --git a/zap/filter_core.go b/zap/filter_core.go
index 16888f583892b05bd379d5868ea5f58c6dbf4628..7e99d942e439afc8a05faaf55be29b8cb604b7fc 100644
--- a/zap/filter_core.go
+++ b/zap/filter_core.go
@@ -56,20 +56,20 @@ type filterCore struct {
 	fields []zap.Field
 }
 
-// RegisterFilters - добавить фильтры, которые будут применяться при записи лога (вызове `core.Write`)
+// WithFilters - добавить фильтры, которые будут применяться при записи лога (вызове `core.Write`)
 // Метод `core.Write` будет вызван только в случае, когда результат всех фильтров `true`
 //
 // Обратить внимание, фильтр не применяется к полям, которые были добавлены в `core` через вызов `core.With`
-// до вызова RegisterFilters. Пример:
+// до вызова WithFilters. Пример:
 //
 //	l, _ := zap.NewDevelopment()
 //	core := l.Core().With([]zapcore.Field{zap.Int("a", 5)})
-//	core = RegisterFilters(core, ContainsField(zap.Int("a", 5)))
+//	core = WithFilters(core, ContainsField(zap.Int("a", 5)))
 //
 //	logger := zap.New(core)
 //	logger.Info("Test log") // НЕ будет записан
 //	logger.Info("Test log", zap.Int("a", 5)) // будет записан
-func RegisterFilters(core zapcore.Core, filters ...FilterFunc) zapcore.Core {
+func WithFilters(core zapcore.Core, filters ...FilterFunc) zapcore.Core {
 	return &filterCore{
 		Core:    core,
 		filters: filters,
diff --git a/zap/filter_core_test.go b/zap/filter_core_test.go
index cb38f41cd2f0c5bab0aabea75112d9f1a57fd109..21c906f24f5b7458c7628985871f1480564a3164 100644
--- a/zap/filter_core_test.go
+++ b/zap/filter_core_test.go
@@ -11,7 +11,7 @@ import (
 
 func TestFilterCore_Write(t *testing.T) {
 	core, logs := observer.New(zapcore.InfoLevel)
-	core = RegisterFilters(core, ContainsField(zap.Bool("check", true)))
+	core = WithFilters(core, ContainsField(zap.Bool("check", true)))
 
 	err := core.With([]zapcore.Field{zap.Bool("check", true)}).Write(zapcore.Entry{Message: "msg"}, nil)
 	require.NoError(t, err)
@@ -25,9 +25,9 @@ func TestFilterCore_Write(t *testing.T) {
 	require.Equal(t, 2, logs.Len())
 }
 
-func TestNotHasField(t *testing.T) {
+func TestNotContainsField(t *testing.T) {
 	core, logs := observer.New(zapcore.InfoLevel)
-	core = RegisterFilters(core, NotHasField(zap.Int("b", 2)))
+	core = WithFilters(core, Not(ContainsField(zap.Int("b", 2))))
 
 	err := core.Write(zapcore.Entry{Message: "msg"}, []zapcore.Field{
 		zap.Int("a", 1),