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 55%
rename from assets/templates/middleware/middleware
rename to assets/templates/middleware/middleware.tmpl
index 89877774c933840c2bdd569f2beed8105588aae2..7a34d393cbe71648913dd708d49118da36da1105 100755
--- a/assets/templates/middleware/middleware
+++ b/assets/templates/middleware/middleware.tmpl
@@ -2,20 +2,23 @@ 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 }}")
-	s = ErrorLoggingMiddleware(logger)(s)
+    logger = logger.Named("{{ .Interface.Name }}")
 	if log_access {
-		s = LoggingMiddleware(logger)(s)
+		s = AccessLoggingMiddleware(logger)(s)
 	}
+	{{- if (has $serviceName (list "Items" "Collections") ) }}
+		s = LoggingMiddleware(logger)(s)
+	{{ else }}
+		s = ErrorLoggingMiddleware(logger)(s)
+	{{ end }}
 	s = RecoveringMiddleware(logger)(s)
 	return s
 }
-
diff --git a/id/bson.go b/id/bson.go
deleted file mode 100644
index 9d44c5a8074361b381524dd7c99ad77906cfc68c..0000000000000000000000000000000000000000
--- a/id/bson.go
+++ /dev/null
@@ -1,32 +0,0 @@
-package id
-
-import (
-	"git.perx.ru/perxis/perxis-go/pkg/errors"
-	"go.mongodb.org/mongo-driver/bson"
-	"go.mongodb.org/mongo-driver/bson/bsonrw"
-	"go.mongodb.org/mongo-driver/bson/bsontype"
-)
-
-func (id *ID) MarshalBSONValue() (bsontype.Type, []byte, error) {
-	return bson.MarshalValue(id.String())
-}
-
-func (id *ID) UnmarshalBSONValue(btype bsontype.Type, data []byte) error {
-	if btype != bson.TypeString {
-		return errors.New("cannot unmarshal non-string bson value to ID")
-	}
-	dec, err := bson.NewDecoder(bsonrw.NewBSONValueReader(btype, data))
-	if err != nil {
-		return err
-	}
-	var str string
-	if err = dec.Decode(&str); err != nil {
-		return err
-	}
-	t, err := Parse(str)
-	if err != nil {
-		return err
-	}
-	*id = *t
-	return nil
-}
diff --git a/id/bson_test.go b/id/bson_test.go
index c8080b5d7ae12181343a8f6502e76484c414e578..7ae62ccba4e1dbc2e74ac48b8c5890216a6cba50 100644
--- a/id/bson_test.go
+++ b/id/bson_test.go
@@ -11,64 +11,64 @@ import (
 func TestID_MarshalUnmarshalBSON(t *testing.T) {
 	tests := []struct {
 		name string
-		id   *ID
+		id   *ObjectId
 	}{
 		{
 			name: "OrganizationID",
-			id:   &ID{Descriptor: &OrganizationID{OrganizationID: "1"}},
+			id:   &ObjectId{Descriptor: &OrganizationId{OrganizationID: "1"}},
 		},
 		{
 			name: "UserID",
-			id:   &ID{Descriptor: &UserID{UserID: "1"}},
+			id:   &ObjectId{Descriptor: &UserId{UserID: "1"}},
 		},
 		{
 			name: "ServiceID",
-			id:   &ID{Descriptor: &ServiceID{ServiceID: "1"}},
+			id:   &ObjectId{Descriptor: &ServiceId{ServiceID: "1"}},
 		},
 		{
-			name: "SpaceID",
-			id:   &ID{Descriptor: &SpaceID{SpaceID: "1"}},
+			name: "SpaceId",
+			id:   &ObjectId{Descriptor: &SpaceId{SpaceID: "1"}},
 		},
 		{
 			name: "EnvironmentID",
-			id:   &ID{Descriptor: &EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}},
+			id:   &ObjectId{Descriptor: &EnvironmentId{EnvironmentID: "1", SpaceId: SpaceId{SpaceID: "1"}}},
 		},
 		{
 			name: "ClientID",
-			id:   &ID{Descriptor: &ClientID{ClientID: "1", SpaceID: SpaceID{SpaceID: "1"}}},
+			id:   &ObjectId{Descriptor: &ClientId{ClientID: "1", SpaceId: SpaceId{SpaceID: "1"}}},
 		},
 		{
 			name: "RoleID",
-			id:   &ID{Descriptor: &RoleID{RoleID: "1", SpaceID: SpaceID{SpaceID: "1"}}},
+			id:   &ObjectId{Descriptor: &RoleId{RoleID: "1", SpaceId: SpaceId{SpaceID: "1"}}},
 		},
 		{
-			name: "CollectionID",
-			id:   &ID{Descriptor: &CollectionID{CollectionID: "1", EnvironmentID: EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}},
+			name: "CollectionId",
+			id:   &ObjectId{Descriptor: &CollectionId{CollectionID: "1", EnvironmentId: EnvironmentId{EnvironmentID: "1", SpaceId: SpaceId{SpaceID: "1"}}}},
 		},
 		{
 			name: "SchemaID",
-			id:   &ID{Descriptor: &SchemaID{CollectionID: "1", EnvironmentID: EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}},
+			id:   &ObjectId{Descriptor: &SchemaId{CollectionID: "1", EnvironmentId: EnvironmentId{EnvironmentID: "1", SpaceId: SpaceId{SpaceID: "1"}}}},
 		},
 		{
-			name: "ItemID",
-			id:   &ID{Descriptor: &ItemID{ItemID: "1", CollectionID: CollectionID{CollectionID: "1", EnvironmentID: EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}}},
+			name: "ItemId",
+			id:   &ObjectId{Descriptor: &ItemId{ItemID: "1", CollectionId: CollectionId{CollectionID: "1", EnvironmentId: EnvironmentId{EnvironmentID: "1", SpaceId: SpaceId{SpaceID: "1"}}}}},
 		},
 		{
 			name: "RevisionID",
-			id:   &ID{Descriptor: &RevisionID{RevisionID: "1", ItemID: ItemID{ItemID: "1", CollectionID: CollectionID{CollectionID: "1", EnvironmentID: EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}}}},
+			id:   &ObjectId{Descriptor: &RevisionId{RevisionID: "1", ItemId: ItemId{ItemID: "1", CollectionId: CollectionId{CollectionID: "1", EnvironmentId: EnvironmentId{EnvironmentID: "1", SpaceId: SpaceId{SpaceID: "1"}}}}}},
 		},
 		{
-			name: "FieldID",
-			id:   &ID{Descriptor: &FieldID{FieldName: "1", ItemID: ItemID{ItemID: "1", CollectionID: CollectionID{CollectionID: "1", EnvironmentID: EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}}}},
+			name: "FieldId",
+			id:   &ObjectId{Descriptor: &FieldId{Field: "1", ItemId: ItemId{ItemID: "1", CollectionId: CollectionId{CollectionID: "1", EnvironmentId: EnvironmentId{EnvironmentID: "1", SpaceId: SpaceId{SpaceID: "1"}}}}}},
 		},
 		{
 			name: "SystemID",
-			id:   &ID{Descriptor: &SystemID{}},
+			id:   &ObjectId{Descriptor: &SystemId{}},
 		},
 	}
 	type test struct {
 		Text string
-		ID   *ID
+		ID   *ObjectId
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
@@ -86,13 +86,14 @@ func TestID_MarshalUnmarshalBSON(t *testing.T) {
 
 func TestID_ExampleBSON(t *testing.T) {
 	type data struct {
-		ID     *ID
+		ID *ObjectId
+
 		Text   string
 		Number int
 	}
 
 	test := &data{
-		ID:     &ID{Descriptor: &SpaceID{SpaceID: Space}},
+		ID:     &ObjectId{Descriptor: &SpaceId{SpaceID: Space}},
 		Text:   "text",
 		Number: 1,
 	}
diff --git a/id/client.go b/id/client.go
index db941c5ad54b539551193004674ac15f15d314be..99c2cedf3e86a809be30fb3e2b4ab61ab93eb5ee 100644
--- a/id/client.go
+++ b/id/client.go
@@ -1,61 +1,65 @@
 package id
 
+import (
+	"fmt"
+)
+
 const (
 	Client        = "client"
 	ClientsPrefix = "clients"
 )
 
-type ClientID struct {
-	SpaceID
+var _ Descriptor = &ClientId{}
+
+type ClientId struct {
+	SpaceId
 	ClientID string `json:"client_id,omitempty" bson:"client_id,omitempty"`
 }
 
-func (t *ClientID) Type() string { return Client }
-
-func (t *ClientID) String() string {
-	return Join(t.SpaceID.String(), ClientsPrefix, t.ClientID)
-
+func (id *ClientId) New() Descriptor {
+	return &ClientId{}
 }
 
-func (t *ClientID) ToMap() map[string]any {
-	m := t.SpaceID.ToMap()
-	m["client_id"] = t.ClientID
-	m["type"] = Client
-	return m
+func (id *ClientId) Type() string { return Client }
+
+func (id *ClientId) String() string {
+	return Join(id.SpaceId.String(), ClientsPrefix, id.ClientID)
 }
 
-func (t *ClientID) FromMap(m map[string]any) error {
-	if err := t.SpaceID.FromMap(m); err != nil {
+func (id *ClientId) FromParts(parts []string) error {
+	if len(parts) != 4 || parts[2] != ClientsPrefix {
+		return ErrInvalidID
+	}
+	if err := id.SpaceId.FromParts(parts[:2]); err != nil {
 		return err
 	}
-	t.ClientID = m["client_id"].(string)
+	id.ClientID = parts[3]
 	return nil
 }
 
-func (t *ClientID) Validate() error {
-	if t.ClientID == "" {
-		return ErrInvalidID
-	}
-
-	return t.SpaceID.Validate()
+func (id *ClientId) Map() map[string]any {
+	m := id.SpaceId.Map()
+	m["client_id"] = id.ClientID
+	m["type"] = Client
+	return m
 }
 
-func parseClientID(parts []string) (*ClientID, error) {
-	if len(parts) != 4 || parts[2] != ClientsPrefix {
-		return nil, ErrInvalidID
+func (id *ClientId) FromMap(m map[string]any) error {
+	id.ClientID, _ = m["client_id"].(string)
+	if id.ClientID == "" {
+		return fmt.Errorf("%w: ClientID required", ErrInvalidID)
 	}
+	return id.SpaceId.FromMap(m)
+}
 
-	spaceID, err := parseSpaceID(parts[:2])
-	if err != nil {
-		return nil, err
+func (id *ClientId) Validate() error {
+	if id.ClientID == "" {
+		return fmt.Errorf("%w: ClientID required", ErrInvalidID)
 	}
 
-	var id ClientID
-	id.SpaceID = *spaceID
-	id.ClientID = parts[3]
-	return &id, nil
+	return id.SpaceId.Validate()
 }
 
-func NewClientID(spaceID, id string) *ID {
-	return &ID{Descriptor: &ClientID{SpaceID: SpaceID{SpaceID: spaceID}, ClientID: id}}
+func NewClientId(spaceID, id string) *ObjectId {
+	return &ObjectId{Descriptor: &ClientId{SpaceId: SpaceId{SpaceID: spaceID}, ClientID: id}}
 }
diff --git a/id/collection.go b/id/collection.go
index e0f37585b41e77b58530cc50f249283beb6d8eca..f507d66c85e35de208468243383c377f09c88259 100644
--- a/id/collection.go
+++ b/id/collection.go
@@ -1,60 +1,64 @@
 package id
 
+import (
+	"fmt"
+)
+
 const (
 	Collection        = "collection"
 	CollectionsPrefix = "cols"
 )
 
-type CollectionID struct {
-	EnvironmentID
+var _ Descriptor = &CollectionId{}
+
+type CollectionId struct {
+	EnvironmentId
 	CollectionID string `json:"col_id,omitempty" bson:"col_id, omitempty"`
 }
 
-func (t *CollectionID) Type() string { return Collection }
+func (id *CollectionId) New() Descriptor {
+	return &CollectionId{EnvironmentId: EnvironmentId{SpaceId: SpaceId{}}}
+}
+
+func (id *CollectionId) Type() string { return Collection }
 
-func (t *CollectionID) String() string {
-	return Join(t.EnvironmentID.String(), CollectionsPrefix, t.CollectionID)
+func (id *CollectionId) String() string {
+	return Join(id.EnvironmentId.String(), CollectionsPrefix, id.CollectionID)
 }
 
-func (t *CollectionID) ToMap() map[string]any {
-	m := t.EnvironmentID.ToMap()
-	m["col_id"] = t.CollectionID
+func (id *CollectionId) Map() map[string]any {
+	m := id.EnvironmentId.Map()
+	m["col_id"] = id.CollectionID
 	m["type"] = Collection
 	return m
 }
 
-func (t *CollectionID) FromMap(m map[string]any) error {
-	if err := t.EnvironmentID.FromMap(m); err != nil {
+func (id *CollectionId) FromParts(parts []string) error {
+	if len(parts) != 6 || parts[4] != CollectionsPrefix {
+		return ErrInvalidID
+	}
+	if err := id.EnvironmentId.FromParts(parts[:4]); err != nil {
 		return err
 	}
-	t.CollectionID = m["col_id"].(string)
+	id.CollectionID = parts[5]
 	return nil
 }
 
-func (t *CollectionID) Validate() error {
-	if t.CollectionID == "" {
-		return ErrInvalidID
+func (id *CollectionId) FromMap(m map[string]any) error {
+	id.CollectionID, _ = m["col_id"].(string)
+	if id.CollectionID == "" {
+		return fmt.Errorf("%w: CollectionID required", ErrInvalidID)
 	}
-
-	return t.EnvironmentID.Validate()
+	return id.EnvironmentId.FromMap(m)
 }
 
-func parseCollectionID(parts []string) (*CollectionID, error) {
-	if len(parts) != 6 || parts[4] != CollectionsPrefix {
-		return nil, ErrInvalidID
-	}
-
-	envID, err := parseEnvironmentID(parts[:4])
-	if err != nil {
-		return nil, err
+func (id *CollectionId) Validate() error {
+	if id.CollectionID == "" {
+		return fmt.Errorf("%w: CollectionID required", ErrInvalidID)
 	}
-
-	var id CollectionID
-	id.CollectionID = parts[5]
-	id.EnvironmentID = *envID
-	return &id, nil
+	return id.EnvironmentId.Validate()
 }
 
-func NewCollectionID(spaceID, envID, id string) *ID {
-	return &ID{Descriptor: &CollectionID{EnvironmentID: EnvironmentID{SpaceID: SpaceID{SpaceID: spaceID}, EnvironmentID: envID}, CollectionID: id}}
+func NewCollectionId(spaceID, envID, id string) *ObjectId {
+	return &ObjectId{Descriptor: &CollectionId{EnvironmentId: EnvironmentId{SpaceId: SpaceId{SpaceID: spaceID}, EnvironmentID: envID}, CollectionID: id}}
 }
diff --git a/id/environment.go b/id/environment.go
index d42df3e658bc04ae70989d199257a7836bd30f00..364529f681c2f9e4dc8c785eb338afc554ae3642 100644
--- a/id/environment.go
+++ b/id/environment.go
@@ -1,61 +1,65 @@
 package id
 
+import (
+	"fmt"
+)
+
 const (
 	Environment        = "environment"
 	EnvironmentsPrefix = "envs"
 )
 
-type EnvironmentID struct {
-	SpaceID
+var _ Descriptor = &EnvironmentId{}
+
+type EnvironmentId struct {
+	SpaceId
 	EnvironmentID string `json:"env_id,omitempty" bson:"env_id,omitempty"`
 }
 
-func (t *EnvironmentID) Type() string { return Environment }
+func (id *EnvironmentId) New() Descriptor {
+	return &EnvironmentId{}
+}
+
+func (id *EnvironmentId) Type() string { return "environment" }
 
-func (t *EnvironmentID) String() string {
-	return Join(t.SpaceID.String(), EnvironmentsPrefix, t.EnvironmentID)
+func (id *EnvironmentId) String() string {
+	return Join(id.SpaceId.String(), EnvironmentsPrefix, id.EnvironmentID)
 
 }
 
-func (t *EnvironmentID) ToMap() map[string]any {
-	m := t.SpaceID.ToMap()
-	m["env_id"] = t.EnvironmentID
+func (id *EnvironmentId) Map() map[string]any {
+	m := id.SpaceId.Map()
+	m["env_id"] = id.EnvironmentID
 	m["type"] = Environment
 	return m
 }
 
-func (t *EnvironmentID) FromMap(m map[string]any) error {
-	if err := t.SpaceID.FromMap(m); err != nil {
+func (id *EnvironmentId) FromParts(parts []string) error {
+	if len(parts) != 4 || parts[2] != EnvironmentsPrefix {
+		return ErrInvalidID
+	}
+	if err := id.SpaceId.FromParts(parts[:2]); err != nil {
 		return err
 	}
-	t.EnvironmentID = m["env_id"].(string)
+	id.EnvironmentID = parts[3]
 	return nil
 }
 
-func (t *EnvironmentID) Validate() error {
-	if t.EnvironmentID == "" {
-		return ErrInvalidID
+func (id *EnvironmentId) FromMap(m map[string]any) error {
+	id.EnvironmentID, _ = m["env_id"].(string)
+	if id.EnvironmentID == "" {
+		return fmt.Errorf("%w: EnviromentID required", ErrInvalidID)
 	}
-
-	return t.SpaceID.Validate()
+	return id.SpaceId.FromMap(m)
 }
 
-func parseEnvironmentID(parts []string) (*EnvironmentID, error) {
-	if len(parts) != 4 || parts[2] != EnvironmentsPrefix {
-		return nil, ErrInvalidID
-	}
-
-	spaceID, err := parseSpaceID(parts[:2])
-	if err != nil {
-		return nil, err
+func (id *EnvironmentId) Validate() error {
+	if id.EnvironmentID == "" {
+		return fmt.Errorf("%w: EnvironmetnID required", ErrInvalidID)
 	}
-
-	var id EnvironmentID
-	id.EnvironmentID = parts[3]
-	id.SpaceID = *spaceID
-	return &id, nil
+	return id.SpaceId.Validate()
 }
 
-func NewEnvironmentID(spaceID, id string) *ID {
-	return &ID{Descriptor: &EnvironmentID{SpaceID: SpaceID{SpaceID: spaceID}, EnvironmentID: id}}
+func NewEnvironmentId(spaceID, id string) *ObjectId {
+	return &ObjectId{Descriptor: &EnvironmentId{SpaceId: SpaceId{SpaceID: spaceID}, EnvironmentID: id}}
 }
diff --git a/id/error.go b/id/error.go
new file mode 100644
index 0000000000000000000000000000000000000000..e8332dc96a651e337cdc34ec83d06442ff27048e
--- /dev/null
+++ b/id/error.go
@@ -0,0 +1,11 @@
+package id
+
+import "errors"
+
+var (
+	ErrInvalidID       = errors.New("invalid ObjectId")
+	ErrUnknownType     = errors.New("unknown ObjectId type")
+	ErrHandlerNotFound = errors.New("ObjectId handler not found")
+	ErrTypeNotFound    = errors.New("type key not found in map")
+	ErrInvalidType     = errors.New("ObjectId should be a string")
+)
diff --git a/id/field.go b/id/field.go
index ca1577552dc7df38d555549ddfc53a51e70e63f1..024732f2d44d7a2ac4db63418ba113563a379c8d 100644
--- a/id/field.go
+++ b/id/field.go
@@ -1,60 +1,82 @@
 package id
 
+import "fmt"
+
 const (
 	Field        = "field"
 	FieldsPrefix = "fields"
 )
 
-type FieldID struct {
-	ItemID
-	FieldName string `json:"field_name,omitempty" bson:"field_name,omitempty"`
+type FieldId struct {
+	ItemId
+	Field string `json:"field,omitempty" bson:"field,omitempty"`
 }
 
-func (t *FieldID) Type() string { return Field }
-
-func (t *FieldID) String() string {
-	return Join(t.ItemID.String(), FieldsPrefix, t.FieldName)
+var _ Descriptor = &FieldId{}
 
+func (id *FieldId) New() Descriptor {
+	return &FieldId{}
 }
 
-func (t *FieldID) ToMap() map[string]any {
-	m := t.ItemID.ToMap()
-	m["field_name"] = t.FieldName
-	m["type"] = Field
-	return m
+func (id *FieldId) Type() string { return Field }
+
+func (id *FieldId) String() string {
+	return Join(id.ItemId.String(), FieldsPrefix, id.Field)
+
 }
 
-func (t *FieldID) FromMap(m map[string]any) error {
-	if err := t.ItemID.FromMap(m); err != nil {
+func (id *FieldId) FromParts(parts []string) error {
+	if len(parts) != 10 || parts[8] != FieldsPrefix {
+		return ErrInvalidID
+	}
+	if err := id.ItemId.FromParts(parts[:8]); err != nil {
 		return err
 	}
-	t.FieldName = m["field_name"].(string)
+	id.Field = parts[9]
 	return nil
 }
 
-func (t *FieldID) Validate() error {
-	if t.FieldName == "" {
-		return ErrInvalidID
-	}
-
-	return t.ItemID.Validate()
+func (id *FieldId) Map() map[string]any {
+	m := id.ItemId.Map()
+	m["field"] = id.Field
+	m["type"] = Field
+	return m
 }
 
-func parseFieldID(parts []string) (*FieldID, error) {
-	if len(parts) != 10 || parts[8] != FieldsPrefix {
-		return nil, ErrInvalidID
+func (id *FieldId) FromMap(m map[string]any) error {
+	id.Field = m["field"].(string)
+	if id.Field == "" {
+		return fmt.Errorf("%w: Field required", ErrInvalidID)
+	}
+
+	if err := id.ItemId.FromMap(m); err != nil {
+		return err
 	}
+	return nil
+}
 
-	itemID, err := parseItemID(parts[:8])
-	if err != nil {
-		return nil, err
+func (id *FieldId) Validate() error {
+	if id.Field == "" {
+		return fmt.Errorf("%w: Field required", ErrInvalidID)
 	}
 
-	var id FieldID
-	id.ItemID = *itemID
-	id.FieldName = parts[9]
-	return &id, nil
+	return id.ItemId.Validate()
 }
-func NewFieldID(spaceID, envID, collID, itemID, id string) *ID {
-	return &ID{Descriptor: &FieldID{ItemID: ItemID{CollectionID: CollectionID{EnvironmentID: EnvironmentID{SpaceID: SpaceID{SpaceID: spaceID}, EnvironmentID: envID}, CollectionID: collID}, ItemID: itemID}, FieldName: id}}
+
+func NewFieldId(spaceID, envID, collID, itemID, id string) *ObjectId {
+	return &ObjectId{Descriptor: &FieldId{
+		ItemId: ItemId{
+			CollectionId: CollectionId{
+				EnvironmentId: EnvironmentId{
+					SpaceId: SpaceId{
+						SpaceID: spaceID,
+					},
+					EnvironmentID: envID,
+				},
+				CollectionID: collID,
+			},
+			ItemID: itemID,
+		},
+		Field: id,
+	}}
 }
diff --git a/id/id.go b/id/id.go
deleted file mode 100644
index ca75c29f94b6fe9100b435e8f546835991bc9fa3..0000000000000000000000000000000000000000
--- a/id/id.go
+++ /dev/null
@@ -1,143 +0,0 @@
-package id
-
-import (
-	"strings"
-
-	"git.perx.ru/perxis/perxis-go/pkg/errors"
-)
-
-const Separator = '/'
-
-var (
-	ErrInvalidID = errors.New("invalid id")
-)
-
-type Descriptor interface {
-	String() string
-	Type() string
-	ToMap() map[string]any
-	FromMap(map[string]any) error
-	Validate() error
-}
-
-type ID struct {
-	Descriptor
-}
-
-func Parse(s string) (*ID, error) {
-	parts := Split(s)
-
-	if id, _ := parseServiceID(parts); id != nil {
-		return &ID{Descriptor: id}, nil
-	}
-
-	if id, _ := parseUserID(parts); id != nil {
-		return &ID{Descriptor: id}, nil
-	}
-
-	if id, _ := parseOrganizationID(parts); id != nil {
-		return &ID{Descriptor: id}, nil
-	}
-
-	if id, _ := parseSpaceID(parts); id != nil {
-		return &ID{Descriptor: id}, nil
-	}
-
-	if id, _ := parseEnvironmentID(parts); id != nil {
-		return &ID{Descriptor: id}, nil
-	}
-
-	if id, _ := parseClientID(parts); id != nil {
-		return &ID{Descriptor: id}, nil
-	}
-
-	if id, _ := parseRoleID(parts); id != nil {
-		return &ID{Descriptor: id}, nil
-	}
-
-	if id, _ := parseCollectionID(parts); id != nil {
-		return &ID{Descriptor: id}, nil
-	}
-
-	if id, _ := parseSchemaID(parts); id != nil {
-		return &ID{Descriptor: id}, nil
-	}
-
-	if id, _ := parseItemID(parts); id != nil {
-		return &ID{Descriptor: id}, nil
-	}
-
-	if id, _ := parseRevisionID(parts); id != nil {
-		return &ID{Descriptor: id}, nil
-	}
-
-	if id, _ := parseFieldID(parts); id != nil {
-		return &ID{Descriptor: id}, nil
-	}
-
-	if id, _ := parseSystemID(parts); id != nil {
-		return &ID{Descriptor: id}, nil
-	}
-
-	return nil, ErrInvalidID
-}
-
-func Split(id string) []string {
-	if id[0] != Separator {
-		return nil
-	}
-	return strings.Split(id[1:], string(Separator))
-}
-
-func Join(parts ...string) string {
-	s := strings.Join(parts, string(Separator))
-	if s[0] != Separator {
-		s = string(Separator) + s
-	}
-	return s
-}
-
-func FromMap(m map[string]any) (*ID, error) {
-	if m == nil {
-		return nil, errors.New("nil map")
-	}
-
-	v := new(ID)
-
-	switch m["type"] {
-	case Organization:
-		v.Descriptor = new(OrganizationID)
-	case Service:
-		v.Descriptor = new(ServiceID)
-	case User:
-		v.Descriptor = new(UserID)
-	case Space:
-		v.Descriptor = new(SpaceID)
-	case Environment:
-		v.Descriptor = new(EnvironmentID)
-	case Client:
-		v.Descriptor = new(ClientID)
-	case Role:
-		v.Descriptor = new(RoleID)
-	case Collection:
-		v.Descriptor = new(CollectionID)
-	case Schema:
-		v.Descriptor = new(SchemaID)
-	case Item:
-		v.Descriptor = new(ItemID)
-	case Revision:
-		v.Descriptor = new(RevisionID)
-	case Field:
-		v.Descriptor = new(FieldID)
-	case System:
-		v.Descriptor = new(SystemID)
-	default:
-		return nil, errors.New("unknown type")
-	}
-
-	if err := v.Descriptor.FromMap(m); err != nil {
-		return nil, err
-	}
-
-	return v, nil
-}
diff --git a/id/id_test.go b/id/id_test.go
deleted file mode 100644
index 041f1cdc6018e6c84c0c4c6a4be4b3944ef34082..0000000000000000000000000000000000000000
--- a/id/id_test.go
+++ /dev/null
@@ -1,301 +0,0 @@
-package id
-
-import (
-	"testing"
-
-	"github.com/stretchr/testify/assert"
-	"github.com/stretchr/testify/require"
-)
-
-func Test_ParseID(t *testing.T) {
-	tests := []struct {
-		name      string
-		id        string
-		result    *ID
-		wantError bool
-	}{
-		{
-			name:   "ServiceID",
-			id:     "/services/<service_id>",
-			result: &ID{Descriptor: &ServiceID{ServiceID: "<service_id>"}},
-		},
-		{
-			name:   "UserID",
-			id:     "/users/<user_id>",
-			result: &ID{Descriptor: &UserID{UserID: "<user_id>"}},
-		},
-		{
-			name:   "OrganizationID",
-			id:     "/orgs/<org_id>",
-			result: &ID{Descriptor: &OrganizationID{OrganizationID: "<org_id>"}},
-		},
-		{
-			name:   "SpaceID",
-			id:     "/spaces/<space_id>",
-			result: &ID{Descriptor: &SpaceID{SpaceID: "<space_id>"}},
-		},
-		{
-			name: "ClientID",
-			id:   "/spaces/<space_id>/clients/<client_id>",
-			result: &ID{Descriptor: &ClientID{
-				SpaceID:  SpaceID{SpaceID: "<space_id>"},
-				ClientID: "<client_id>",
-			}},
-		},
-		{
-			name: "RoleID",
-			id:   "/spaces/<space_id>/roles/<role_id>",
-			result: &ID{Descriptor: &RoleID{
-				SpaceID: SpaceID{SpaceID: "<space_id>"},
-				RoleID:  "<role_id>",
-			}},
-		},
-		{
-			name: "EnvironmentID",
-			id:   "/spaces/<space_id>/envs/<env_id>",
-			result: &ID{Descriptor: &EnvironmentID{
-				SpaceID:       SpaceID{SpaceID: "<space_id>"},
-				EnvironmentID: "<env_id>",
-			}},
-		},
-		{
-			name: "CollectionID",
-			id:   "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>",
-			result: &ID{Descriptor: &CollectionID{
-				EnvironmentID: EnvironmentID{
-					SpaceID:       SpaceID{SpaceID: "<space_id>"},
-					EnvironmentID: "<env_id>",
-				},
-				CollectionID: "<collection_id>",
-			}},
-		},
-		{
-			name: "SchemaID",
-			id:   "/spaces/<space_id>/envs/<env_id>/schema/<collection_id>",
-			result: &ID{Descriptor: &SchemaID{
-				EnvironmentID: EnvironmentID{
-					SpaceID:       SpaceID{SpaceID: "<space_id>"},
-					EnvironmentID: "<env_id>",
-				},
-				CollectionID: "<collection_id>",
-			}},
-		},
-		{
-			name: "ItemID",
-			id:   "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>",
-			result: &ID{Descriptor: &ItemID{
-				CollectionID: CollectionID{
-					EnvironmentID: EnvironmentID{
-						SpaceID:       SpaceID{SpaceID: "<space_id>"},
-						EnvironmentID: "<env_id>",
-					},
-					CollectionID: "<collection_id>",
-				},
-				ItemID: "<item_id>",
-			}},
-		},
-		{
-			name: "RevisionID",
-			id:   "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>/revs/<rev_id>",
-			result: &ID{Descriptor: &RevisionID{
-				ItemID: ItemID{
-					CollectionID: CollectionID{
-						EnvironmentID: EnvironmentID{
-							SpaceID:       SpaceID{SpaceID: "<space_id>"},
-							EnvironmentID: "<env_id>",
-						},
-						CollectionID: "<collection_id>",
-					},
-					ItemID: "<item_id>",
-				},
-				RevisionID: "<rev_id>",
-			}},
-		},
-		{
-			name: "FieldID",
-			id:   "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>/fields/<field_name>",
-			result: &ID{Descriptor: &FieldID{
-				ItemID: ItemID{
-					CollectionID: CollectionID{
-						EnvironmentID: EnvironmentID{
-							SpaceID:       SpaceID{SpaceID: "<space_id>"},
-							EnvironmentID: "<env_id>",
-						},
-						CollectionID: "<collection_id>",
-					},
-					ItemID: "<item_id>",
-				},
-				FieldName: "<field_name>",
-			}},
-		},
-		{
-			name:   "SystemID",
-			id:     "/system",
-			result: &ID{Descriptor: &SystemID{}},
-		},
-		{
-			name:      "With error #1: no backslash in the beginning of id",
-			id:        "spaces/<space_id>",
-			result:    nil,
-			wantError: true,
-		},
-		{
-			name:      "With error #2: backslash in the end of id",
-			id:        "/spaces/<space_id>/",
-			result:    nil,
-			wantError: true,
-		},
-		{
-			name:      "With error #3: typo in 'spaces'",
-			id:        "/space/<space_id>",
-			result:    nil,
-			wantError: true,
-		},
-		{
-			name:      "With error #4: no space_id in id",
-			id:        "/spaces",
-			result:    nil,
-			wantError: true,
-		},
-		{
-			name:      "With error #5: multiple backslashes in the end of id",
-			id:        "/spaces/<space_id>///",
-			result:    nil,
-			wantError: true,
-		},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			id, err := Parse(tt.id)
-			if tt.wantError {
-				require.Error(t, err)
-				return
-			}
-			require.NoError(t, err)
-			require.Equal(t, tt.result, id)
-			require.Equal(t, tt.id, id.String(), "проверяем корректность работы метода String, полученное ID должно совпадать с исходным")
-		})
-	}
-}
-
-func Test_Map(t *testing.T) {
-	tests := []struct {
-		name string
-		id   *ID
-	}{
-		{
-			name: "ServiceID",
-			id:   &ID{Descriptor: &ServiceID{ServiceID: "<service_id>"}},
-		},
-		{
-			name: "UserID",
-			id:   &ID{Descriptor: &UserID{UserID: "<user_id>"}},
-		},
-		{
-			name: "OrganizationID",
-			id:   &ID{Descriptor: &OrganizationID{OrganizationID: "<org_id>"}},
-		},
-		{
-			name: "SpaceID",
-			id:   &ID{Descriptor: &SpaceID{SpaceID: "<space_id>"}},
-		},
-		{
-			name: "ClientID",
-			id: &ID{Descriptor: &ClientID{
-				SpaceID:  SpaceID{SpaceID: "<space_id>"},
-				ClientID: "<client_id>",
-			}},
-		},
-		{
-			name: "RoleID",
-			id: &ID{Descriptor: &RoleID{
-				SpaceID: SpaceID{SpaceID: "<space_id>"},
-				RoleID:  "<role_id>",
-			}},
-		},
-		{
-			name: "EnvironmentID",
-			id: &ID{Descriptor: &EnvironmentID{
-				SpaceID:       SpaceID{SpaceID: "<space_id>"},
-				EnvironmentID: "<env_id>",
-			}},
-		},
-		{
-			name: "CollectionID",
-			id: &ID{Descriptor: &CollectionID{
-				EnvironmentID: EnvironmentID{
-					SpaceID:       SpaceID{SpaceID: "<space_id>"},
-					EnvironmentID: "<env_id>",
-				},
-				CollectionID: "<collection_id>",
-			}},
-		},
-		{
-			name: "Schema ID",
-			id: &ID{Descriptor: &SchemaID{
-				EnvironmentID: EnvironmentID{
-					SpaceID:       SpaceID{SpaceID: "<space_id>"},
-					EnvironmentID: "<env_id>",
-				},
-				CollectionID: "<collection_id>",
-			}},
-		},
-		{
-			name: "ItemID",
-			id: &ID{Descriptor: &ItemID{
-				CollectionID: CollectionID{
-					EnvironmentID: EnvironmentID{
-						SpaceID:       SpaceID{SpaceID: "<space_id>"},
-						EnvironmentID: "<env_id>",
-					},
-					CollectionID: "<collection_id>",
-				},
-				ItemID: "<item_id>",
-			}},
-		},
-		{
-			name: "RevisionID",
-			id: &ID{Descriptor: &RevisionID{
-				ItemID: ItemID{
-					CollectionID: CollectionID{
-						EnvironmentID: EnvironmentID{
-							SpaceID:       SpaceID{SpaceID: "<space_id>"},
-							EnvironmentID: "<env_id>",
-						},
-						CollectionID: "<collection_id>",
-					},
-					ItemID: "<item_id>",
-				},
-				RevisionID: "<rev_id>",
-			}},
-		},
-		{
-			name: "FieldID",
-			id: &ID{Descriptor: &FieldID{
-				ItemID: ItemID{
-					CollectionID: CollectionID{
-						EnvironmentID: EnvironmentID{
-							SpaceID:       SpaceID{SpaceID: "<space_id>"},
-							EnvironmentID: "<env_id>",
-						},
-						CollectionID: "<collection_id>",
-					},
-					ItemID: "<item_id>",
-				},
-				FieldName: "<field_name>",
-			}},
-		},
-		{
-			name: "SystemID",
-			id:   &ID{Descriptor: &SystemID{}},
-		},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			v, err := FromMap(tt.id.ToMap())
-			require.NoError(t, err)
-			assert.Equal(t, tt.id, v, "проверка FromMap для типа ID, должен быть равен исходному значению")
-			assert.Equal(t, v.ToMap(), tt.id.ToMap())
-		})
-	}
-}
diff --git a/id/item.go b/id/item.go
index 70c3e7be95b517a85e07e487eb00f94f9f73e14f..968175a2cb4f4429199d8848bd9409828461b08a 100644
--- a/id/item.go
+++ b/id/item.go
@@ -1,61 +1,68 @@
 package id
 
+import (
+	"fmt"
+)
+
 const (
 	Item        = "item"
 	ItemsPrefix = "items"
 )
 
-type ItemID struct {
-	CollectionID
+type ItemId struct {
+	CollectionId
 	ItemID string `json:"item_id,omitempty" bson:"item_id,omitempty"`
 }
 
-func (t *ItemID) Type() string { return Item }
+var _ Descriptor = &ItemId{}
+
+func (i *ItemId) New() Descriptor {
+	return &ItemId{}
+}
+
+func (i *ItemId) Type() string { return Item }
 
-func (t *ItemID) String() string {
-	return Join(t.CollectionID.String(), ItemsPrefix, t.ItemID)
+func (i *ItemId) String() string {
+	return Join(i.CollectionId.String(), ItemsPrefix, i.ItemID)
 
 }
 
-func (t *ItemID) ToMap() map[string]any {
-	m := t.CollectionID.ToMap()
-	m["item_id"] = t.ItemID
+func (i *ItemId) Map() map[string]any {
+	m := i.CollectionId.Map()
+	m["item_id"] = i.ItemID
 	m["type"] = Item
 	return m
 }
 
-func (t *ItemID) FromMap(m map[string]any) error {
-	if err := t.CollectionID.FromMap(m); err != nil {
+func (i *ItemId) FromParts(parts []string) error {
+	if len(parts) != 8 || parts[6] != ItemsPrefix {
+		return ErrInvalidID
+	}
+
+	if err := i.CollectionId.FromParts(parts[:6]); err != nil {
 		return err
 	}
-	t.ItemID = m["item_id"].(string)
+
+	i.ItemID = parts[7]
 	return nil
 }
 
-func (t *ItemID) Validate() error {
-	if t.ItemID == "" {
-		return ErrInvalidID
+func (i *ItemId) FromMap(m map[string]any) error {
+	i.ItemID = m["item_id"].(string)
+	if i.ItemID == "" {
+		return fmt.Errorf("%w: ItemId required", ErrInvalidID)
 	}
 
-	return t.CollectionID.Validate()
+	return i.CollectionId.FromMap(m)
 }
 
-func parseItemID(parts []string) (*ItemID, error) {
-	if len(parts) != 8 || parts[6] != ItemsPrefix {
-		return nil, ErrInvalidID
+func (i *ItemId) Validate() error {
+	if i.ItemID == "" {
+		return fmt.Errorf("%w: ItemId required", ErrInvalidID)
 	}
-
-	collID, err := parseCollectionID(parts[:6])
-	if err != nil {
-		return nil, err
-	}
-
-	var id ItemID
-	id.CollectionID = *collID
-	id.ItemID = parts[7]
-	return &id, nil
+	return i.CollectionId.Validate()
 }
 
-func NewItemID(spaceID, envID, collID, id string) *ID {
-	return &ID{Descriptor: &ItemID{CollectionID: CollectionID{EnvironmentID: EnvironmentID{SpaceID: SpaceID{SpaceID: spaceID}, EnvironmentID: envID}, CollectionID: collID}, ItemID: id}}
+func NewItemId(spaceID, envID, collID, id string) *ObjectId {
+	return &ObjectId{Descriptor: &ItemId{CollectionId: CollectionId{EnvironmentId: EnvironmentId{SpaceId: SpaceId{SpaceID: spaceID}, EnvironmentID: envID}, CollectionID: collID}, ItemID: id}}
 }
diff --git a/id/json.go b/id/json.go
deleted file mode 100644
index 34b87589dfd81e6b793c5812fb2b90f43fb724f1..0000000000000000000000000000000000000000
--- a/id/json.go
+++ /dev/null
@@ -1,23 +0,0 @@
-package id
-
-import (
-	jsoniter "github.com/json-iterator/go"
-)
-
-func (id *ID) MarshalJSON() ([]byte, error) {
-	return jsoniter.Marshal(id.String())
-}
-
-func (id *ID) UnmarshalJSON(b []byte) error {
-	var s string
-	var err error
-	if err = jsoniter.Unmarshal(b, &s); err != nil {
-		return err
-	}
-	t, err := Parse(s)
-	if err != nil {
-		return err
-	}
-	*id = *t
-	return nil
-}
diff --git a/id/json_test.go b/id/json_test.go
index afe831d4ef6038eaa6bc8a565b0623334cdf6bc8..0da4f292db94ffc93558b76ba702f63b0ce5a121 100644
--- a/id/json_test.go
+++ b/id/json_test.go
@@ -11,59 +11,59 @@ import (
 func TestID_MarshalUnmarshalJSON(t *testing.T) {
 	tests := []struct {
 		name string
-		id   *ID
+		id   *ObjectId
 	}{
 		{
 			name: "OrganizationID",
-			id:   &ID{Descriptor: &OrganizationID{OrganizationID: "1"}},
+			id:   &ObjectId{Descriptor: &OrganizationId{OrganizationID: "1"}},
 		},
 		{
 			name: "UserID",
-			id:   &ID{Descriptor: &UserID{UserID: "1"}},
+			id:   &ObjectId{Descriptor: &UserId{UserID: "1"}},
 		},
 		{
 			name: "ServiceID",
-			id:   &ID{Descriptor: &ServiceID{ServiceID: "1"}},
+			id:   &ObjectId{Descriptor: &ServiceId{ServiceID: "1"}},
 		},
 		{
-			name: "SpaceID",
-			id:   &ID{Descriptor: &SpaceID{SpaceID: "1"}},
+			name: "SpaceId",
+			id:   &ObjectId{Descriptor: &SpaceId{SpaceID: "1"}},
 		},
 		{
 			name: "EnvironmentID",
-			id:   &ID{Descriptor: &EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}},
+			id:   &ObjectId{Descriptor: &EnvironmentId{EnvironmentID: "1", SpaceId: SpaceId{SpaceID: "1"}}},
 		},
 		{
 			name: "ClientID",
-			id:   &ID{Descriptor: &ClientID{ClientID: "1", SpaceID: SpaceID{SpaceID: "1"}}},
+			id:   &ObjectId{Descriptor: &ClientId{ClientID: "1", SpaceId: SpaceId{SpaceID: "1"}}},
 		},
 		{
 			name: "RoleID",
-			id:   &ID{Descriptor: &RoleID{RoleID: "1", SpaceID: SpaceID{SpaceID: "1"}}},
+			id:   &ObjectId{Descriptor: &RoleId{RoleID: "1", SpaceId: SpaceId{SpaceID: "1"}}},
 		},
 		{
-			name: "CollectionID",
-			id:   &ID{Descriptor: &CollectionID{CollectionID: "1", EnvironmentID: EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}},
+			name: "CollectionId",
+			id:   &ObjectId{Descriptor: &CollectionId{CollectionID: "1", EnvironmentId: EnvironmentId{EnvironmentID: "1", SpaceId: SpaceId{SpaceID: "1"}}}},
 		},
 		{
 			name: "SchemaID",
-			id:   &ID{Descriptor: &SchemaID{CollectionID: "1", EnvironmentID: EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}},
+			id:   &ObjectId{Descriptor: &SchemaId{CollectionID: "1", EnvironmentId: EnvironmentId{EnvironmentID: "1", SpaceId: SpaceId{SpaceID: "1"}}}},
 		},
 		{
-			name: "ItemID",
-			id:   &ID{Descriptor: &ItemID{ItemID: "1", CollectionID: CollectionID{CollectionID: "1", EnvironmentID: EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}}},
+			name: "ItemId",
+			id:   &ObjectId{Descriptor: &ItemId{ItemID: "1", CollectionId: CollectionId{CollectionID: "1", EnvironmentId: EnvironmentId{EnvironmentID: "1", SpaceId: SpaceId{SpaceID: "1"}}}}},
 		},
 		{
 			name: "RevisionID",
-			id:   &ID{Descriptor: &RevisionID{RevisionID: "1", ItemID: ItemID{ItemID: "1", CollectionID: CollectionID{CollectionID: "1", EnvironmentID: EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}}}},
+			id:   &ObjectId{Descriptor: &RevisionId{RevisionID: "1", ItemId: ItemId{ItemID: "1", CollectionId: CollectionId{CollectionID: "1", EnvironmentId: EnvironmentId{EnvironmentID: "1", SpaceId: SpaceId{SpaceID: "1"}}}}}},
 		},
 		{
-			name: "FieldID",
-			id:   &ID{Descriptor: &FieldID{FieldName: "1", ItemID: ItemID{ItemID: "1", CollectionID: CollectionID{CollectionID: "1", EnvironmentID: EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}}}},
+			name: "FieldId",
+			id:   &ObjectId{Descriptor: &FieldId{Field: "1", ItemId: ItemId{ItemID: "1", CollectionId: CollectionId{CollectionID: "1", EnvironmentId: EnvironmentId{EnvironmentID: "1", SpaceId: SpaceId{SpaceID: "1"}}}}}},
 		},
 		{
 			name: "SystemID",
-			id:   &ID{Descriptor: &SystemID{}},
+			id:   &ObjectId{Descriptor: &SystemId{}},
 		},
 	}
 	for _, tt := range tests {
@@ -71,7 +71,7 @@ func TestID_MarshalUnmarshalJSON(t *testing.T) {
 			b, err := jsoniter.Marshal(&tt.id)
 			require.NoError(t, err)
 
-			var i ID
+			var i ObjectId
 			require.NoError(t, jsoniter.Unmarshal(b, &i))
 			assert.Equal(t, tt.id, &i, "после Unmarshal объект должен быть идентичен исходному")
 		})
@@ -80,13 +80,13 @@ func TestID_MarshalUnmarshalJSON(t *testing.T) {
 
 func TestID_ExampleJSON(t *testing.T) {
 	type data struct {
-		ID     *ID
+		ID     *ObjectId
 		Text   string
 		Number int
 	}
 
 	test := &data{
-		ID:     &ID{Descriptor: &SpaceID{SpaceID: Space}},
+		ID:     &ObjectId{Descriptor: &SpaceId{SpaceID: Space}},
 		Text:   "text",
 		Number: 1,
 	}
diff --git a/id/object_id.go b/id/object_id.go
new file mode 100644
index 0000000000000000000000000000000000000000..e84ea1c0fff9960bfef7584fbd68699509cfa2a6
--- /dev/null
+++ b/id/object_id.go
@@ -0,0 +1,102 @@
+package id
+
+import (
+	jsoniter "github.com/json-iterator/go"
+	"go.mongodb.org/mongo-driver/bson"
+	"go.mongodb.org/mongo-driver/bson/bsonrw"
+	"go.mongodb.org/mongo-driver/bson/bsontype"
+	"strings"
+)
+
+const Separator = '/'
+
+type Descriptor interface {
+	New() Descriptor
+	String() string
+	Type() string
+	Map() map[string]any
+	Validate() error
+	FromMap(map[string]any) error
+	FromParts([]string) error
+}
+
+type ObjectId struct {
+	Descriptor
+}
+
+func NewObjectId(in any) (*ObjectId, error) {
+	switch v := in.(type) {
+	case Descriptor:
+		return &ObjectId{Descriptor: v}, nil
+	case string:
+		return FromString(v)
+	case map[string]any:
+		return FromMap(v)
+	}
+	return FromObject(in)
+}
+
+func MustObjectId(in any) *ObjectId {
+	oid, err := NewObjectId(in)
+	if err != nil {
+		panic(err)
+	}
+	return oid
+}
+
+func (oid *ObjectId) MarshalJSON() ([]byte, error) {
+	return jsoniter.Marshal(oid.String())
+}
+
+func (oid *ObjectId) UnmarshalJSON(b []byte) error {
+	var s string
+	var err error
+	if err = jsoniter.Unmarshal(b, &s); err != nil {
+		return err
+	}
+	t, err := FromString(s)
+	if err != nil {
+		return err
+	}
+	*oid = *t
+	return nil
+}
+
+func (oid *ObjectId) MarshalBSONValue() (bsontype.Type, []byte, error) {
+	return bson.MarshalValue(oid.String())
+}
+
+func (oid *ObjectId) UnmarshalBSONValue(btype bsontype.Type, data []byte) error {
+	if btype != bson.TypeString {
+		return ErrInvalidType
+	}
+	dec, err := bson.NewDecoder(bsonrw.NewBSONValueReader(btype, data))
+	if err != nil {
+		return err
+	}
+	var str string
+	if err = dec.Decode(&str); err != nil {
+		return err
+	}
+	t, err := FromString(str)
+	if err != nil {
+		return err
+	}
+	*oid = *t
+	return nil
+}
+
+func Join(parts ...string) string {
+	s := strings.Join(parts, string(Separator))
+	if s[0] != Separator {
+		s = string(Separator) + s
+	}
+	return s
+}
+
+func Split(id string) []string {
+	if id[0] != Separator {
+		return nil
+	}
+	return strings.Split(id[1:], string(Separator))
+}
diff --git a/id/object_id_test.go b/id/object_id_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..4f8d018139ce0b125e09124e5113e94c8031f5b3
--- /dev/null
+++ b/id/object_id_test.go
@@ -0,0 +1,253 @@
+package id
+
+import (
+	"testing"
+
+	"git.perx.ru/perxis/perxis-go/pkg/items"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func Test_ParseID(t *testing.T) {
+	tests := []struct {
+		name      string
+		id        any
+		result    *ObjectId
+		wantError bool
+	}{
+		{
+			name:   "SpaceId",
+			id:     "/spaces/<space_id>",
+			result: MustObjectId("/spaces/<space_id>"),
+		},
+		{
+			name:   "ServiceID",
+			id:     "/services/<service_id>",
+			result: MustObjectId("/services/<service_id>"),
+		},
+		{
+			name:   "UserID",
+			id:     "/users/<user_id>",
+			result: MustObjectId("/users/<user_id>"),
+		},
+		{
+			name:   "OrganizationID",
+			id:     "/orgs/<org_id>",
+			result: MustObjectId("/orgs/<org_id>"),
+		},
+		{
+			name:   "ClientID",
+			id:     "/spaces/<space_id>/clients/<client_id>",
+			result: MustObjectId("/spaces/<space_id>/clients/<client_id>"),
+		},
+		{
+			name:   "RoleID",
+			id:     "/spaces/<space_id>/roles/<role_id>",
+			result: MustObjectId("/spaces/<space_id>/roles/<role_id>"),
+		},
+		{
+			name:   "EnvironmentID",
+			id:     "/spaces/<space_id>/envs/<env_id>",
+			result: MustObjectId("/spaces/<space_id>/envs/<env_id>"),
+		},
+		{
+			name:   "CollectionId",
+			id:     "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>",
+			result: MustObjectId("/spaces/<space_id>/envs/<env_id>/cols/<collection_id>"),
+		},
+		{
+			name:   "SchemaID",
+			id:     "/spaces/<space_id>/envs/<env_id>/schema/<collection_id>",
+			result: MustObjectId("/spaces/<space_id>/envs/<env_id>/schema/<collection_id>"),
+		},
+		{
+			name:   "ItemId",
+			id:     "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>",
+			result: MustObjectId("/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>"),
+		},
+		{
+			name:   "RevisionID",
+			id:     "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>/revs/<rev_id>",
+			result: MustObjectId("/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>/revs/<rev_id>"),
+		},
+		{
+			name:   "FieldId",
+			id:     "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>/fields/<field_name>",
+			result: MustObjectId("/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>/fields/<field_name>"),
+		},
+		{
+			name:      "With error #1: no backslash in the beginning of id",
+			id:        "spaces/<space_id>",
+			result:    nil,
+			wantError: true,
+		},
+		{
+			name:      "With error #2: backslash in the end of id",
+			id:        "/spaces/<space_id>/",
+			result:    nil,
+			wantError: true,
+		},
+		{
+			name:      "With error #3: typo in 'spaces'",
+			id:        "/space/<space_id>",
+			result:    nil,
+			wantError: true,
+		},
+		{
+			name:      "With error #4: no space_id in id",
+			id:        "/spaces",
+			result:    nil,
+			wantError: true,
+		},
+		{
+			name:      "With error #5: multiple backslashes in the end of id",
+			id:        "/spaces/<space_id>///",
+			result:    nil,
+			wantError: true,
+		},
+		{
+			name:      "With error #6: nil value",
+			id:        nil,
+			wantError: true,
+		},
+		{
+			name:      "With error #7: nil object value",
+			id:        (*items.Item)(nil),
+			wantError: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			id, err := NewObjectId(tt.id)
+			if tt.wantError {
+				require.Error(t, err)
+				return
+			}
+			require.NoError(t, err)
+			require.Equal(t, tt.result, id)
+			require.Equal(t, tt.id, id.String())
+		})
+	}
+}
+
+func Test_Map(t *testing.T) {
+	tests := []struct {
+		name string
+		id   *ObjectId
+	}{
+		{
+			name: "ServiceID",
+			id:   &ObjectId{Descriptor: &ServiceId{ServiceID: "<service_id>"}},
+		},
+		{
+			name: "UserID",
+			id:   &ObjectId{Descriptor: &UserId{UserID: "<user_id>"}},
+		},
+		{
+			name: "OrganizationID",
+			id:   &ObjectId{Descriptor: &OrganizationId{OrganizationID: "<org_id>"}},
+		},
+		{
+			name: "SpaceId",
+			id:   &ObjectId{Descriptor: &SpaceId{SpaceID: "<space_id>"}},
+		},
+		{
+			name: "ClientID",
+			id: &ObjectId{Descriptor: &ClientId{
+				SpaceId:  SpaceId{SpaceID: "<space_id>"},
+				ClientID: "<client_id>",
+			}},
+		},
+		{
+			name: "RoleID",
+			id: &ObjectId{Descriptor: &RoleId{
+				SpaceId: SpaceId{SpaceID: "<space_id>"},
+				RoleID:  "<role_id>",
+			}},
+		},
+		{
+			name: "EnvironmentID",
+			id: &ObjectId{Descriptor: &EnvironmentId{
+				SpaceId:       SpaceId{SpaceID: "<space_id>"},
+				EnvironmentID: "<env_id>",
+			}},
+		},
+		{
+			name: "CollectionId",
+			id: &ObjectId{Descriptor: &CollectionId{
+				EnvironmentId: EnvironmentId{
+					SpaceId:       SpaceId{SpaceID: "<space_id>"},
+					EnvironmentID: "<env_id>",
+				},
+				CollectionID: "<collection_id>",
+			}},
+		},
+		{
+			name: "Schema ID",
+			id: &ObjectId{Descriptor: &SchemaId{
+				EnvironmentId: EnvironmentId{
+					SpaceId:       SpaceId{SpaceID: "<space_id>"},
+					EnvironmentID: "<env_id>",
+				},
+				CollectionID: "<collection_id>",
+			}},
+		},
+		{
+			name: "ItemId",
+			id: &ObjectId{Descriptor: &ItemId{
+				CollectionId: CollectionId{
+					EnvironmentId: EnvironmentId{
+						SpaceId:       SpaceId{SpaceID: "<space_id>"},
+						EnvironmentID: "<env_id>",
+					},
+					CollectionID: "<collection_id>",
+				},
+				ItemID: "<item_id>",
+			}},
+		},
+		{
+			name: "RevisionID",
+			id: &ObjectId{Descriptor: &RevisionId{
+				ItemId: ItemId{
+					CollectionId: CollectionId{
+						EnvironmentId: EnvironmentId{
+							SpaceId:       SpaceId{SpaceID: "<space_id>"},
+							EnvironmentID: "<env_id>",
+						},
+						CollectionID: "<collection_id>",
+					},
+					ItemID: "<item_id>",
+				},
+				RevisionID: "<rev_id>",
+			}},
+		},
+		{
+			name: "FieldId",
+			id: &ObjectId{Descriptor: &FieldId{
+				ItemId: ItemId{
+					CollectionId: CollectionId{
+						EnvironmentId: EnvironmentId{
+							SpaceId:       SpaceId{SpaceID: "<space_id>"},
+							EnvironmentID: "<env_id>",
+						},
+						CollectionID: "<collection_id>",
+					},
+					ItemID: "<item_id>",
+				},
+				Field: "<field_name>",
+			}},
+		},
+		{
+			name: "SystemID",
+			id:   &ObjectId{Descriptor: &SystemId{}},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			v, err := FromMap(tt.id.Map())
+			require.NoError(t, err)
+			assert.Equal(t, tt.id, v, "проверка FromMap для типа ID, должен быть равен исходному значению")
+			assert.Equal(t, v.Map(), tt.id.Map())
+		})
+	}
+}
diff --git a/id/organization.go b/id/organization.go
index fe9d22837977b486e78689ebee5f426bdee80fcf..5aedfb709ef6ff5bde9fd7f06bcba49f07fbd643 100644
--- a/id/organization.go
+++ b/id/organization.go
@@ -1,49 +1,60 @@
 package id
 
+import (
+	"fmt"
+)
+
 const (
 	Organization        = "organization"
 	OrganizationsPrefix = "orgs"
 )
 
-type OrganizationID struct {
+var _ Descriptor = &OrganizationId{}
+
+type OrganizationId struct {
 	OrganizationID string `json:"organization_id,omitempty" bson:"organization_id,omitempty"`
 }
 
-func (t *OrganizationID) Type() string { return Organization }
+func (id *OrganizationId) New() Descriptor {
+	return &OrganizationId{}
+}
+
+func (id *OrganizationId) Type() string { return Organization }
 
-func (t *OrganizationID) String() string {
-	return Join(OrganizationsPrefix, t.OrganizationID)
+func (id *OrganizationId) String() string {
+	return Join(OrganizationsPrefix, id.OrganizationID)
 }
 
-func (t *OrganizationID) ToMap() map[string]any {
-	return map[string]any{
-		"organization_id": t.OrganizationID,
-		"type":            Organization,
+func (id *OrganizationId) FromParts(parts []string) error {
+	if len(parts) != 2 || parts[0] != OrganizationsPrefix {
+		return ErrInvalidID
 	}
+	id.OrganizationID = parts[1]
+	return nil
 }
 
-func (t *OrganizationID) FromMap(m map[string]any) error {
-	t.OrganizationID = m["organization_id"].(string)
-	return nil
+func (id *OrganizationId) Map() map[string]any {
+	return map[string]any{
+		"organization_id": id.OrganizationID,
+		"type":            id.Type(),
+	}
 }
 
-func (t *OrganizationID) Validate() error {
-	if t.OrganizationID == "" {
-		return ErrInvalidID
+func (id *OrganizationId) FromMap(m map[string]any) error {
+	id.OrganizationID, _ = m["organization_id"].(string)
+	if id.OrganizationID == "" {
+		return fmt.Errorf("%w: OrganizationId required", ErrInvalidID)
 	}
 	return nil
 }
 
-func parseOrganizationID(parts []string) (*OrganizationID, error) {
-	var id OrganizationID
-	if len(parts) != 2 || parts[0] != OrganizationsPrefix {
-		return nil, ErrInvalidID
+func (id *OrganizationId) Validate() error {
+	if id.OrganizationID == "" {
+		return fmt.Errorf("%w: OrganizationId required", ErrInvalidID)
 	}
-
-	id.OrganizationID = parts[1]
-	return &id, nil
+	return nil
 }
 
-func NewOrganizationID(id string) *ID {
-	return &ID{Descriptor: &OrganizationID{OrganizationID: id}}
+func NewOrganizationId(id string) *ObjectId {
+	return &ObjectId{Descriptor: &OrganizationId{OrganizationID: id}}
 }
diff --git a/id/registry.go b/id/registry.go
new file mode 100644
index 0000000000000000000000000000000000000000..0ffec0240952b742c33a4763c6ee49fa210ce4f0
--- /dev/null
+++ b/id/registry.go
@@ -0,0 +1,121 @@
+package id
+
+import (
+	"fmt"
+	"reflect"
+)
+
+var registry = NewRegistry()
+
+type ObjectHandler func(interface{}) *ObjectId
+
+type Registry struct {
+	handlers    map[reflect.Type]ObjectHandler
+	descriptors map[string]Descriptor
+}
+
+func NewRegistry() *Registry {
+	return &Registry{
+		handlers:    make(map[reflect.Type]ObjectHandler),
+		descriptors: make(map[string]Descriptor),
+	}
+}
+
+// Register method adds a handler based on its type.
+func (r *Registry) RegisterObjectHandler(t reflect.Type, h ObjectHandler) {
+	r.handlers[t] = h
+}
+
+func (r *Registry) RegisterDescriptor(d Descriptor) {
+	r.descriptors[d.Type()] = d
+}
+
+func (r *Registry) FromParts(s string) (*ObjectId, error) {
+	parts := Split(s)
+
+	for _, d := range r.descriptors {
+		id := d.New()
+		if err := id.FromParts(parts); err == nil {
+			return &ObjectId{Descriptor: id}, nil
+		}
+	}
+
+	return nil, fmt.Errorf("%w: %s", ErrInvalidID, s)
+}
+
+func (r *Registry) FromMap(m map[string]interface{}) (*ObjectId, error) {
+	t, ok := m["type"].(string)
+	if !ok {
+		return nil, ErrTypeNotFound
+	}
+
+	if d, ok := r.descriptors[t]; ok {
+		id := d.New()
+		if err := id.FromMap(m); err != nil {
+			return nil, err
+		}
+		return &ObjectId{Descriptor: id}, nil
+	}
+
+	return nil, fmt.Errorf("%s: %s", t, ErrInvalidID)
+}
+
+func (r *Registry) FromObject(v interface{}) (*ObjectId, error) {
+	value := reflect.ValueOf(v)
+	if v == nil || (value.Kind() == reflect.Ptr && value.IsNil()) {
+		return nil, fmt.Errorf("object value is nil")
+	}
+
+	t := value.Type()
+	if handler, ok := r.handlers[t]; ok {
+		i := handler(v)
+		if i == nil {
+			panic(fmt.Sprintf("handler for %s returned nil", t))
+		}
+		return i, nil
+	}
+	return nil, fmt.Errorf("%s : %w", t, ErrHandlerNotFound)
+}
+
+func FromMap(m map[string]interface{}) (*ObjectId, error) {
+	return registry.FromMap(m)
+}
+
+func FromString(s string) (*ObjectId, error) {
+	return registry.FromParts(s)
+}
+
+type ObjectIdentifier interface {
+	ObjectId() *ObjectId
+}
+
+func FromObject(v interface{}) (*ObjectId, error) {
+	if id, ok := v.(ObjectIdentifier); ok {
+		return id.ObjectId(), nil
+	}
+	return registry.FromObject(v)
+}
+
+func RegisterSystemIds(r *Registry) {
+	r.RegisterDescriptor(&SpaceId{})
+	r.RegisterDescriptor(&EnvironmentId{})
+	r.RegisterDescriptor(&CollectionId{})
+	r.RegisterDescriptor(&SchemaId{})
+	r.RegisterDescriptor(&ItemId{})
+	r.RegisterDescriptor(&RevisionId{})
+	r.RegisterDescriptor(&FieldId{})
+	r.RegisterDescriptor(&ClientId{})
+	r.RegisterDescriptor(&RoleId{})
+	r.RegisterDescriptor(&UserId{})
+	r.RegisterDescriptor(&SystemId{})
+	r.RegisterDescriptor(&ServiceId{})
+	r.RegisterDescriptor(&OrganizationId{})
+}
+
+func GetRegistry() *Registry {
+	return registry
+}
+
+func init() {
+	RegisterSystemIds(registry)
+}
diff --git a/id/revision.go b/id/revision.go
index 0cb417e132fe1683f1e53a081e7c6244820478df..f8a2ecb4b893d71734e72bd1911a32173a3fd17c 100644
--- a/id/revision.go
+++ b/id/revision.go
@@ -1,61 +1,82 @@
 package id
 
+import "fmt"
+
 const (
 	Revision        = "revision"
 	RevisionsPrefix = "revs"
 )
 
-type RevisionID struct {
-	ItemID
+type RevisionId struct {
+	ItemId
 	RevisionID string `json:"rev_id" bson:"rev_id,omitempty"`
 }
 
-func (t *RevisionID) Type() string { return Revision }
-
-func (t *RevisionID) String() string {
-	return Join(t.ItemID.String(), RevisionsPrefix, t.RevisionID)
+var _ Descriptor = &RevisionId{}
 
+func (id *RevisionId) New() Descriptor {
+	return &RevisionId{}
 }
 
-func (t *RevisionID) ToMap() map[string]any {
-	m := t.ItemID.ToMap()
-	m["rev_id"] = t.RevisionID
-	m["type"] = Revision
-	return m
+func (id *RevisionId) Type() string { return Revision }
+
+func (id *RevisionId) String() string {
+	return Join(id.ItemId.String(), RevisionsPrefix, id.RevisionID)
+
 }
 
-func (t *RevisionID) FromMap(m map[string]any) error {
-	if err := t.ItemID.FromMap(m); err != nil {
+func (id *RevisionId) FromParts(parts []string) error {
+	if len(parts) != 10 || parts[8] != RevisionsPrefix {
+		return ErrInvalidID
+	}
+	if err := id.ItemId.FromParts(parts[:8]); err != nil {
 		return err
 	}
-	t.RevisionID = m["rev_id"].(string)
+	id.RevisionID = parts[9]
 	return nil
 }
 
-func (t *RevisionID) Validate() error {
-	if t.RevisionID == "" {
-		return ErrInvalidID
-	}
-
-	return t.ItemID.Validate()
+func (id *RevisionId) Map() map[string]any {
+	m := id.ItemId.Map()
+	m["rev_id"] = id.RevisionID
+	m["type"] = Revision
+	return m
 }
 
-func parseRevisionID(parts []string) (*RevisionID, error) {
-	if len(parts) != 10 || parts[8] != RevisionsPrefix {
-		return nil, ErrInvalidID
+func (id *RevisionId) FromMap(m map[string]any) error {
+	id.RevisionID = m["rev_id"].(string)
+	if id.RevisionID == "" {
+		return fmt.Errorf("%w: RevisionId required", ErrInvalidID)
 	}
 
-	itemID, err := parseItemID(parts[:8])
-	if err != nil {
-		return nil, err
+	if err := id.ItemId.FromMap(m); err != nil {
+		return err
 	}
+	return nil
+}
 
-	var id RevisionID
-	id.ItemID = *itemID
-	id.RevisionID = parts[9]
-	return &id, nil
+func (id *RevisionId) Validate() error {
+	if id.RevisionID == "" {
+		return fmt.Errorf("%w: RevisionId required", ErrInvalidID)
+	}
+
+	return id.ItemId.Validate()
 }
 
-func NewRevisionID(spaceID, envID, collID, itemID, id string) *ID {
-	return &ID{Descriptor: &RevisionID{ItemID: ItemID{CollectionID: CollectionID{EnvironmentID: EnvironmentID{SpaceID: SpaceID{SpaceID: spaceID}, EnvironmentID: envID}, CollectionID: collID}, ItemID: itemID}, RevisionID: id}}
+func NewRevisionID(spaceID, envID, collID, itemID, id string) *ObjectId {
+	return &ObjectId{Descriptor: &RevisionId{
+		ItemId: ItemId{
+			CollectionId: CollectionId{
+				EnvironmentId: EnvironmentId{
+					SpaceId: SpaceId{
+						SpaceID: spaceID,
+					},
+					EnvironmentID: envID,
+				},
+				CollectionID: collID,
+			},
+			ItemID: itemID,
+		},
+		RevisionID: id,
+	}}
 }
diff --git a/id/role.go b/id/role.go
index abb6537fc605d3f01f644a67a724cc81e3a6b54b..d9f8f01989b5bb9cca5da38acad867356b4dea92 100644
--- a/id/role.go
+++ b/id/role.go
@@ -1,61 +1,65 @@
 package id
 
+import (
+	"fmt"
+)
+
 const (
 	Role        = "role"
 	RolesPrefix = "roles"
 )
 
-type RoleID struct {
-	SpaceID
+var _ Descriptor = &RoleId{}
+
+type RoleId struct {
+	SpaceId
 	RoleID string `json:"role_id,omitempty" bson:"role_id,omitempty"`
 }
 
-func (t *RoleID) Type() string { return Role }
-
-func (t *RoleID) String() string {
-	return Join(t.SpaceID.String(), RolesPrefix, t.RoleID)
-
+func (id *RoleId) New() Descriptor {
+	return &RoleId{}
 }
 
-func (t *RoleID) ToMap() map[string]any {
-	m := t.SpaceID.ToMap()
-	m["role_id"] = t.RoleID
-	m["type"] = Role
-	return m
+func (id *RoleId) Type() string { return Role }
+
+func (id *RoleId) String() string {
+	return Join(id.SpaceId.String(), RolesPrefix, id.RoleID)
 }
 
-func (t *RoleID) FromMap(m map[string]any) error {
-	if err := t.SpaceID.FromMap(m); err != nil {
+func (id *RoleId) FromParts(parts []string) error {
+	if len(parts) != 4 || parts[2] != RolesPrefix {
+		return ErrInvalidID
+	}
+	if err := id.SpaceId.FromParts(parts[:2]); err != nil {
 		return err
 	}
-	t.RoleID = m["role_id"].(string)
+	id.RoleID = parts[3]
 	return nil
 }
 
-func (t *RoleID) Validate() error {
-	if t.RoleID == "" {
-		return ErrInvalidID
-	}
-
-	return t.SpaceID.Validate()
+func (id *RoleId) Map() map[string]any {
+	m := id.SpaceId.Map()
+	m["role_id"] = id.RoleID
+	m["type"] = Role
+	return m
 }
 
-func parseRoleID(parts []string) (*RoleID, error) {
-	if len(parts) != 4 || parts[2] != RolesPrefix {
-		return nil, ErrInvalidID
+func (id *RoleId) FromMap(m map[string]any) error {
+	id.RoleID = m["role_id"].(string)
+	if id.RoleID == "" {
+		return fmt.Errorf("%w: RoleID required", ErrInvalidID)
 	}
+	return id.SpaceId.FromMap(m)
+}
 
-	spaceID, err := parseSpaceID(parts[:2])
-	if err != nil {
-		return nil, err
+func (id *RoleId) Validate() error {
+	if id.RoleID == "" {
+		return fmt.Errorf("%w: RoleID required", ErrInvalidID)
 	}
 
-	var id RoleID
-	id.SpaceID = *spaceID
-	id.RoleID = parts[3]
-	return &id, nil
+	return id.SpaceId.Validate()
 }
 
-func NewRoleID(spaceID, id string) *ID {
-	return &ID{Descriptor: &RoleID{SpaceID: SpaceID{SpaceID: spaceID}, RoleID: id}}
+func NewRoleId(spaceID, id string) *ObjectId {
+	return &ObjectId{Descriptor: &RoleId{SpaceId: SpaceId{SpaceID: spaceID}, RoleID: id}}
 }
diff --git a/id/schema.go b/id/schema.go
index e3afee8c6cf29bca6cae1f1eedd72253c47362a5..b1280f81ca440ae78ce128e471292e26a2db4a97 100644
--- a/id/schema.go
+++ b/id/schema.go
@@ -1,60 +1,70 @@
 package id
 
+import "fmt"
+
 const (
 	Schema       = "schema"
 	SchemaPrefix = "schema"
 )
 
-type SchemaID struct {
-	EnvironmentID
+var _ Descriptor = &SchemaId{}
+
+type SchemaId struct {
+	EnvironmentId
 	CollectionID string `json:"col_id" bson:"col_id,omitempty"`
 }
 
-func (t *SchemaID) Type() string { return Schema }
+func (id *SchemaId) New() Descriptor {
+	return &SchemaId{}
+}
+
+func (id *SchemaId) Type() string { return Schema }
 
-func (t *SchemaID) String() string {
-	return Join(t.EnvironmentID.String(), SchemaPrefix, t.CollectionID)
+func (id *SchemaId) String() string {
+	return Join(id.EnvironmentId.String(), SchemaPrefix, id.CollectionID)
 }
 
-func (t *SchemaID) ToMap() map[string]any {
-	m := t.EnvironmentID.ToMap()
-	m["col_id"] = t.CollectionID
+func (id *SchemaId) Map() map[string]any {
+	m := id.EnvironmentId.Map()
+	m["col_id"] = id.CollectionID
 	m["type"] = Schema
 	return m
 }
 
-func (t *SchemaID) FromMap(m map[string]any) error {
-	if err := t.EnvironmentID.FromMap(m); err != nil {
+func (id *SchemaId) FromParts(parts []string) error {
+	if len(parts) != 6 || parts[4] != SchemaPrefix {
+		return ErrInvalidID
+	}
+	if err := id.EnvironmentId.FromParts(parts[:4]); err != nil {
 		return err
 	}
-	t.CollectionID = m["col_id"].(string)
+	id.CollectionID = parts[5]
 	return nil
 }
 
-func (t *SchemaID) Validate() error {
-	if t.CollectionID == "" {
-		return ErrInvalidID
+func (id *SchemaId) FromMap(m map[string]any) error {
+	id.CollectionID, _ = m["col_id"].(string)
+	if id.CollectionID == "" {
+		return fmt.Errorf("%w: SchemaID required", ErrInvalidID)
 	}
-
-	return t.EnvironmentID.Validate()
+	return id.EnvironmentId.FromMap(m)
 }
 
-func parseSchemaID(parts []string) (*SchemaID, error) {
-	if len(parts) != 6 || parts[4] != SchemaPrefix {
-		return nil, ErrInvalidID
+func (id *SchemaId) Validate() error {
+	if id.CollectionID == "" {
+		return fmt.Errorf("%w: SchemaID required", ErrInvalidID)
 	}
-
-	envID, err := parseEnvironmentID(parts[:4])
-	if err != nil {
-		return nil, err
-	}
-
-	var id SchemaID
-	id.EnvironmentID = *envID
-	id.CollectionID = parts[5]
-	return &id, nil
+	return id.EnvironmentId.Validate()
 }
 
-func NewSchemaID(spaceID, envID, id string) *ID {
-	return &ID{Descriptor: &SchemaID{EnvironmentID: EnvironmentID{SpaceID: SpaceID{SpaceID: spaceID}, EnvironmentID: envID}, CollectionID: id}}
+func NewSchemaId(spaceID, envID, id string) *ObjectId {
+	return &ObjectId{Descriptor: &SchemaId{
+		EnvironmentId: EnvironmentId{
+			SpaceId: SpaceId{
+				SpaceID: spaceID,
+			},
+			EnvironmentID: envID,
+		},
+		CollectionID: id,
+	}}
 }
diff --git a/id/service.go b/id/service.go
index 23bb23aa500fbba082e1aae578eae6da5624a5b9..29c44ec635f763b9f35223f710f2d530f2dfb159 100644
--- a/id/service.go
+++ b/id/service.go
@@ -1,49 +1,58 @@
 package id
 
+import "fmt"
+
 const (
 	Service        = "service"
 	ServicesPrefix = "services"
 )
 
-type ServiceID struct {
+type ServiceId struct {
 	ServiceID string `json:"service_id,omitempty" bson:"service_id,omitempty"`
 }
 
-func (t *ServiceID) Type() string { return Service }
+var _ Descriptor = &ServiceId{}
 
-func (t *ServiceID) String() string {
-	return Join(ServicesPrefix, t.ServiceID)
+func (id *ServiceId) New() Descriptor {
+	return &ServiceId{}
 }
 
-func (t *ServiceID) ToMap() map[string]any {
-	return map[string]any{
-		"service_id": t.ServiceID,
-		"type":       Service,
-	}
-}
+func (id *ServiceId) Type() string { return Service }
 
-func (t *ServiceID) FromMap(m map[string]any) error {
-	t.ServiceID = m["service_id"].(string)
-	return nil
+func (id *ServiceId) String() string {
+	return Join(ServicesPrefix, id.ServiceID)
 }
 
-func (t *ServiceID) Validate() error {
-	if t.ServiceID == "" {
+func (id *ServiceId) FromParts(parts []string) error {
+	if len(parts) != 2 || parts[0] != ServicesPrefix {
 		return ErrInvalidID
 	}
+	id.ServiceID = parts[1]
 	return nil
 }
 
-func parseServiceID(parts []string) (*ServiceID, error) {
-	var id ServiceID
-	if len(parts) != 2 || parts[0] != ServicesPrefix {
-		return nil, ErrInvalidID
+func (id *ServiceId) Map() map[string]any {
+	return map[string]any{
+		"service_id": id.ServiceID,
+		"type":       id.Type(),
 	}
+}
 
-	id.ServiceID = parts[1]
-	return &id, nil
+func (id *ServiceId) FromMap(m map[string]any) error {
+	id.ServiceID, _ = m["service_id"].(string)
+	if id.ServiceID == "" {
+		return fmt.Errorf("%w: ServiceId required", ErrInvalidID)
+	}
+	return nil
+}
+
+func (id *ServiceId) Validate() error {
+	if id.ServiceID == "" {
+		return fmt.Errorf("%w: ServiceId required", ErrInvalidID)
+	}
+	return nil
 }
 
-func NewServiceID(id string) *ID {
-	return &ID{Descriptor: &ServiceID{ServiceID: id}}
+func NewServiceID(id string) *ObjectId {
+	return &ObjectId{Descriptor: &ServiceId{ServiceID: id}}
 }
diff --git a/id/space.go b/id/space.go
index 39096673456d74462ef203b10814396fe48b2bcf..9859a4788b05fb906f6a22f230a4b88366ab9311 100644
--- a/id/space.go
+++ b/id/space.go
@@ -1,48 +1,61 @@
 package id
 
+import (
+	"fmt"
+)
+
 const (
 	Space        = "space"
 	SpacesPrefix = "spaces"
 )
 
-type SpaceID struct {
+var _ Descriptor = &SpaceId{}
+
+type SpaceId struct {
 	SpaceID string `json:"space_id,omitempty" bson:"space_id,omitempty"`
 }
 
-func (t *SpaceID) Type() string { return Space }
+func (id *SpaceId) New() Descriptor {
+	return &SpaceId{}
+}
+
+func (id *SpaceId) Type() string { return Space }
 
-func (t *SpaceID) String() string {
-	return Join(SpacesPrefix, t.SpaceID)
+func (id *SpaceId) String() string {
+	return Join(SpacesPrefix, id.SpaceID)
 }
 
-func (t *SpaceID) ToMap() map[string]any {
-	return map[string]any{
-		"space_id": t.SpaceID,
-		"type":     Space,
+func (id *SpaceId) FromParts(parts []string) error {
+	if len(parts) != 2 || parts[0] != SpacesPrefix {
+		return ErrInvalidID
 	}
-}
 
-func (t *SpaceID) FromMap(m map[string]any) error {
-	t.SpaceID = m["space_id"].(string)
+	id.SpaceID = parts[1]
 	return nil
 }
 
-func (t *SpaceID) Validate() error {
-	if t.SpaceID == "" {
-		return ErrInvalidID
+func (id *SpaceId) Map() map[string]any {
+	return map[string]any{
+		"space_id": id.SpaceID,
+		"type":     id.Type(),
 	}
-	return nil
 }
 
-func parseSpaceID(parts []string) (*SpaceID, error) {
-	var id SpaceID
-	if len(parts) != 2 || parts[0] != SpacesPrefix {
-		return nil, ErrInvalidID
+func (id *SpaceId) FromMap(m map[string]any) error {
+	id.SpaceID, _ = m["space_id"].(string)
+	if id.SpaceID == "" {
+		return fmt.Errorf("%w: SpaceId required", ErrInvalidID)
 	}
+	return nil
+}
 
-	id.SpaceID = parts[1]
-	return &id, nil
+func (id *SpaceId) Validate() error {
+	if id.SpaceID == "" {
+		return fmt.Errorf("%w: SpaceId required", ErrInvalidID)
+	}
+	return nil
 }
-func NewSpaceID(id string) *ID {
-	return &ID{Descriptor: &SpaceID{SpaceID: id}}
+
+func NewSpaceId(id string) *ObjectId {
+	return &ObjectId{Descriptor: &SpaceId{SpaceID: id}}
 }
diff --git a/id/system.go b/id/system.go
index de2f3c2571896e448f8aad17be32df58b73a6ea4..f8f82faaf7ace71c255c32fb37657a1d7b28296a 100644
--- a/id/system.go
+++ b/id/system.go
@@ -2,21 +2,23 @@ package id
 
 const System = "system"
 
-type SystemID struct{}
+type SystemId struct{}
 
-func (t *SystemID) Type() string                   { return Space }
-func (t *SystemID) String() string                 { return string(Separator) + System }
-func (t *SystemID) ToMap() map[string]any          { return map[string]any{"type": System} }
-func (t *SystemID) FromMap(m map[string]any) error { return nil }
-func (t *SystemID) Validate() error                { return nil }
+var _ Descriptor = &SystemId{}
 
-func parseSystemID(parts []string) (*SystemID, error) {
-	var id SystemID
+func (id *SystemId) New() Descriptor {
+	return &SystemId{}
+}
+
+func (id *SystemId) Type() string   { return System }
+func (id *SystemId) String() string { return string(Separator) + System }
+func (id *SystemId) FromParts(parts []string) error {
 	if len(parts) != 1 || parts[0] != System {
-		return nil, ErrInvalidID
+		return ErrInvalidID
 	}
-	return &id, nil
-}
-func NewSystemID() *ID {
-	return &ID{Descriptor: &SystemID{}}
+	return nil
 }
+func (id *SystemId) Map() map[string]any            { return map[string]any{"type": System} }
+func (id *SystemId) FromMap(m map[string]any) error { return nil }
+func (id *SystemId) Validate() error                { return nil }
+func NewSystemId() *ObjectId                        { return &ObjectId{Descriptor: &SystemId{}} }
diff --git a/id/system/system.go b/id/system/system.go
new file mode 100644
index 0000000000000000000000000000000000000000..77084f5dcb8dc702b2b14337e97cdc97fd80930b
--- /dev/null
+++ b/id/system/system.go
@@ -0,0 +1,97 @@
+package system
+
+import (
+	"context"
+	"reflect"
+
+	"git.perx.ru/perxis/perxis-go/id"
+	"git.perx.ru/perxis/perxis-go/pkg/auth"
+	"git.perx.ru/perxis/perxis-go/pkg/clients"
+	"git.perx.ru/perxis/perxis-go/pkg/collections"
+	"git.perx.ru/perxis/perxis-go/pkg/environments"
+	"git.perx.ru/perxis/perxis-go/pkg/items"
+	"git.perx.ru/perxis/perxis-go/pkg/organizations"
+	"git.perx.ru/perxis/perxis-go/pkg/roles"
+	"git.perx.ru/perxis/perxis-go/pkg/spaces"
+	"git.perx.ru/perxis/perxis-go/pkg/users"
+)
+
+func Handler(obj any) *id.ObjectId {
+	switch val := obj.(type) {
+	case *organizations.Organization:
+		var i id.OrganizationId
+		i.OrganizationID = val.ID
+		return id.MustObjectId(&i)
+	case *spaces.Space:
+		var i id.SpaceId
+		i.SpaceID = val.ID
+		return id.MustObjectId(&i)
+	case *environments.Environment:
+		var i id.EnvironmentId
+		i.SpaceID = val.SpaceID
+		i.EnvironmentID = val.ID
+		return id.MustObjectId(&i)
+	case *collections.Collection:
+		var i id.CollectionId
+		i.SpaceID = val.SpaceID
+		i.EnvironmentID = val.EnvID
+		i.CollectionID = val.ID
+		return id.MustObjectId(&i)
+	case *items.Item:
+		var i id.ItemId
+		i.SpaceID = val.SpaceID
+		i.EnvironmentID = val.EnvID
+		i.CollectionID = val.CollectionID
+		i.ItemID = val.ID
+		return id.MustObjectId(&i)
+	case *users.User:
+		var i id.UserId
+		i.UserID = val.ID
+		return id.MustObjectId(&i)
+	case *clients.Client:
+		var i id.ClientId
+		i.SpaceID = val.SpaceID
+		i.ClientID = val.ID
+		return id.MustObjectId(&i)
+	case *roles.Role:
+		var i id.RoleId
+		i.SpaceID = val.SpaceID
+		i.RoleID = val.ID
+		return id.MustObjectId(&i)
+	case *auth.UserPrincipal:
+		var i id.UserId
+		i.UserID = val.GetID(context.TODO())
+		return id.MustObjectId(&i)
+	case *auth.ClientPrincipal:
+		var i id.ClientId
+		i.ClientID = val.GetID(context.TODO())
+		return id.MustObjectId(&i)
+	case *auth.SystemPrincipal:
+		return id.MustObjectId(&id.SystemId{})
+	case *auth.Anonymous:
+		var i id.UserId
+		i.UserID = val.GetID(context.TODO())
+		return id.MustObjectId(&i)
+	}
+	return nil
+}
+
+// Register registers object handler for system types into the provided Registry.
+func Register(r *id.Registry) {
+	r.RegisterObjectHandler(reflect.TypeOf(&organizations.Organization{}), Handler)
+	r.RegisterObjectHandler(reflect.TypeOf(&spaces.Space{}), Handler)
+	r.RegisterObjectHandler(reflect.TypeOf(&environments.Environment{}), Handler)
+	r.RegisterObjectHandler(reflect.TypeOf(&collections.Collection{}), Handler)
+	r.RegisterObjectHandler(reflect.TypeOf(&items.Item{}), Handler)
+	r.RegisterObjectHandler(reflect.TypeOf(&clients.Client{}), Handler)
+	r.RegisterObjectHandler(reflect.TypeOf(&roles.Role{}), Handler)
+	r.RegisterObjectHandler(reflect.TypeOf(&users.User{}), Handler)
+	r.RegisterObjectHandler(reflect.TypeOf(&auth.UserPrincipal{}), Handler)
+	r.RegisterObjectHandler(reflect.TypeOf(&auth.ClientPrincipal{}), Handler)
+	r.RegisterObjectHandler(reflect.TypeOf(&auth.SystemPrincipal{}), Handler)
+	r.RegisterObjectHandler(reflect.TypeOf(&auth.Anonymous{}), Handler)
+}
+
+func init() {
+	Register(id.GetRegistry())
+}
diff --git a/id/test/object_id_test.go b/id/test/object_id_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..f97e50e49068173b5aa1968de33abc4d8a44817b
--- /dev/null
+++ b/id/test/object_id_test.go
@@ -0,0 +1,801 @@
+package test
+
+import (
+	"testing"
+
+	"git.perx.ru/perxis/perxis-go/id"
+	_ "git.perx.ru/perxis/perxis-go/id/system"
+	"git.perx.ru/perxis/perxis-go/pkg/clients"
+	"git.perx.ru/perxis/perxis-go/pkg/collections"
+	"git.perx.ru/perxis/perxis-go/pkg/environments"
+	"git.perx.ru/perxis/perxis-go/pkg/items"
+	"git.perx.ru/perxis/perxis-go/pkg/organizations"
+	"git.perx.ru/perxis/perxis-go/pkg/roles"
+	"git.perx.ru/perxis/perxis-go/pkg/spaces"
+	"git.perx.ru/perxis/perxis-go/pkg/users"
+	"github.com/stretchr/testify/require"
+)
+
+func Test_OrganizationId(t *testing.T) {
+
+	tests := []struct {
+		name   string
+		in     any
+		out    string
+		result *id.ObjectId
+		err    error
+	}{
+		{
+			name:   "valid string",
+			in:     "/orgs/<org_id>",
+			result: &id.ObjectId{Descriptor: &id.OrganizationId{OrganizationID: "<org_id>"}},
+		},
+		{
+			name:   "valid object",
+			in:     &organizations.Organization{ID: "<org_id>"},
+			out:    "/orgs/<org_id>",
+			result: &id.ObjectId{Descriptor: &id.OrganizationId{OrganizationID: "<org_id>"}},
+		},
+		{
+			name:   "valid map",
+			in:     map[string]any{"type": "organization", "organization_id": "<org_id>"},
+			out:    "/orgs/<org_id>",
+			result: &id.ObjectId{Descriptor: &id.OrganizationId{OrganizationID: "<org_id>"}},
+		},
+		{
+			name:   "invalid map",
+			in:     map[string]any{"type": "organization"},
+			out:    "/orgs/<org_id>",
+			result: &id.ObjectId{Descriptor: &id.OrganizationId{OrganizationID: "<org_id>"}},
+			err:    id.ErrInvalidID,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			i, err := id.NewObjectId(tt.in)
+
+			if tt.err != nil {
+				require.ErrorIs(t, err, tt.err)
+				return
+			}
+
+			require.NoError(t, err)
+			require.Equal(t, tt.result, i)
+			if tt.out == "" {
+				require.Equal(t, tt.in, i.String())
+			} else {
+				require.Equal(t, tt.out, i.String())
+			}
+		})
+	}
+}
+
+func Test_ServiceId(t *testing.T) {
+
+	tests := []struct {
+		name   string
+		in     any
+		out    string
+		result *id.ObjectId
+		err    error
+	}{
+		{
+			name:   "valid string",
+			in:     "/services/<service_id>",
+			result: &id.ObjectId{Descriptor: &id.ServiceId{ServiceID: "<service_id>"}},
+		},
+		{
+			name:   "valid map",
+			in:     map[string]any{"type": "service", "service_id": "<service_id>"},
+			out:    "/services/<service_id>",
+			result: &id.ObjectId{Descriptor: &id.ServiceId{ServiceID: "<service_id>"}},
+		},
+		{
+			name:   "invalid map",
+			in:     map[string]any{"type": "service"},
+			out:    "/services/<service_id>",
+			result: &id.ObjectId{Descriptor: &id.ServiceId{ServiceID: "<service_id>"}},
+			err:    id.ErrInvalidID,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			i, err := id.NewObjectId(tt.in)
+
+			if tt.err != nil {
+				require.ErrorIs(t, err, tt.err)
+				return
+			}
+
+			require.NoError(t, err)
+			require.Equal(t, tt.result, i)
+			if tt.out == "" {
+				require.Equal(t, tt.in, i.String())
+			} else {
+				require.Equal(t, tt.out, i.String())
+			}
+		})
+	}
+}
+
+func Test_UserId(t *testing.T) {
+
+	tests := []struct {
+		name   string
+		in     any
+		out    string
+		result *id.ObjectId
+		err    error
+	}{
+		{
+			name:   "valid string",
+			in:     "/users/<user_id>",
+			result: &id.ObjectId{Descriptor: &id.UserId{UserID: "<user_id>"}},
+		},
+		{
+			name:   "valid object",
+			in:     &users.User{ID: "<user_id>"},
+			out:    "/users/<user_id>",
+			result: &id.ObjectId{Descriptor: &id.UserId{UserID: "<user_id>"}},
+		},
+		{
+			name:   "valid map",
+			in:     map[string]any{"type": "user", "user_id": "<user_id>"},
+			out:    "/users/<user_id>",
+			result: &id.ObjectId{Descriptor: &id.UserId{UserID: "<user_id>"}},
+		},
+		{
+			name:   "invalid map",
+			in:     map[string]any{"type": "user"},
+			out:    "/users/<user_id>",
+			result: &id.ObjectId{Descriptor: &id.UserId{UserID: "<user_id>"}},
+			err:    id.ErrInvalidID,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			i, err := id.NewObjectId(tt.in)
+
+			if tt.err != nil {
+				require.ErrorIs(t, err, tt.err)
+				return
+			}
+
+			require.NoError(t, err)
+			require.Equal(t, tt.result, i)
+			if tt.out == "" {
+				require.Equal(t, tt.in, i.String())
+			} else {
+				require.Equal(t, tt.out, i.String())
+			}
+		})
+	}
+}
+
+func Test_SpaceId(t *testing.T) {
+
+	tests := []struct {
+		name   string
+		in     any
+		out    string
+		result *id.ObjectId
+		err    error
+	}{
+		{
+			name:   "valid string",
+			in:     "/spaces/<space_id>",
+			result: &id.ObjectId{Descriptor: &id.SpaceId{SpaceID: "<space_id>"}},
+		},
+		{
+			name:   "valid object",
+			in:     &spaces.Space{ID: "<space_id>"},
+			out:    "/spaces/<space_id>",
+			result: &id.ObjectId{Descriptor: &id.SpaceId{SpaceID: "<space_id>"}},
+		},
+		{
+			name:   "valid map",
+			in:     map[string]any{"type": "space", "space_id": "<space_id>"},
+			out:    "/spaces/<space_id>",
+			result: &id.ObjectId{Descriptor: &id.SpaceId{SpaceID: "<space_id>"}},
+		},
+		{
+			name:   "invalid map",
+			in:     map[string]any{"type": "space"},
+			out:    "/spaces/<space_id>",
+			result: &id.ObjectId{Descriptor: &id.SpaceId{SpaceID: "<space_id>"}},
+			err:    id.ErrInvalidID,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			i, err := id.NewObjectId(tt.in)
+
+			if tt.err != nil {
+				require.ErrorIs(t, err, tt.err)
+				return
+			}
+
+			require.NoError(t, err)
+			require.Equal(t, tt.result, i)
+			if tt.out == "" {
+				require.Equal(t, tt.in, i.String())
+			} else {
+				require.Equal(t, tt.out, i.String())
+			}
+		})
+	}
+}
+
+func Test_EnvironmentId(t *testing.T) {
+
+	tests := []struct {
+		name   string
+		in     any
+		out    string
+		result *id.ObjectId
+		err    error
+	}{
+		{
+			name: "valid string",
+			in:   "/spaces/<space_id>/envs/<env_id>",
+			result: &id.ObjectId{Descriptor: &id.EnvironmentId{
+				SpaceId:       id.SpaceId{SpaceID: "<space_id>"},
+				EnvironmentID: "<env_id>",
+			}},
+		},
+		{
+			name: "invalid string",
+			in:   "/envs/<env_id>",
+			result: &id.ObjectId{Descriptor: &id.EnvironmentId{
+				SpaceId:       id.SpaceId{SpaceID: "<space_id>"},
+				EnvironmentID: "<env_id>",
+			}},
+			err: id.ErrInvalidID,
+		},
+		{
+			name: "valid object",
+			in:   &environments.Environment{SpaceID: "<space_id>", ID: "<env_id>"},
+			out:  "/spaces/<space_id>/envs/<env_id>",
+			result: &id.ObjectId{Descriptor: &id.EnvironmentId{
+				SpaceId:       id.SpaceId{SpaceID: "<space_id>"},
+				EnvironmentID: "<env_id>",
+			}},
+		},
+		{
+			name: "valid map",
+			in:   map[string]any{"type": "environment", "space_id": "<space_id>", "env_id": "<env_id>"},
+			out:  "/spaces/<space_id>/envs/<env_id>",
+			result: &id.ObjectId{Descriptor: &id.EnvironmentId{
+				SpaceId:       id.SpaceId{SpaceID: "<space_id>"},
+				EnvironmentID: "<env_id>",
+			}},
+		},
+		{
+			name: "invalid map 1",
+			in:   map[string]any{"type": "environment", "space_id": "<space_id>"},
+			out:  "/spaces/<space_id>/envs/<env_id>",
+			result: &id.ObjectId{Descriptor: &id.EnvironmentId{
+				SpaceId:       id.SpaceId{SpaceID: "<space_id>"},
+				EnvironmentID: "<env_id>",
+			}},
+			err: id.ErrInvalidID,
+		},
+		{
+			name: "invalid map 2",
+			in:   map[string]any{"type": "environment", "env_id": "<env_id>"},
+			out:  "/spaces/<space_id>/envs/<env_id>",
+			result: &id.ObjectId{Descriptor: &id.EnvironmentId{
+				SpaceId:       id.SpaceId{SpaceID: "<space_id>"},
+				EnvironmentID: "<env_id>",
+			}},
+			err: id.ErrInvalidID,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			i, err := id.NewObjectId(tt.in)
+
+			if tt.err != nil {
+				require.ErrorIs(t, err, tt.err)
+				return
+			}
+
+			require.NoError(t, err)
+			require.Equal(t, tt.result, i)
+			if tt.out == "" {
+				require.Equal(t, tt.in, i.String())
+			} else {
+				require.Equal(t, tt.out, i.String())
+			}
+		})
+	}
+}
+
+func Test_ClientId(t *testing.T) {
+
+	tests := []struct {
+		name   string
+		in     any
+		out    string
+		result *id.ObjectId
+		err    error
+	}{
+		{
+			name: "valid string",
+			in:   "/spaces/<space_id>/clients/<client_id>",
+			result: &id.ObjectId{Descriptor: &id.ClientId{
+				SpaceId:  id.SpaceId{SpaceID: "<space_id>"},
+				ClientID: "<client_id>",
+			}},
+		},
+		{
+			name: "invalid string",
+			in:   "/clients/<client_id>",
+			result: &id.ObjectId{Descriptor: &id.ClientId{
+				SpaceId:  id.SpaceId{SpaceID: "<space_id>"},
+				ClientID: "<client_id>",
+			}},
+			err: id.ErrInvalidID,
+		},
+		{
+			name: "valid object",
+			in:   &clients.Client{SpaceID: "<space_id>", ID: "<client_id>"},
+			out:  "/spaces/<space_id>/clients/<client_id>",
+			result: &id.ObjectId{Descriptor: &id.ClientId{
+				SpaceId:  id.SpaceId{SpaceID: "<space_id>"},
+				ClientID: "<client_id>",
+			}},
+		},
+		{
+			name: "valid map",
+			in:   map[string]any{"type": "client", "space_id": "<space_id>", "client_id": "<client_id>"},
+			out:  "/spaces/<space_id>/clients/<client_id>",
+			result: &id.ObjectId{Descriptor: &id.ClientId{
+				SpaceId:  id.SpaceId{SpaceID: "<space_id>"},
+				ClientID: "<client_id>",
+			}},
+		},
+		{
+			name: "invalid map 1",
+			in:   map[string]any{"type": "client", "space_id": "<space_id>"},
+			out:  "/spaces/<space_id>/clients/<client_id>",
+			result: &id.ObjectId{Descriptor: &id.ClientId{
+				SpaceId:  id.SpaceId{SpaceID: "<space_id>"},
+				ClientID: "<client_id>",
+			}},
+			err: id.ErrInvalidID,
+		},
+		{
+			name: "invalid map 2",
+			in:   map[string]any{"type": "client", "client_id": "<client_id>"},
+			out:  "/spaces/<space_id>/clients/<client_id>",
+			result: &id.ObjectId{Descriptor: &id.ClientId{
+				SpaceId:  id.SpaceId{SpaceID: "<space_id>"},
+				ClientID: "<client_id>",
+			}},
+			err: id.ErrInvalidID,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			i, err := id.NewObjectId(tt.in)
+
+			if tt.err != nil {
+				require.ErrorIs(t, err, tt.err)
+				return
+			}
+
+			require.NoError(t, err)
+			require.Equal(t, tt.result, i)
+			if tt.out == "" {
+				require.Equal(t, tt.in, i.String())
+			} else {
+				require.Equal(t, tt.out, i.String())
+			}
+		})
+	}
+}
+
+func Test_RoleId(t *testing.T) {
+
+	tests := []struct {
+		name   string
+		in     any
+		out    string
+		result *id.ObjectId
+		err    error
+	}{
+		{
+			name: "valid string",
+			in:   "/spaces/<space_id>/roles/<role_id>",
+			result: &id.ObjectId{Descriptor: &id.RoleId{
+				SpaceId: id.SpaceId{SpaceID: "<space_id>"},
+				RoleID:  "<role_id>",
+			}},
+		},
+		{
+			name: "invalid string",
+			in:   "/roles/<role_id>",
+			result: &id.ObjectId{Descriptor: &id.RoleId{
+				SpaceId: id.SpaceId{SpaceID: "<space_id>"},
+				RoleID:  "<role_id>",
+			}},
+			err: id.ErrInvalidID,
+		},
+		{
+			name: "valid object",
+			in:   &roles.Role{SpaceID: "<space_id>", ID: "<role_id>"},
+			out:  "/spaces/<space_id>/roles/<role_id>",
+			result: &id.ObjectId{Descriptor: &id.RoleId{
+				SpaceId: id.SpaceId{SpaceID: "<space_id>"},
+				RoleID:  "<role_id>",
+			}},
+		},
+		{
+			name: "valid map",
+			in:   map[string]any{"type": "role", "space_id": "<space_id>", "role_id": "<role_id>"},
+			out:  "/spaces/<space_id>/roles/<role_id>",
+			result: &id.ObjectId{Descriptor: &id.RoleId{
+				SpaceId: id.SpaceId{SpaceID: "<space_id>"},
+				RoleID:  "<role_id>",
+			}},
+		},
+		{
+			name: "invalid map 1",
+			in:   map[string]any{"type": "client", "space_id": "<space_id>"},
+			out:  "/spaces/<space_id>/roles/<role_id>",
+			result: &id.ObjectId{Descriptor: &id.RoleId{
+				SpaceId: id.SpaceId{SpaceID: "<space_id>"},
+				RoleID:  "<role_id>",
+			}},
+			err: id.ErrInvalidID,
+		},
+		{
+			name: "invalid map 2",
+			in:   map[string]any{"type": "role", "role_id": "<role_id>"},
+			out:  "/spaces/<space_id>/roles/<role_id>",
+			result: &id.ObjectId{Descriptor: &id.RoleId{
+				SpaceId: id.SpaceId{SpaceID: "<space_id>"},
+				RoleID:  "<role_id>",
+			}},
+			err: id.ErrInvalidID,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			i, err := id.NewObjectId(tt.in)
+
+			if tt.err != nil {
+				require.ErrorIs(t, err, tt.err)
+				return
+			}
+
+			require.NoError(t, err)
+			require.Equal(t, tt.result, i)
+			if tt.out == "" {
+				require.Equal(t, tt.in, i.String())
+			} else {
+				require.Equal(t, tt.out, i.String())
+			}
+		})
+	}
+}
+
+func Test_CollectionId(t *testing.T) {
+
+	tests := []struct {
+		name   string
+		in     any
+		out    string
+		result *id.ObjectId
+		err    error
+	}{
+		{
+			name: "valid string",
+			in:   "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>",
+			result: &id.ObjectId{Descriptor: &id.CollectionId{
+				EnvironmentId: id.EnvironmentId{SpaceId: id.SpaceId{SpaceID: "<space_id>"}, EnvironmentID: "<env_id>"},
+				CollectionID:  "<collection_id>",
+			}},
+		},
+		{
+			name: "valid object",
+			in:   &collections.Collection{SpaceID: "<space_id>", EnvID: "<env_id>", ID: "<collection_id>"},
+			out:  "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>",
+			result: &id.ObjectId{Descriptor: &id.CollectionId{
+				EnvironmentId: id.EnvironmentId{SpaceId: id.SpaceId{SpaceID: "<space_id>"}, EnvironmentID: "<env_id>"},
+				CollectionID:  "<collection_id>",
+			}},
+		},
+		{
+			name: "valid map",
+			in:   map[string]any{"type": "collection", "space_id": "<space_id>", "env_id": "<env_id>", "col_id": "<collection_id>"},
+			out:  "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>",
+			result: &id.ObjectId{Descriptor: &id.CollectionId{
+				EnvironmentId: id.EnvironmentId{SpaceId: id.SpaceId{SpaceID: "<space_id>"}, EnvironmentID: "<env_id>"},
+				CollectionID:  "<collection_id>",
+			}},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			i, err := id.NewObjectId(tt.in)
+
+			if tt.err != nil {
+				require.ErrorIs(t, err, tt.err)
+				return
+			}
+
+			require.NoError(t, err)
+			require.Equal(t, tt.result, i)
+			if tt.out == "" {
+				require.Equal(t, tt.in, i.String())
+			} else {
+				require.Equal(t, tt.out, i.String())
+			}
+		})
+	}
+}
+
+func Test_SchemaId(t *testing.T) {
+
+	tests := []struct {
+		name   string
+		in     any
+		out    string
+		result *id.ObjectId
+		err    error
+	}{
+		{
+			name: "valid string",
+			in:   "/spaces/<space_id>/envs/<env_id>/schema/<collection_id>",
+			result: &id.ObjectId{Descriptor: &id.SchemaId{
+				EnvironmentId: id.EnvironmentId{SpaceId: id.SpaceId{SpaceID: "<space_id>"}, EnvironmentID: "<env_id>"},
+				CollectionID:  "<collection_id>",
+			}},
+		},
+		{
+			name: "valid map",
+			in:   map[string]any{"type": "schema", "space_id": "<space_id>", "env_id": "<env_id>", "col_id": "<collection_id>"},
+			out:  "/spaces/<space_id>/envs/<env_id>/schema/<collection_id>",
+			result: &id.ObjectId{Descriptor: &id.SchemaId{
+				EnvironmentId: id.EnvironmentId{SpaceId: id.SpaceId{SpaceID: "<space_id>"}, EnvironmentID: "<env_id>"},
+				CollectionID:  "<collection_id>",
+			}},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			i, err := id.NewObjectId(tt.in)
+
+			if tt.err != nil {
+				require.ErrorIs(t, err, tt.err)
+				return
+			}
+
+			require.NoError(t, err)
+			require.Equal(t, tt.result, i)
+			if tt.out == "" {
+				require.Equal(t, tt.in, i.String())
+			} else {
+				require.Equal(t, tt.out, i.String())
+			}
+		})
+	}
+}
+
+func Test_ItemId(t *testing.T) {
+
+	tests := []struct {
+		name   string
+		in     any
+		out    string
+		result *id.ObjectId
+		err    error
+	}{
+		{
+			name: "valid string",
+			in:   "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>",
+			result: &id.ObjectId{Descriptor: &id.ItemId{
+				CollectionId: id.CollectionId{
+					EnvironmentId: id.EnvironmentId{
+						SpaceId:       id.SpaceId{SpaceID: "<space_id>"},
+						EnvironmentID: "<env_id>",
+					},
+					CollectionID: "<collection_id>",
+				},
+				ItemID: "<item_id>",
+			}},
+		},
+		{
+			name: "valid object",
+			in:   &items.Item{SpaceID: "<space_id>", EnvID: "<env_id>", CollectionID: "<collection_id>", ID: "<item_id>"},
+			out:  "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>",
+			result: &id.ObjectId{Descriptor: &id.ItemId{
+				CollectionId: id.CollectionId{
+					EnvironmentId: id.EnvironmentId{
+						SpaceId:       id.SpaceId{SpaceID: "<space_id>"},
+						EnvironmentID: "<env_id>",
+					},
+					CollectionID: "<collection_id>",
+				},
+				ItemID: "<item_id>",
+			}},
+		},
+		{
+			name: "valid map",
+			in:   map[string]any{"type": "item", "space_id": "<space_id>", "env_id": "<env_id>", "col_id": "<collection_id>", "item_id": "<item_id>"},
+			out:  "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>",
+			result: &id.ObjectId{Descriptor: &id.ItemId{
+				CollectionId: id.CollectionId{
+					EnvironmentId: id.EnvironmentId{
+						SpaceId:       id.SpaceId{SpaceID: "<space_id>"},
+						EnvironmentID: "<env_id>",
+					},
+					CollectionID: "<collection_id>",
+				},
+				ItemID: "<item_id>",
+			}},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			i, err := id.NewObjectId(tt.in)
+
+			if tt.err != nil {
+				require.ErrorIs(t, err, tt.err)
+				return
+			}
+
+			require.NoError(t, err)
+			require.Equal(t, tt.result, i)
+			if tt.out == "" {
+				require.Equal(t, tt.in, i.String())
+			} else {
+				require.Equal(t, tt.out, i.String())
+			}
+		})
+	}
+}
+
+func Test_FieldId(t *testing.T) {
+
+	tests := []struct {
+		name   string
+		in     any
+		out    string
+		result *id.ObjectId
+		err    error
+	}{
+		{
+			name: "valid string",
+			in:   "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>/fields/<field>",
+			result: &id.ObjectId{Descriptor: &id.FieldId{
+				ItemId: id.ItemId{
+					CollectionId: id.CollectionId{
+						EnvironmentId: id.EnvironmentId{
+							SpaceId:       id.SpaceId{SpaceID: "<space_id>"},
+							EnvironmentID: "<env_id>",
+						},
+						CollectionID: "<collection_id>",
+					},
+					ItemID: "<item_id>",
+				},
+				Field: "<field>",
+			}},
+		},
+		{
+			name: "valid map",
+			in:   map[string]any{"type": "field", "space_id": "<space_id>", "env_id": "<env_id>", "col_id": "<collection_id>", "item_id": "<item_id>", "field": "<field>"},
+			out:  "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>/fields/<field>",
+			result: &id.ObjectId{Descriptor: &id.FieldId{
+				ItemId: id.ItemId{
+					CollectionId: id.CollectionId{
+						EnvironmentId: id.EnvironmentId{
+							SpaceId:       id.SpaceId{SpaceID: "<space_id>"},
+							EnvironmentID: "<env_id>",
+						},
+						CollectionID: "<collection_id>",
+					},
+					ItemID: "<item_id>",
+				},
+				Field: "<field>",
+			}},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			i, err := id.NewObjectId(tt.in)
+
+			if tt.err != nil {
+				require.ErrorIs(t, err, tt.err)
+				return
+			}
+
+			require.NoError(t, err)
+			require.Equal(t, tt.result, i)
+			if tt.out == "" {
+				require.Equal(t, tt.in, i.String())
+			} else {
+				require.Equal(t, tt.out, i.String())
+			}
+		})
+	}
+}
+
+func Test_RevisionId(t *testing.T) {
+
+	tests := []struct {
+		name   string
+		in     any
+		out    string
+		result *id.ObjectId
+		err    error
+	}{
+		{
+			name: "valid string",
+			in:   "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>/revs/<rev_id>",
+			result: &id.ObjectId{Descriptor: &id.RevisionId{
+				ItemId: id.ItemId{
+					CollectionId: id.CollectionId{
+						EnvironmentId: id.EnvironmentId{
+							SpaceId:       id.SpaceId{SpaceID: "<space_id>"},
+							EnvironmentID: "<env_id>",
+						},
+						CollectionID: "<collection_id>",
+					},
+					ItemID: "<item_id>",
+				},
+				RevisionID: "<rev_id>",
+			}},
+		},
+		{
+			name: "valid map",
+			in:   map[string]any{"type": "revision", "space_id": "<space_id>", "env_id": "<env_id>", "col_id": "<collection_id>", "item_id": "<item_id>", "rev_id": "<rev_id>"},
+			out:  "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>/revs/<rev_id>",
+			result: &id.ObjectId{Descriptor: &id.RevisionId{
+				ItemId: id.ItemId{
+					CollectionId: id.CollectionId{
+						EnvironmentId: id.EnvironmentId{
+							SpaceId:       id.SpaceId{SpaceID: "<space_id>"},
+							EnvironmentID: "<env_id>",
+						},
+						CollectionID: "<collection_id>",
+					},
+					ItemID: "<item_id>",
+				},
+				RevisionID: "<rev_id>",
+			}},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			i, err := id.NewObjectId(tt.in)
+
+			if tt.err != nil {
+				require.ErrorIs(t, err, tt.err)
+				return
+			}
+
+			require.NoError(t, err)
+			require.Equal(t, tt.result, i)
+			if tt.out == "" {
+				require.Equal(t, tt.in, i.String())
+			} else {
+				require.Equal(t, tt.out, i.String())
+			}
+		})
+	}
+}
diff --git a/id/user.go b/id/user.go
index c76f6c9fa0e8ce440a1ef15e36bbb82a73301be5..85459c7e29e94bcaf3edce684ec2d28eb8669a50 100644
--- a/id/user.go
+++ b/id/user.go
@@ -1,49 +1,60 @@
 package id
 
+import (
+	"fmt"
+)
+
 const (
 	User        = "user"
 	UsersPrefix = "users"
 )
 
-type UserID struct {
+type UserId struct {
 	UserID string `json:"user_id,omitempty" bson:"user_id,omitempty"`
 }
 
-func (t *UserID) Type() string { return User }
+var _ Descriptor = &UserId{}
 
-func (t *UserID) String() string {
-	return Join(UsersPrefix, t.UserID)
+func (id *UserId) New() Descriptor {
+	return &UserId{}
 }
 
-func (t *UserID) ToMap() map[string]any {
-	return map[string]any{
-		"user_id": t.UserID,
-		"type":    User,
-	}
-}
+func (id *UserId) Type() string { return User }
 
-func (t *UserID) FromMap(m map[string]any) error {
-	t.UserID = m["user_id"].(string)
-	return nil
+func (id *UserId) String() string {
+	return Join(UsersPrefix, id.UserID)
 }
 
-func (t *UserID) Validate() error {
-	if t.UserID == "" {
+func (id *UserId) FromParts(parts []string) error {
+	if len(parts) != 2 || parts[0] != UsersPrefix {
 		return ErrInvalidID
 	}
+	id.UserID = parts[1]
 	return nil
 }
 
-func parseUserID(parts []string) (*UserID, error) {
-	var id UserID
-	if len(parts) != 2 || parts[0] != UsersPrefix {
-		return nil, ErrInvalidID
+func (id *UserId) Map() map[string]any {
+	return map[string]any{
+		"user_id": id.UserID,
+		"type":    id.Type(),
 	}
+}
 
-	id.UserID = parts[1]
-	return &id, nil
+func (id *UserId) FromMap(m map[string]any) error {
+	id.UserID, _ = m["user_id"].(string)
+	if id.UserID == "" {
+		return fmt.Errorf("%w: UserId required", ErrInvalidID)
+	}
+	return nil
+}
+
+func (id *UserId) Validate() error {
+	if id.UserID == "" {
+		return fmt.Errorf("%w: UserId required", ErrInvalidID)
+	}
+	return nil
 }
 
-func NewUserID(id string) *ID {
-	return &ID{Descriptor: &UserID{UserID: id}}
+func NewUserId(id string) *ObjectId {
+	return &ObjectId{Descriptor: &UserId{UserID: id}}
 }
diff --git a/images/middleware/access_logging_middleware.go b/images/middleware/access_logging_middleware.go
new file mode 100644
index 0000000000000000000000000000000000000000..0a5f5ec2374db349e5eea81ec147d8258649412f
--- /dev/null
+++ b/images/middleware/access_logging_middleware.go
@@ -0,0 +1,53 @@
+// 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/images -i Images -t ../../assets/templates/middleware/access_log.tmpl -o access_logging_middleware.go -l ""
+
+import (
+	"context"
+	"time"
+
+	"git.perx.ru/perxis/perxis-go/images"
+	"git.perx.ru/perxis/perxis-go/pkg/auth"
+	"git.perx.ru/perxis/perxis-go/pkg/files"
+	"go.uber.org/zap"
+)
+
+// accessLoggingMiddleware implements images.Images that is instrumented with logging
+type accessLoggingMiddleware struct {
+	logger *zap.Logger
+	next   images.Images
+}
+
+// AccessLoggingMiddleware instruments an implementation of the images.Images with simple logging
+func AccessLoggingMiddleware(logger *zap.Logger) Middleware {
+	return func(next images.Images) images.Images {
+		return &accessLoggingMiddleware{
+			next:   next,
+			logger: logger,
+		}
+	}
+}
+
+func (m *accessLoggingMiddleware) Get(ctx context.Context, source *files.File, opts *images.GetOptions) (result *files.File, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Get.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("source", source),
+		zap.Reflect("opts", opts),
+	)
+
+	result, err = m.next.Get(ctx, source, opts)
+
+	m.logger.Debug("Get.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("result", result),
+		zap.Error(err),
+	)
+
+	return result, err
+}
diff --git a/images/middleware/logging_middleware.go b/images/middleware/logging_middleware.go
deleted file mode 100644
index a69a56ee6a236aab96b1d22340691a63e36fdc01..0000000000000000000000000000000000000000
--- a/images/middleware/logging_middleware.go
+++ /dev/null
@@ -1,73 +0,0 @@
-// 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/images -i Images -t ../../assets/templates/middleware/access_log -o logging_middleware.go -l ""
-
-import (
-	"context"
-	"fmt"
-	"time"
-
-	"git.perx.ru/perxis/perxis-go/images"
-	"git.perx.ru/perxis/perxis-go/pkg/auth"
-	"git.perx.ru/perxis/perxis-go/pkg/files"
-	"go.uber.org/zap"
-	"go.uber.org/zap/zapcore"
-)
-
-// loggingMiddleware implements images.Images that is instrumented with logging
-type loggingMiddleware struct {
-	logger *zap.Logger
-	next   images.Images
-}
-
-// LoggingMiddleware instruments an implementation of the images.Images with simple logging
-func LoggingMiddleware(logger *zap.Logger) Middleware {
-	return func(next images.Images) images.Images {
-		return &loggingMiddleware{
-			next:   next,
-			logger: logger,
-		}
-	}
-}
-
-func (m *loggingMiddleware) Get(ctx context.Context, source *files.File, opts *images.GetOptions) (result *files.File, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":    ctx,
-		"source": source,
-		"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("Get.Request", fields...)
-
-	result, err = m.next.Get(ctx, source, opts)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	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("Get.Response", fields...)
-
-	return result, err
-}
diff --git a/images/middleware/middleware.go b/images/middleware/middleware.go
index e29bdeb8c6e7a5e2d6b298d01069f4566481e80a..bf07dd54eb80280edf4ea7edc2ac43d66085ae2d 100644
--- a/images/middleware/middleware.go
+++ b/images/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/images -i Images -t ../../assets/templates/middleware/middleware -o middleware.go -l ""
+//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/images -i Images -t ../../assets/templates/middleware/middleware.tmpl -o middleware.go -l ""
 
 import (
 	"git.perx.ru/perxis/perxis-go/images"
@@ -17,12 +17,12 @@ func WithLog(s images.Images, logger *zap.Logger, log_access bool) images.Images
 	if logger == nil {
 		logger = zap.NewNop()
 	}
-
 	logger = logger.Named("Images")
-	s = ErrorLoggingMiddleware(logger)(s)
 	if log_access {
-		s = LoggingMiddleware(logger)(s)
+		s = AccessLoggingMiddleware(logger)(s)
 	}
+	s = ErrorLoggingMiddleware(logger)(s)
+
 	s = RecoveringMiddleware(logger)(s)
 	return s
 }
diff --git a/images/middleware/recovering_middleware.go b/images/middleware/recovering_middleware.go
index 9ad61603a2584b58f4635c9ee8296f67f047b7a5..9c0a447e802a95d3002f6e45060a2a359024ff5a 100644
--- a/images/middleware/recovering_middleware.go
+++ b/images/middleware/recovering_middleware.go
@@ -1,5 +1,5 @@
 // Code generated by gowrap. DO NOT EDIT.
-// template: ../../../assets/templates/middleware/recovery
+// template: ../../assets/templates/middleware/recovery
 // gowrap: http://github.com/hexdigest/gowrap
 
 package middleware
diff --git a/logs/client.go b/logs/client.go
new file mode 100644
index 0000000000000000000000000000000000000000..fc6a5b901343514c27a9f39e2c5c50ab234f1be7
--- /dev/null
+++ b/logs/client.go
@@ -0,0 +1,71 @@
+package logs
+
+import (
+	"context"
+
+	errorsgrpc "git.perx.ru/perxis/perxis-go/pkg/errors/grpc"
+	"git.perx.ru/perxis/perxis-go/pkg/options"
+	pb "git.perx.ru/perxis/perxis-go/proto/logs"
+	"google.golang.org/grpc"
+)
+
+type Client struct {
+	client pb.LogsServiceClient
+}
+
+var _ Service = &Client{}
+
+func NewClient(conn *grpc.ClientConn) *Client {
+	return &Client{
+		client: pb.NewLogsServiceClient(conn),
+	}
+}
+
+func (c *Client) Log(ctx context.Context, entries []*Entry) error {
+	var pbEntries []*pb.LogEntry
+	for _, e := range entries {
+		pbEntries = append(pbEntries, EntryToPB(e))
+	}
+	response, err := c.client.Log(ctx, &pb.LogRequest{Entries: pbEntries})
+	if err != nil {
+		return err
+	}
+	if response.GetError() != nil {
+		return errorsgrpc.ErrorFromProto(nil, response.GetError())
+	}
+	return nil
+}
+
+func (c *Client) Find(ctx context.Context, filter *Filter, opts *options.FindOptions) (*FindResult, error) {
+	request := new(pb.FindRequest)
+
+	if filter != nil {
+		request.Filter = &pb.Filter{Q: filter.Q}
+	}
+
+	request.Options = options.FindOptionsToPB(opts)
+
+	response, err := c.client.Find(ctx, request)
+	if err != nil {
+		return nil, err
+	}
+	if response.GetError() != nil {
+		return nil, errorsgrpc.ErrorFromProto(nil, response.GetError())
+	}
+	return FindResultFromPB(response.GetResult()), nil
+}
+
+func (c *Client) Delete(ctx context.Context, filter *Filter) error {
+	request := new(pb.DeleteRequest)
+	if filter != nil {
+		request.Filter = &pb.Filter{Q: filter.Q}
+	}
+	response, err := c.client.Delete(ctx, &pb.DeleteRequest{Filter: &pb.Filter{Q: filter.Q}})
+	if err != nil {
+		return err
+	}
+	if response.GetError() != nil {
+		return errorsgrpc.ErrorFromProto(nil, response.GetError())
+	}
+	return nil
+}
diff --git a/logs/log.go b/logs/log.go
new file mode 100644
index 0000000000000000000000000000000000000000..2bc9dedf82c450e54d46363fbe63eceb44b1a50d
--- /dev/null
+++ b/logs/log.go
@@ -0,0 +1,127 @@
+package logs
+
+import (
+	"time"
+
+	"git.perx.ru/perxis/perxis-go/id"
+	pb "git.perx.ru/perxis/perxis-go/proto/logs"
+	"google.golang.org/protobuf/types/known/timestamppb"
+)
+
+type Level int
+
+const (
+	Info     = Level(pb.LogLevel_INFO)
+	Warning  = Level(pb.LogLevel_WARNING)
+	Error    = Level(pb.LogLevel_ERROR)
+	Critical = Level(pb.LogLevel_CRITICAL)
+	Fatal    = Level(pb.LogLevel_FATAL)
+)
+
+func (l Level) String() string {
+	s := pb.LogLevel_name[int32(l)]
+	if s == "" {
+		s = "UNKNOWN"
+	}
+	return s
+}
+
+type Entry struct {
+	ID        string       `json:"id" bson:"id" mapstructure:"id"`
+	Timestamp time.Time    `json:"timestamp,omitempty" bson:"timestamp,omitempty" mapstructure:"timestamp,omitempty"`
+	LogLevel  Level        `json:"log_level,omitempty" bson:"log_level,omitempty" mapstructure:"log_level,omitempty"`
+	Message   string       `json:"message,omitempty" bson:"message,omitempty" mapstructure:"message,omitempty"`
+	Category  string       `json:"category,omitempty" bson:"category,omitempty" mapstructure:"category,omitempty"`
+	Component string       `json:"component,omitempty" bson:"component,omitempty" mapstructure:"component,omitempty"`
+	Event     string       `json:"event,omitempty" bson:"event,omitempty" mapstructure:"event,omitempty"`
+	ObjectID  *id.ObjectId `json:"object_id,omitempty" bson:"object_id,omitempty" mapstructure:"object_id,omitempty"`
+	CallerID  *id.ObjectId `json:"caller_id,omitempty" bson:"caller_id,omitempty" mapstructure:"caller_id,omitempty"`
+	Attr      interface{}  `json:"attr,omitempty" bson:"attr,omitempty" mapstructure:"attr,omitempty"`
+	Tags      []string     `json:"tags,omitempty" bson:"tags,omitempty" mapstructure:"tags,omitempty"`
+}
+
+//func convertInterfaceToAny(v interface{}) (*any.Any, error) {
+//	anyValue := &any.Any{}
+//	bytes, _ := json.Marshal(v)
+//	bytesValue := &wrappers.BytesValue{
+//		Value: bytes,
+//	}
+//	err := anypb.MarshalFrom(anyValue, bytesValue, proto.MarshalOptions{})
+//	return anyValue, err
+//}
+
+func EntryToPB(entry *Entry) *pb.LogEntry {
+	logEntry := &pb.LogEntry{
+		Id:        entry.ID,
+		Timestamp: timestamppb.New(entry.Timestamp),
+		Level:     pb.LogLevel(entry.LogLevel),
+		Message:   entry.Message,
+		Category:  entry.Category,
+		Component: entry.Component,
+		Event:     entry.Event,
+		Attr:      nil, //implement
+		Tags:      entry.Tags,
+	}
+	if entry.ObjectID != nil {
+		logEntry.ObjectId = entry.ObjectID.String()
+	}
+	if entry.CallerID != nil {
+		logEntry.CallerId = entry.CallerID.String()
+	}
+
+	return logEntry
+}
+
+func EntryFromPB(request *pb.LogEntry) *Entry {
+	logEntry := &Entry{
+		ID:        request.Id,
+		Timestamp: request.Timestamp.AsTime(),
+		LogLevel:  Level(request.Level),
+		Message:   request.Message,
+		Category:  request.Category,
+		Component: request.Component,
+		Event:     request.Event,
+	}
+
+	if request.ObjectId != "" {
+		logEntry.ObjectID, _ = id.NewObjectId(request.ObjectId)
+	}
+	if request.CallerId != "" {
+		logEntry.CallerID, _ = id.NewObjectId(request.CallerId)
+	}
+	if request.Attr != nil {
+		logEntry.Attr = request.Attr // todo: как с этим работать?
+	}
+	if request.Tags != nil {
+		logEntry.Tags = request.Tags
+	}
+
+	return logEntry
+}
+
+func (e *Entry) ToMap() map[string]any {
+	res := map[string]any{
+		"id":        e.ID,
+		"timestamp": e.Timestamp,
+		"log_level": e.LogLevel,
+		"message":   e.Message,
+		"category":  e.Category,
+		"component": e.Component,
+		"event":     e.Event,
+	}
+	if e.ObjectID != nil {
+		res["object_id"] = e.ObjectID.String()
+		res["object"] = e.ObjectID.Map()
+	}
+	if e.CallerID != nil {
+		res["caller_id"] = e.CallerID.String()
+		res["caller"] = e.CallerID.Map()
+	}
+	if e.Attr != nil {
+		res["attr"] = e.Attr
+	}
+	if e.Tags != nil {
+		res["tags"] = e.Tags
+	}
+	return res
+}
diff --git a/logs/log_test.go b/logs/log_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..9ccb7e7065cccf5b3945339b6085a108e6b0fae2
--- /dev/null
+++ b/logs/log_test.go
@@ -0,0 +1,66 @@
+package logs
+
+import (
+	"testing"
+	"time"
+
+	"git.perx.ru/perxis/perxis-go/id"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestEntry_ToMap(t *testing.T) {
+	type fields struct {
+		ID        string
+		Timestamp time.Time
+		LogLevel  Level
+		Message   string
+		Category  string
+		Component string
+		Event     string
+		ObjectId  *id.ObjectId
+		CallerId  *id.ObjectId
+		Attr      interface{}
+		Tags      []string
+	}
+	tests := []struct {
+		name   string
+		fields fields
+		want   map[string]interface{}
+	}{
+		{
+			"#1",
+			fields{
+				"1",
+				time.Time{},
+				0,
+				"message",
+				"",
+				"",
+				"",
+				id.MustObjectId("/spaces/<space_id>/envs/<env_id>"),
+				id.MustObjectId("/users/<user_id>"),
+				nil,
+				nil,
+			},
+			map[string]interface{}{"caller": map[string]interface{}{"type": "user", "user_id": "<user_id>"}, "caller_id": "/users/<user_id>", "category": "", "component": "", "event": "", "id": "1", "log_level": Level(0), "message": "message", "object": map[string]interface{}{"env_id": "<env_id>", "space_id": "<space_id>", "type": "environment"}, "object_id": "/spaces/<space_id>/envs/<env_id>", "timestamp": time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC)},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			e := &Entry{
+				ID:        tt.fields.ID,
+				Timestamp: tt.fields.Timestamp,
+				LogLevel:  tt.fields.LogLevel,
+				Message:   tt.fields.Message,
+				Category:  tt.fields.Category,
+				Component: tt.fields.Component,
+				Event:     tt.fields.Event,
+				ObjectID:  tt.fields.ObjectId,
+				CallerID:  tt.fields.CallerId,
+				Attr:      tt.fields.Attr,
+				Tags:      tt.fields.Tags,
+			}
+			assert.Equal(t, tt.want, e.ToMap())
+		})
+	}
+}
diff --git a/logs/middleware/error_logging_middleware.go b/logs/middleware/error_logging_middleware.go
new file mode 100644
index 0000000000000000000000000000000000000000..5d6ab71065765f8c366dcf4f6528f944e27f87b1
--- /dev/null
+++ b/logs/middleware/error_logging_middleware.go
@@ -0,0 +1,61 @@
+// Code generated by gowrap. DO NOT EDIT.
+// template: ../../assets/templates/middleware/error_log
+// gowrap: http://github.com/hexdigest/gowrap
+
+package middleware
+
+//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/logs -i Service -t ../../assets/templates/middleware/error_log -o error_logging_middleware.go -l ""
+
+import (
+	"context"
+
+	"git.perx.ru/perxis/perxis-go/logs"
+	"git.perx.ru/perxis/perxis-go/pkg/options"
+	"go.uber.org/zap"
+)
+
+// errorLoggingMiddleware implements logs.Service that is instrumented with logging
+type errorLoggingMiddleware struct {
+	logger *zap.Logger
+	next   logs.Service
+}
+
+// ErrorLoggingMiddleware instruments an implementation of the logs.Service with simple logging
+func ErrorLoggingMiddleware(logger *zap.Logger) Middleware {
+	return func(next logs.Service) logs.Service {
+		return &errorLoggingMiddleware{
+			next:   next,
+			logger: logger,
+		}
+	}
+}
+
+func (m *errorLoggingMiddleware) Delete(ctx context.Context, filter *logs.Filter) (err error) {
+	logger := m.logger
+	defer func() {
+		if err != nil {
+			logger.Warn("response error", zap.Error(err))
+		}
+	}()
+	return m.next.Delete(ctx, filter)
+}
+
+func (m *errorLoggingMiddleware) Find(ctx context.Context, filter *logs.Filter, options *options.FindOptions) (fp1 *logs.FindResult, err error) {
+	logger := m.logger
+	defer func() {
+		if err != nil {
+			logger.Warn("response error", zap.Error(err))
+		}
+	}()
+	return m.next.Find(ctx, filter, options)
+}
+
+func (m *errorLoggingMiddleware) Log(ctx context.Context, entries []*logs.Entry) (err error) {
+	logger := m.logger
+	defer func() {
+		if err != nil {
+			logger.Warn("response error", zap.Error(err))
+		}
+	}()
+	return m.next.Log(ctx, entries)
+}
diff --git a/logs/middleware/logging_middleware.go b/logs/middleware/logging_middleware.go
new file mode 100644
index 0000000000000000000000000000000000000000..3a425bf03a64b0f5fa6d998db8d4076f92755d29
--- /dev/null
+++ b/logs/middleware/logging_middleware.go
@@ -0,0 +1,89 @@
+// 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/logs -i Service -t ../../assets/templates/middleware/access_log.tmpl -o logging_middleware.go -l ""
+
+import (
+	"context"
+	"time"
+
+	"git.perx.ru/perxis/perxis-go/logs"
+	"git.perx.ru/perxis/perxis-go/pkg/auth"
+	"git.perx.ru/perxis/perxis-go/pkg/options"
+	"go.uber.org/zap"
+)
+
+// accessLoggingMiddleware implements logs.Service that is instrumented with logging
+type accessLoggingMiddleware struct {
+	logger *zap.Logger
+	next   logs.Service
+}
+
+// AccessLoggingMiddleware instruments an implementation of the logs.Service with simple logging
+func AccessLoggingMiddleware(logger *zap.Logger) Middleware {
+	return func(next logs.Service) logs.Service {
+		return &accessLoggingMiddleware{
+			next:   next,
+			logger: logger,
+		}
+	}
+}
+
+func (m *accessLoggingMiddleware) Delete(ctx context.Context, filter *logs.Filter) (err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Delete.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("filter", filter),
+	)
+
+	err = m.next.Delete(ctx, filter)
+
+	m.logger.Debug("Delete.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	)
+
+	return err
+}
+
+func (m *accessLoggingMiddleware) Find(ctx context.Context, filter *logs.Filter, options *options.FindOptions) (fp1 *logs.FindResult, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Find.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("filter", filter),
+		zap.Reflect("options", options),
+	)
+
+	fp1, err = m.next.Find(ctx, filter, options)
+
+	m.logger.Debug("Find.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("fp1", fp1),
+		zap.Error(err),
+	)
+
+	return fp1, err
+}
+
+func (m *accessLoggingMiddleware) Log(ctx context.Context, entries []*logs.Entry) (err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Log.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("entries", entries),
+	)
+
+	err = m.next.Log(ctx, entries)
+
+	m.logger.Debug("Log.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	)
+
+	return err
+}
diff --git a/logs/middleware/middleware.go b/logs/middleware/middleware.go
new file mode 100644
index 0000000000000000000000000000000000000000..120d2b41cc8d392ed7ca2eff73f1f8b47c3dcc14
--- /dev/null
+++ b/logs/middleware/middleware.go
@@ -0,0 +1,28 @@
+// Code generated by gowrap. DO NOT EDIT.
+// 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/logs -i Service -t ../../assets/templates/middleware/middleware.tmpl -o middleware.go -l ""
+
+import (
+	"git.perx.ru/perxis/perxis-go/logs"
+	"go.uber.org/zap"
+)
+
+type Middleware func(logs.Service) logs.Service
+
+func WithLog(s logs.Service, logger *zap.Logger, log_access bool) logs.Service {
+	if logger == nil {
+		logger = zap.NewNop()
+	}
+	logger = logger.Named("Service")
+	if log_access {
+		s = AccessLoggingMiddleware(logger)(s)
+	}
+	s = ErrorLoggingMiddleware(logger)(s)
+
+	s = RecoveringMiddleware(logger)(s)
+	return s
+}
diff --git a/logs/middleware/recovering_middleware.go b/logs/middleware/recovering_middleware.go
new file mode 100644
index 0000000000000000000000000000000000000000..950963dbf13a88fb5d82cecc7f983f1a605d0f84
--- /dev/null
+++ b/logs/middleware/recovering_middleware.go
@@ -0,0 +1,68 @@
+// Code generated by gowrap. DO NOT EDIT.
+// template: ../../assets/templates/middleware/recovery
+// gowrap: http://github.com/hexdigest/gowrap
+
+package middleware
+
+//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/logs -i Service -t ../../assets/templates/middleware/recovery -o recovering_middleware.go -l ""
+
+import (
+	"context"
+	"fmt"
+
+	"git.perx.ru/perxis/perxis-go/logs"
+	"git.perx.ru/perxis/perxis-go/pkg/options"
+	"go.uber.org/zap"
+)
+
+// recoveringMiddleware implements logs.Service that is instrumented with logging
+type recoveringMiddleware struct {
+	logger *zap.Logger
+	next   logs.Service
+}
+
+// RecoveringMiddleware instruments an implementation of the logs.Service with simple logging
+func RecoveringMiddleware(logger *zap.Logger) Middleware {
+	return func(next logs.Service) logs.Service {
+		return &recoveringMiddleware{
+			next:   next,
+			logger: logger,
+		}
+	}
+}
+
+func (m *recoveringMiddleware) Delete(ctx context.Context, filter *logs.Filter) (err error) {
+	logger := m.logger
+	defer func() {
+		if r := recover(); r != nil {
+			logger.Error("panic", zap.Error(fmt.Errorf("%v", r)))
+			err = fmt.Errorf("%v", r)
+		}
+	}()
+
+	return m.next.Delete(ctx, filter)
+}
+
+func (m *recoveringMiddleware) Find(ctx context.Context, filter *logs.Filter, options *options.FindOptions) (fp1 *logs.FindResult, err error) {
+	logger := m.logger
+	defer func() {
+		if r := recover(); r != nil {
+			logger.Error("panic", zap.Error(fmt.Errorf("%v", r)))
+			err = fmt.Errorf("%v", r)
+		}
+	}()
+
+	return m.next.Find(ctx, filter, options)
+}
+
+func (m *recoveringMiddleware) Log(ctx context.Context, entries []*logs.Entry) (err error) {
+	logger := m.logger
+	defer func() {
+		if r := recover(); r != nil {
+			logger.Error("panic", zap.Error(fmt.Errorf("%v", r)))
+			err = fmt.Errorf("%v", r)
+		}
+	}()
+
+	return m.next.Log(ctx, entries)
+}
diff --git a/logs/middleware/telemetry_middleware.go b/logs/middleware/telemetry_middleware.go
new file mode 100644
index 0000000000000000000000000000000000000000..2aebc795b22d770c369f0445bfa543bde6828ac2
--- /dev/null
+++ b/logs/middleware/telemetry_middleware.go
@@ -0,0 +1,151 @@
+// Code generated by gowrap. DO NOT EDIT.
+// template: ../../assets/templates/middleware/telemetry
+// gowrap: http://github.com/hexdigest/gowrap
+
+package middleware
+
+//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/logs -i Service -t ../../assets/templates/middleware/telemetry -o telemetry_middleware.go -l ""
+
+// source template: https://github.com/hexdigest/gowrap/blob/master/templates/opentelemetry
+
+import (
+	"context"
+	"time"
+
+	"git.perx.ru/perxis/perxis-go/logs"
+	"git.perx.ru/perxis/perxis-go/pkg/options"
+	"git.perx.ru/perxis/perxis-go/pkg/telemetry/metrics"
+	"go.opentelemetry.io/otel"
+	"go.opentelemetry.io/otel/attribute"
+	otelmetric "go.opentelemetry.io/otel/metric"
+	"go.opentelemetry.io/otel/trace"
+)
+
+// telemetryMiddleware implements logs.Service interface instrumented with opentracing spans
+type telemetryMiddleware struct {
+	logs.Service
+	_instance      string
+	requestMetrics *metrics.RequestMetrics
+	_spanDecorator func(span trace.Span, params, results map[string]interface{})
+}
+
+// TelemetryMiddleware returns telemetryMiddleware
+func TelemetryMiddleware(base logs.Service, instance string, spanDecorator ...func(span trace.Span, params, results map[string]interface{})) telemetryMiddleware {
+	requestMetrics, err := metrics.GetRequestMetrics()
+	if err != nil {
+		panic(err)
+	}
+
+	d := telemetryMiddleware{
+		Service:        base,
+		_instance:      instance,
+		requestMetrics: requestMetrics,
+	}
+
+	if len(spanDecorator) > 0 && spanDecorator[0] != nil {
+		d._spanDecorator = spanDecorator[0]
+	}
+
+	return d
+}
+
+// Delete implements logs.Service
+func (_d telemetryMiddleware) Delete(ctx context.Context, filter *logs.Filter) (err error) {
+	attributes := otelmetric.WithAttributeSet(attribute.NewSet(
+		attribute.String("service", "Service"),
+		attribute.String("method", "Delete"),
+	))
+
+	_d.requestMetrics.Total.Add(ctx, 1, attributes)
+
+	start := time.Now()
+	ctx, _span := otel.Tracer(_d._instance).Start(ctx, "Service.Delete")
+
+	defer func() {
+		_d.requestMetrics.DurationMilliseconds.Record(ctx, time.Since(start).Milliseconds(), attributes)
+
+		if _d._spanDecorator != nil {
+			_d._spanDecorator(_span, map[string]interface{}{
+				"ctx":    ctx,
+				"filter": filter}, map[string]interface{}{
+				"err": err})
+		} else if err != nil {
+			_d.requestMetrics.FailedTotal.Add(ctx, 1, attributes)
+
+			_span.RecordError(err)
+			_span.SetAttributes(attribute.String("event", "error"))
+			_span.SetAttributes(attribute.String("message", err.Error()))
+		}
+
+		_span.End()
+	}()
+	return _d.Service.Delete(ctx, filter)
+}
+
+// Find implements logs.Service
+func (_d telemetryMiddleware) Find(ctx context.Context, filter *logs.Filter, options *options.FindOptions) (fp1 *logs.FindResult, err error) {
+	attributes := otelmetric.WithAttributeSet(attribute.NewSet(
+		attribute.String("service", "Service"),
+		attribute.String("method", "Find"),
+	))
+
+	_d.requestMetrics.Total.Add(ctx, 1, attributes)
+
+	start := time.Now()
+	ctx, _span := otel.Tracer(_d._instance).Start(ctx, "Service.Find")
+
+	defer func() {
+		_d.requestMetrics.DurationMilliseconds.Record(ctx, time.Since(start).Milliseconds(), attributes)
+
+		if _d._spanDecorator != nil {
+			_d._spanDecorator(_span, map[string]interface{}{
+				"ctx":     ctx,
+				"filter":  filter,
+				"options": options}, map[string]interface{}{
+				"fp1": fp1,
+				"err": err})
+		} else if err != nil {
+			_d.requestMetrics.FailedTotal.Add(ctx, 1, attributes)
+
+			_span.RecordError(err)
+			_span.SetAttributes(attribute.String("event", "error"))
+			_span.SetAttributes(attribute.String("message", err.Error()))
+		}
+
+		_span.End()
+	}()
+	return _d.Service.Find(ctx, filter, options)
+}
+
+// Log implements logs.Service
+func (_d telemetryMiddleware) Log(ctx context.Context, entries []*logs.Entry) (err error) {
+	attributes := otelmetric.WithAttributeSet(attribute.NewSet(
+		attribute.String("service", "Service"),
+		attribute.String("method", "Log"),
+	))
+
+	_d.requestMetrics.Total.Add(ctx, 1, attributes)
+
+	start := time.Now()
+	ctx, _span := otel.Tracer(_d._instance).Start(ctx, "Service.Log")
+
+	defer func() {
+		_d.requestMetrics.DurationMilliseconds.Record(ctx, time.Since(start).Milliseconds(), attributes)
+
+		if _d._spanDecorator != nil {
+			_d._spanDecorator(_span, map[string]interface{}{
+				"ctx":     ctx,
+				"entries": entries}, map[string]interface{}{
+				"err": err})
+		} else if err != nil {
+			_d.requestMetrics.FailedTotal.Add(ctx, 1, attributes)
+
+			_span.RecordError(err)
+			_span.SetAttributes(attribute.String("event", "error"))
+			_span.SetAttributes(attribute.String("message", err.Error()))
+		}
+
+		_span.End()
+	}()
+	return _d.Service.Log(ctx, entries)
+}
diff --git a/logs/mocks/Service.go b/logs/mocks/Service.go
new file mode 100644
index 0000000000000000000000000000000000000000..af4da73bc4d4c7b088fbfea7e7babc4c1ba30e36
--- /dev/null
+++ b/logs/mocks/Service.go
@@ -0,0 +1,97 @@
+// Code generated by mockery v2.40.1. DO NOT EDIT.
+
+package mocks
+
+import (
+	context "context"
+
+	log2 "git.perx.ru/perxis/perxis-go/logs"
+	mock "github.com/stretchr/testify/mock"
+
+	options "git.perx.ru/perxis/perxis-go/pkg/options"
+)
+
+// Service is an autogenerated mock type for the Service type
+type Service struct {
+	mock.Mock
+}
+
+// Delete provides a mock function with given fields: ctx, filter
+func (_m *Service) Delete(ctx context.Context, filter *log2.Filter) error {
+	ret := _m.Called(ctx, filter)
+
+	if len(ret) == 0 {
+		panic("no return value specified for Delete")
+	}
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, *log2.Filter) error); ok {
+		r0 = rf(ctx, filter)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// Find provides a mock function with given fields: ctx, filter, _a2
+func (_m *Service) Find(ctx context.Context, filter *log2.Filter, _a2 *options.FindOptions) (*log2.FindResult, error) {
+	ret := _m.Called(ctx, filter, _a2)
+
+	if len(ret) == 0 {
+		panic("no return value specified for Find")
+	}
+
+	var r0 *log2.FindResult
+	var r1 error
+	if rf, ok := ret.Get(0).(func(context.Context, *log2.Filter, *options.FindOptions) (*log2.FindResult, error)); ok {
+		return rf(ctx, filter, _a2)
+	}
+	if rf, ok := ret.Get(0).(func(context.Context, *log2.Filter, *options.FindOptions) *log2.FindResult); ok {
+		r0 = rf(ctx, filter, _a2)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*log2.FindResult)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func(context.Context, *log2.Filter, *options.FindOptions) error); ok {
+		r1 = rf(ctx, filter, _a2)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// Log provides a mock function with given fields: ctx, entries
+func (_m *Service) Log(ctx context.Context, entries []*log2.Entry) error {
+	ret := _m.Called(ctx, entries)
+
+	if len(ret) == 0 {
+		panic("no return value specified for Log")
+	}
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, []*log2.Entry) error); ok {
+		r0 = rf(ctx, entries)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// NewService creates a new instance of Service. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+// The first argument is typically a *testing.T value.
+func NewService(t interface {
+	mock.TestingT
+	Cleanup(func())
+}) *Service {
+	mock := &Service{}
+	mock.Mock.Test(t)
+
+	t.Cleanup(func() { mock.AssertExpectations(t) })
+
+	return mock
+}
diff --git a/logs/mocks/Storage.go b/logs/mocks/Storage.go
new file mode 100644
index 0000000000000000000000000000000000000000..87a6376d47ee44ece2f9d2458ee05e48a162b8ca
--- /dev/null
+++ b/logs/mocks/Storage.go
@@ -0,0 +1,140 @@
+// Code generated by mockery v2.40.1. DO NOT EDIT.
+
+package mocks
+
+import (
+	context "context"
+
+	log2 "git.perx.ru/perxis/perxis-go/logs"
+	mock "github.com/stretchr/testify/mock"
+
+	options "git.perx.ru/perxis/perxis-go/pkg/options"
+)
+
+// Storage is an autogenerated mock type for the Storage type
+type Storage struct {
+	mock.Mock
+}
+
+// Delete provides a mock function with given fields: ctx, filter
+func (_m *Storage) Delete(ctx context.Context, filter *log2.Filter) error {
+	ret := _m.Called(ctx, filter)
+
+	if len(ret) == 0 {
+		panic("no return value specified for Delete")
+	}
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, *log2.Filter) error); ok {
+		r0 = rf(ctx, filter)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// Find provides a mock function with given fields: ctx, filter, _a2
+func (_m *Storage) Find(ctx context.Context, filter *log2.Filter, _a2 *options.FindOptions) ([]*log2.Entry, int, error) {
+	ret := _m.Called(ctx, filter, _a2)
+
+	if len(ret) == 0 {
+		panic("no return value specified for Find")
+	}
+
+	var r0 []*log2.Entry
+	var r1 int
+	var r2 error
+	if rf, ok := ret.Get(0).(func(context.Context, *log2.Filter, *options.FindOptions) ([]*log2.Entry, int, error)); ok {
+		return rf(ctx, filter, _a2)
+	}
+	if rf, ok := ret.Get(0).(func(context.Context, *log2.Filter, *options.FindOptions) []*log2.Entry); ok {
+		r0 = rf(ctx, filter, _a2)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).([]*log2.Entry)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func(context.Context, *log2.Filter, *options.FindOptions) int); ok {
+		r1 = rf(ctx, filter, _a2)
+	} else {
+		r1 = ret.Get(1).(int)
+	}
+
+	if rf, ok := ret.Get(2).(func(context.Context, *log2.Filter, *options.FindOptions) error); ok {
+		r2 = rf(ctx, filter, _a2)
+	} else {
+		r2 = ret.Error(2)
+	}
+
+	return r0, r1, r2
+}
+
+// Init provides a mock function with given fields: ctx
+func (_m *Storage) Init(ctx context.Context) error {
+	ret := _m.Called(ctx)
+
+	if len(ret) == 0 {
+		panic("no return value specified for Init")
+	}
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context) error); ok {
+		r0 = rf(ctx)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// Log provides a mock function with given fields: ctx, entry
+func (_m *Storage) Log(ctx context.Context, entry []*log2.Entry) error {
+	ret := _m.Called(ctx, entry)
+
+	if len(ret) == 0 {
+		panic("no return value specified for Log")
+	}
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, []*log2.Entry) error); ok {
+		r0 = rf(ctx, entry)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// Reset provides a mock function with given fields: ctx
+func (_m *Storage) Reset(ctx context.Context) error {
+	ret := _m.Called(ctx)
+
+	if len(ret) == 0 {
+		panic("no return value specified for Reset")
+	}
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context) error); ok {
+		r0 = rf(ctx)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// NewStorage creates a new instance of Storage. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+// The first argument is typically a *testing.T value.
+func NewStorage(t interface {
+	mock.TestingT
+	Cleanup(func())
+}) *Storage {
+	mock := &Storage{}
+	mock.Mock.Test(t)
+
+	t.Cleanup(func() { mock.AssertExpectations(t) })
+
+	return mock
+}
diff --git a/logs/service.go b/logs/service.go
new file mode 100644
index 0000000000000000000000000000000000000000..34b3b4e61bd4b24c43dea910dcc54874deab13c5
--- /dev/null
+++ b/logs/service.go
@@ -0,0 +1,76 @@
+package logs
+
+import (
+	"context"
+
+	itemstransportgrpc "git.perx.ru/perxis/perxis-go/pkg/items/transport/grpc"
+	"git.perx.ru/perxis/perxis-go/pkg/options"
+	pb "git.perx.ru/perxis/perxis-go/proto/logs"
+)
+
+const ServiceName = "logs"
+
+type Service interface {
+
+	// Log метод записи логов
+	Log(ctx context.Context, entries []*Entry) error
+
+	// Find метод для поиска логов по заданным параметрам
+	Find(ctx context.Context, filter *Filter, options *options.FindOptions) (*FindResult, error)
+
+	// Delete метод для удаления логов по заданным параметрам
+	Delete(ctx context.Context, filter *Filter) error
+}
+
+type Filter struct {
+	Q []string
+}
+
+type FindResult struct {
+	Entries []*Entry
+	Filter  *Filter
+	Options *options.FindOptions
+	Total   uint32
+}
+
+func FindResultToPB(result *FindResult) *pb.FindResult {
+	findResult := &pb.FindResult{
+		Total: result.Total,
+	}
+
+	entries := make([]*pb.LogEntry, 0, len(result.Entries))
+	for _, e := range result.Entries {
+		entries = append(entries, EntryToPB(e))
+	}
+	findResult.Entries = entries
+
+	if result.Filter != nil {
+		findResult.Filter.Q = result.Filter.Q
+	}
+	if result.Options != nil {
+		findResult.Options, _ = itemstransportgrpc.PtrServicesFindOptionsToProto(result.Options)
+	}
+
+	return findResult
+}
+
+func FindResultFromPB(result *pb.FindResult) *FindResult {
+	findResult := &FindResult{
+		Total: result.Total,
+	}
+
+	entries := make([]*Entry, 0, len(result.Entries))
+	for _, e := range result.Entries {
+		entries = append(entries, EntryFromPB(e))
+	}
+	findResult.Entries = entries
+
+	if result.Filter != nil {
+		findResult.Filter.Q = result.Filter.Q
+	}
+	if result.Options != nil {
+		findResult.Options, _ = itemstransportgrpc.ProtoToPtrServicesFindOptions(result.Options)
+	}
+
+	return findResult
+}
diff --git a/logs/storage.go b/logs/storage.go
new file mode 100644
index 0000000000000000000000000000000000000000..e6271dace2fca81715c0fa2d0982191c4fa517e6
--- /dev/null
+++ b/logs/storage.go
@@ -0,0 +1,15 @@
+package logs
+
+import (
+	"context"
+
+	"git.perx.ru/perxis/perxis-go/pkg/options"
+)
+
+type Storage interface {
+	Init(ctx context.Context) error
+	Reset(ctx context.Context) error
+	Log(ctx context.Context, entry []*Entry) error
+	Find(ctx context.Context, filter *Filter, options *options.FindOptions) ([]*Entry, int, error)
+	Delete(ctx context.Context, filter *Filter) error
+}
diff --git a/logs/zap/buffered_write_syncer.go b/logs/zap/buffered_write_syncer.go
new file mode 100644
index 0000000000000000000000000000000000000000..e862a79d115ecd82bc09622276d4194d39715345
--- /dev/null
+++ b/logs/zap/buffered_write_syncer.go
@@ -0,0 +1,182 @@
+package zap
+
+import (
+	"context"
+	"sync"
+	"time"
+
+	"git.perx.ru/perxis/perxis-go/logs"
+	"git.perx.ru/perxis/perxis-go/pkg/auth"
+	"git.perx.ru/perxis/perxis-go/pkg/errors"
+)
+
+const (
+	defaultMaxBufferSize    = 1000
+	defaultMaxSyncQueueSize = 16
+	defaultFlushInterval    = 5 * time.Second
+)
+
+var SyncQueueOverflow = errors.New("sync queue overflow")
+
+// BufferedWriteSyncer это WriteSyncer, который отправляет записи в logs.Service.
+// Когда количество буферизированных записей достигает некоторого предела или проходит определенный фиксированный интервал,
+// записи отправляются в очередь для синхронизации с logs.Service.
+type BufferedWriteSyncer struct {
+	// FlushInterval устанавливает интервал, через который буферизированные записи будут отправлены на синхронизацию.
+	//
+	// Значение по умолчанию для этого параметра равно 5 секунд.
+	FlushInterval time.Duration
+
+	// MaxBufferSize устанавливает максимальное количество записей, которые могут быть буферизованы.
+	// Когда количество буферизованных записей превысит этот порог, они будут отправлены на синхронизацию в logs.Service.
+	//
+	// Значение по умолчанию для этого параметра равно 1000.
+	MaxBufferSize int
+
+	// MaxSyncQueueSize устанавливает максимальный размер очереди записей на синхронизацию с logs.Service.
+	//
+	// Значение по умолчанию для этого параметра равно 16.
+	MaxSyncQueueSize int
+
+	// Service сервис для хранения записей
+	Service logs.Service
+
+	wg        sync.WaitGroup
+	mu        sync.RWMutex
+	buffer    []*logs.Entry
+	syncQueue chan []*logs.Entry
+
+	flushStop chan struct{} // flushStop закрывается, когда flushLoop должен быть остановлен
+	started   bool          // started указывает, был ли выполнен Start
+	stopped   bool          // stopped указывает, был ли выполнен Stop
+}
+
+func (ws *BufferedWriteSyncer) start() {
+	if ws.Service == nil {
+		panic("service is required")
+	}
+
+	if ws.FlushInterval == 0 {
+		ws.FlushInterval = defaultFlushInterval
+	}
+
+	if ws.MaxBufferSize == 0 {
+		ws.MaxBufferSize = defaultMaxBufferSize
+	}
+
+	if ws.MaxSyncQueueSize == 0 {
+		ws.MaxSyncQueueSize = defaultMaxSyncQueueSize
+	}
+
+	ws.buffer = make([]*logs.Entry, 0, ws.MaxBufferSize)
+	ws.syncQueue = make(chan []*logs.Entry, ws.MaxSyncQueueSize)
+	ws.flushStop = make(chan struct{})
+
+	ws.wg.Add(2)
+	go ws.syncLoop()
+	go ws.flushLoop()
+
+	ws.started = true
+}
+
+func (ws *BufferedWriteSyncer) Stop() error {
+	ws.mu.Lock()
+	defer ws.mu.Unlock()
+
+	if !ws.started || ws.stopped {
+		return nil
+	}
+	ws.stopped = true
+
+	close(ws.flushStop) // завершаем flushLoop
+
+	err := ws.flush() // очищаем оставшиеся записи
+
+	close(ws.syncQueue) // завершаем syncLoop
+
+	ws.wg.Wait() // дожидаемся завершения flushLoop и syncLoop
+
+	return err
+}
+
+// Write отправляет запись в буфер.
+// Когда количество буферизованных записей превышает максимальный размер буфера, буферизированные записи будут отправлены на синхронизацию.
+func (ws *BufferedWriteSyncer) Write(entry *logs.Entry) error {
+	ws.mu.Lock()
+	defer ws.mu.Unlock()
+
+	if !ws.started {
+		ws.start()
+	}
+
+	// Проверяем, не достигли ли мы предела размера буфера. Если это так, тогда освобождаем его.
+	if len(ws.buffer)+1 > ws.MaxBufferSize {
+		err := ws.flush()
+		if err != nil {
+			return err
+		}
+	}
+
+	ws.buffer = append(ws.buffer, entry)
+
+	return nil
+}
+
+// Sync освобождает буфер и отправляет буферизированные записи на синхронизацию.
+func (ws *BufferedWriteSyncer) Sync() error {
+	ws.mu.Lock()
+	defer ws.mu.Unlock()
+
+	if ws.started {
+		return ws.flush()
+	}
+
+	return nil
+}
+
+// flush освобождает буфер и отправляет буферизированные записи на синхронизацию.
+// Если очередь на синхронизацию переполнена, будет возвращена ошибка SyncQueueOverflow
+//
+// ВНИМАНИЕ: Не является безопасным для конкурентного вызова.
+func (ws *BufferedWriteSyncer) flush() error {
+	if len(ws.buffer) == 0 {
+		return nil
+	}
+
+	// Проверяем, не достигли ли мы предела размера очереди. Если это так, возвращаем ошибку.
+	if len(ws.syncQueue)+1 > ws.MaxSyncQueueSize {
+		return SyncQueueOverflow
+	}
+
+	ws.syncQueue <- ws.buffer
+	ws.buffer = make([]*logs.Entry, 0, ws.MaxBufferSize)
+
+	return nil
+}
+
+// flushLoop периодически отправляет буферизированные записи на синхронизацию.
+func (ws *BufferedWriteSyncer) flushLoop() {
+	ticker := time.NewTicker(ws.FlushInterval)
+	defer func() {
+		ticker.Stop()
+		ws.wg.Done()
+	}()
+
+	for {
+		select {
+		case <-ticker.C:
+			_ = ws.Sync()
+		case <-ws.flushStop:
+			return
+		}
+	}
+}
+
+// syncLoop синхронизирует записи с logs.Service.
+func (ws *BufferedWriteSyncer) syncLoop() {
+	defer ws.wg.Done()
+
+	for entries := range ws.syncQueue {
+		_ = ws.Service.Log(auth.WithSystem(context.Background()), entries)
+	}
+}
diff --git a/logs/zap/buffered_write_syncer_test.go b/logs/zap/buffered_write_syncer_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..073663edc829dd1847ce416b704916ec92ccc80c
--- /dev/null
+++ b/logs/zap/buffered_write_syncer_test.go
@@ -0,0 +1,131 @@
+package zap
+
+import (
+	"sync"
+	"testing"
+	"time"
+
+	"git.perx.ru/perxis/perxis-go/logs"
+	logmocks "git.perx.ru/perxis/perxis-go/logs/mocks"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/mock"
+	"github.com/stretchr/testify/require"
+)
+
+func TestBufferedWriteSyncer_Write(t *testing.T) {
+	service := &logmocks.Service{}
+	service.On("Log", mock.Anything, mock.Anything).
+		Return(nil).
+		Run(func(args mock.Arguments) {
+			entries := args.Get(1).([]*logs.Entry)
+			require.Equal(t, 2, len(entries))
+		}).
+		Once()
+
+	ws := &BufferedWriteSyncer{Service: service}
+
+	err := ws.Write(&logs.Entry{Message: "first log message"})
+	require.NoError(t, err)
+
+	err = ws.Write(&logs.Entry{Message: "second log message"})
+	require.NoError(t, err)
+
+	err = ws.Stop()
+	require.NoError(t, err)
+
+	service.AssertExpectations(t)
+}
+
+func TestBufferedWriteSyncer_Write_Concurrent(t *testing.T) {
+	service := &logmocks.Service{}
+	service.On("Log", mock.Anything, mock.Anything).
+		Return(nil).
+		Run(func(args mock.Arguments) {
+			entries := args.Get(1).([]*logs.Entry)
+			require.Equal(t, 100, len(entries))
+		}).
+		Once()
+
+	ws := &BufferedWriteSyncer{Service: service}
+
+	var wg sync.WaitGroup
+	for i := 0; i < 100; i++ {
+		wg.Add(1)
+		go func(wg *sync.WaitGroup) {
+			defer wg.Done()
+			require.NoError(t, ws.Write(&logs.Entry{Message: "log message"}))
+		}(&wg)
+	}
+
+	wg.Wait()
+	require.NoError(t, ws.Stop())
+
+	service.AssertExpectations(t)
+}
+
+func TestBufferedWriteSyncer_Flush(t *testing.T) {
+	service := &logmocks.Service{}
+	service.On("Log", mock.Anything, mock.Anything).
+		Return(nil).
+		Run(func(args mock.Arguments) {
+			entries := args.Get(1).([]*logs.Entry)
+			require.Equal(t, 10, len(entries))
+		}).
+		Times(10)
+
+	ws := &BufferedWriteSyncer{Service: service}
+
+	for i := 0; i < 10; i++ {
+		for j := 0; j < 10; j++ {
+			require.NoError(t, ws.Write(&logs.Entry{Message: "log message"}))
+		}
+		require.NoError(t, ws.Sync())
+	}
+
+	require.NoError(t, ws.Stop())
+
+	service.AssertExpectations(t)
+}
+
+func TestBufferedWriteSyncer_MaxBufferSize(t *testing.T) {
+	service := &logmocks.Service{}
+	service.On("Log", mock.Anything, mock.Anything).
+		Return(nil).
+		Run(func(args mock.Arguments) {
+			entries := args.Get(1).([]*logs.Entry)
+			assert.Equal(t, 10, len(entries))
+		}).
+		Times(10)
+
+	ws := &BufferedWriteSyncer{Service: service, MaxBufferSize: 10}
+
+	for i := 0; i < 100; i++ {
+		require.NoError(t, ws.Write(&logs.Entry{Message: "log message"}))
+	}
+
+	require.NoError(t, ws.Stop())
+
+	service.AssertExpectations(t)
+}
+
+func TestBufferedWriteSyncer_FlushInterval(t *testing.T) {
+	service := &logmocks.Service{}
+	service.On("Log", mock.Anything, mock.Anything).
+		Return(nil).
+		Run(func(args mock.Arguments) {
+			entries := args.Get(1).([]*logs.Entry)
+			assert.Equal(t, 10, len(entries))
+		}).
+		Once()
+
+	ws := &BufferedWriteSyncer{Service: service, FlushInterval: time.Second}
+
+	for j := 0; j < 10; j++ {
+		require.NoError(t, ws.Write(&logs.Entry{Message: "log message"}))
+	}
+
+	time.Sleep(3 * time.Second) // ждем, пока сработает интервал
+	require.NoError(t, ws.Stop())
+
+	service.AssertExpectations(t)
+}
diff --git a/logs/zap/core.go b/logs/zap/core.go
new file mode 100644
index 0000000000000000000000000000000000000000..3b0c3305f685f8dfe52628dd49c1d4716fda9e91
--- /dev/null
+++ b/logs/zap/core.go
@@ -0,0 +1,94 @@
+package zap
+
+import (
+	"fmt"
+
+	oid "git.perx.ru/perxis/perxis-go/id"
+	"git.perx.ru/perxis/perxis-go/logs"
+	"git.perx.ru/perxis/perxis-go/pkg/id"
+	"go.uber.org/zap"
+	"go.uber.org/zap/zapcore"
+)
+
+// WriteSyncer отвечает за хранение и синхронизацию logs.Entry
+type WriteSyncer interface {
+	Write(entry *logs.Entry) error
+	Sync() error
+}
+
+// Core кодирует zapcore.Entry в logs.Entry и отправляет их в WriteSyncer
+type Core struct {
+	zapcore.LevelEnabler
+
+	writeSyncer WriteSyncer
+	fields      []zap.Field
+}
+
+func NewCore(writeSyncer WriteSyncer) *Core {
+	return &Core{
+		LevelEnabler: zapcore.InfoLevel,
+		writeSyncer:  writeSyncer,
+	}
+}
+
+func (core *Core) With(fields []zapcore.Field) zapcore.Core {
+	return &Core{
+		LevelEnabler: core.LevelEnabler,
+		writeSyncer:  core.writeSyncer,
+		fields:       append(core.fields, fields...),
+	}
+}
+
+func (core *Core) Check(entry zapcore.Entry, checkedEntry *zapcore.CheckedEntry) *zapcore.CheckedEntry {
+	if core.Enabled(entry.Level) {
+		return checkedEntry.AddCore(entry, core)
+	}
+	return checkedEntry
+}
+
+func (core *Core) Write(entry zapcore.Entry, fields []zapcore.Field) error {
+	return core.writeSyncer.Write(core.getEntry(entry, fields))
+}
+
+func (core *Core) Sync() error {
+	return core.writeSyncer.Sync()
+}
+
+func (core *Core) getEntry(entry zapcore.Entry, fields []zapcore.Field) *logs.Entry {
+	if len(core.fields) > 0 {
+		fields = append(fields, core.fields...)
+	}
+
+	enc := zapcore.NewMapObjectEncoder()
+	for _, field := range fields {
+		field.AddTo(enc)
+	}
+
+	ent := &logs.Entry{
+		ID:        id.GenerateNewID(),
+		Timestamp: entry.Time,
+		LogLevel:  logs.Level(entry.Level),
+		Message:   entry.Message,
+	}
+
+	ent.Category, _ = enc.Fields["category"].(string)
+	ent.Component, _ = enc.Fields["component"].(string)
+	ent.Event, _ = enc.Fields["event"].(string)
+	ent.ObjectID, _ = enc.Fields["object"].(*oid.ObjectId)
+	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 {
+				ent.Tags = append(ent.Tags, tag)
+			}
+		}
+	}
+
+	return ent
+}
diff --git a/logs/zap/core_test.go b/logs/zap/core_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..aabe9cea3da3f4eb4da1d0696c7c6fb08753cc63
--- /dev/null
+++ b/logs/zap/core_test.go
@@ -0,0 +1,65 @@
+package zap
+
+import (
+	"testing"
+
+	"git.perx.ru/perxis/perxis-go/id"
+	"git.perx.ru/perxis/perxis-go/logs"
+	logzap "git.perx.ru/perxis/perxis-go/zap"
+	"github.com/stretchr/testify/require"
+	"go.uber.org/zap"
+	"go.uber.org/zap/zapcore"
+)
+
+func TestCore_getEntry(t *testing.T) {
+	core := NewCore(nil)
+
+	tests := []struct {
+		name  string
+		input struct {
+			entry  zapcore.Entry
+			fields []zapcore.Field
+		}
+		want *logs.Entry
+	}{
+		{
+			name: "simple",
+			input: struct {
+				entry  zapcore.Entry
+				fields []zapcore.Field
+			}{
+				entry: zapcore.Entry{Level: zapcore.InfoLevel, Message: "создан элемент коллекции"},
+				fields: []zapcore.Field{
+					zap.String("key", "val"), // будет проигнорировано
+					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: &logs.Entry{
+				LogLevel:  logs.Level(zapcore.InfoLevel),
+				Message:   "создан элемент коллекции",
+				Category:  "create",
+				Component: "Items.Service",
+				Event:     "Items.Create",
+				ObjectID:  id.MustObjectId("/spaces/WPNN/envs/9VGP/cols/GxNv/items/W0fl"),
+				CallerID:  id.MustObjectId("/users/PHVz"),
+				Attr:      "any",
+				Tags:      []string{"tag1", "tag2", "tag3"},
+			},
+		},
+	}
+
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			got := core.getEntry(tc.input.entry, tc.input.fields)
+			got.ID = tc.want.ID               // игнорируем ID
+			got.Timestamp = tc.want.Timestamp // игнорируем Timestamp
+			require.Equal(t, tc.want, got)
+		})
+	}
+}
diff --git a/logs/zap/example_test.go b/logs/zap/example_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..9dfd0aaa2f33820c72bffcdd6ecae965672ad838
--- /dev/null
+++ b/logs/zap/example_test.go
@@ -0,0 +1,97 @@
+package zap
+
+import (
+	"context"
+	"reflect"
+	"slices"
+	"testing"
+
+	"git.perx.ru/perxis/perxis-go/id"
+	"git.perx.ru/perxis/perxis-go/logs"
+	logmocks "git.perx.ru/perxis/perxis-go/logs/mocks"
+	"git.perx.ru/perxis/perxis-go/pkg/auth"
+	"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"
+	logzap "git.perx.ru/perxis/perxis-go/zap"
+	"github.com/stretchr/testify/mock"
+	"github.com/stretchr/testify/require"
+	"go.uber.org/zap"
+	"go.uber.org/zap/zapcore"
+)
+
+func TestExample(t *testing.T) {
+	item := items.NewItem("WPNN", "9VGP", "GxNv", "W0fl", nil, nil)
+	user := &users.User{ID: "294de355"}
+
+	wantEntries := []*logs.Entry{
+		{
+			LogLevel:  logs.Level(zapcore.InfoLevel),
+			Message:   "Successfully created",
+			Component: "Items",
+			Event:     items.EventCreateItem,
+			ObjectID:  id.MustObjectId(item),
+			CallerID:  id.MustObjectId(user),
+			Tags:      []string{"tag1", "tag2", "tag3"},
+		},
+		{
+			LogLevel:  logs.Level(zapcore.WarnLevel),
+			Message:   "Successfully updated",
+			Component: "Items",
+			Event:     items.EventUpdateItem,
+			ObjectID:  id.MustObjectId(item),
+			CallerID:  id.MustObjectId(user),
+			Attr:      map[string]map[string]any{"title": {"old": "old title", "new": "new title"}},
+		},
+	}
+
+	service := &logmocks.Service{}
+	service.On("Log", mock.Anything, mock.Anything).
+		Return(nil).
+		Run(func(args mock.Arguments) {
+			entries := args.Get(1).([]*logs.Entry)
+			require.True(t, slices.EqualFunc(wantEntries, entries, func(wantEntry, gotEntry *logs.Entry) bool {
+				require.NotEmpty(t, gotEntry.ID)
+				require.NotEmpty(t, gotEntry.Timestamp)
+				gotEntry.ID = wantEntry.ID               // игнорируем ID
+				gotEntry.Timestamp = wantEntry.Timestamp // игнорируем Timestamp
+				return reflect.DeepEqual(wantEntry, gotEntry)
+			}))
+		}).
+		Once()
+
+	usersService := &usersmocks.Users{}
+	usersService.On("GetByIdentity", mock.Anything, "74d90aaf").Return(user, nil).Once()
+
+	factory := auth.PrincipalFactory{Users: usersService}
+
+	ws := &BufferedWriteSyncer{Service: service}
+	logger := zap.New(NewCore(ws))
+
+	// Пример отправки логов для сервиса Items
+	{
+		logger := logger.With(logzap.Component("Items"))
+		ctx := auth.WithPrincipal(context.Background(), factory.User("74d90aaf"))
+
+		// Отправка лога при создании item
+		logger.Info("Successfully created",
+			logzap.Event(items.EventCreateItem),
+			logzap.Object(item),
+			logzap.CallerFromContext(ctx),
+			logzap.Tags("tag1", "tag2", "tag3"),
+		)
+
+		// Отправка лога при обновлении item
+		logger.Warn("Successfully updated",
+			logzap.Event(items.EventUpdateItem),
+			logzap.Object(item),
+			logzap.CallerFromContext(ctx),
+			logzap.Attr(map[string]map[string]any{"title": {"old": "old title", "new": "new title"}}),
+		)
+	}
+
+	err := ws.Stop()
+	require.NoError(t, err)
+
+	service.AssertExpectations(t)
+}
diff --git a/perxis-proto b/perxis-proto
index 78fe6a1ea7e2fe588e4107bf14ac85293b201163..f10336dc4a4f58111c12dd95afec82be18388803 160000
--- a/perxis-proto
+++ b/perxis-proto
@@ -1 +1 @@
-Subproject commit 78fe6a1ea7e2fe588e4107bf14ac85293b201163
+Subproject commit f10336dc4a4f58111c12dd95afec82be18388803
diff --git a/pkg/clients/middleware/access_logging_middleware.go b/pkg/clients/middleware/access_logging_middleware.go
new file mode 100644
index 0000000000000000000000000000000000000000..66379ef014588aa0b6bcdb92863e00ba9f428fc5
--- /dev/null
+++ b/pkg/clients/middleware/access_logging_middleware.go
@@ -0,0 +1,167 @@
+// 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/clients -i Clients -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/clients"
+	"go.uber.org/zap"
+)
+
+// accessLoggingMiddleware implements clients.Clients that is instrumented with logging
+type accessLoggingMiddleware struct {
+	logger *zap.Logger
+	next   clients.Clients
+}
+
+// AccessLoggingMiddleware instruments an implementation of the clients.Clients with simple logging
+func AccessLoggingMiddleware(logger *zap.Logger) Middleware {
+	return func(next clients.Clients) clients.Clients {
+		return &accessLoggingMiddleware{
+			next:   next,
+			logger: logger,
+		}
+	}
+}
+
+func (m *accessLoggingMiddleware) Create(ctx context.Context, client *clients.Client) (created *clients.Client, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Create.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("client", client),
+	)
+
+	created, err = m.next.Create(ctx, client)
+
+	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, id string) (err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Delete.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("spaceId", spaceId),
+		zap.Reflect("id", id),
+	)
+
+	err = m.next.Delete(ctx, spaceId, id)
+
+	m.logger.Debug("Delete.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	)
+
+	return err
+}
+
+func (m *accessLoggingMiddleware) Enable(ctx context.Context, spaceId string, id string, enable bool) (err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Enable.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("spaceId", spaceId),
+		zap.Reflect("id", id),
+		zap.Reflect("enable", enable),
+	)
+
+	err = m.next.Enable(ctx, spaceId, id, enable)
+
+	m.logger.Debug("Enable.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	)
+
+	return err
+}
+
+func (m *accessLoggingMiddleware) Get(ctx context.Context, spaceId string, id string) (client *clients.Client, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Get.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("spaceId", spaceId),
+		zap.Reflect("id", id),
+	)
+
+	client, err = m.next.Get(ctx, spaceId, id)
+
+	m.logger.Debug("Get.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("client", client),
+		zap.Error(err),
+	)
+
+	return client, err
+}
+
+func (m *accessLoggingMiddleware) GetBy(ctx context.Context, spaceId string, params *clients.GetByParams) (client *clients.Client, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("GetBy.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("spaceId", spaceId),
+		zap.Reflect("params", params),
+	)
+
+	client, err = m.next.GetBy(ctx, spaceId, params)
+
+	m.logger.Debug("GetBy.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("client", client),
+		zap.Error(err),
+	)
+
+	return client, err
+}
+
+func (m *accessLoggingMiddleware) List(ctx context.Context, spaceId string) (clients []*clients.Client, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("List.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("spaceId", spaceId),
+	)
+
+	clients, err = m.next.List(ctx, spaceId)
+
+	m.logger.Debug("List.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("clients", clients),
+		zap.Error(err),
+	)
+
+	return clients, err
+}
+
+func (m *accessLoggingMiddleware) Update(ctx context.Context, client *clients.Client) (err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Update.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("client", client),
+	)
+
+	err = m.next.Update(ctx, client)
+
+	m.logger.Debug("Update.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	)
+
+	return err
+}
diff --git a/pkg/clients/middleware/error_logging_middleware.go b/pkg/clients/middleware/error_logging_middleware.go
index 0b96827a0c620ad1ca1aa5aaf6b93a821af6279d..3e60cc1d8e49f0712362a7e3e8f072571333a97a 100644
--- a/pkg/clients/middleware/error_logging_middleware.go
+++ b/pkg/clients/middleware/error_logging_middleware.go
@@ -1,9 +1,9 @@
-package middleware
-
 // 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/clients -i Clients -t ../../../assets/templates/middleware/error_log -o error_logging_middleware.go -l ""
 
 import (
diff --git a/pkg/clients/middleware/logging_middleware.go b/pkg/clients/middleware/logging_middleware.go
deleted file mode 100644
index 6fa0e811c5511748b55d756742a2c81789c0cced..0000000000000000000000000000000000000000
--- a/pkg/clients/middleware/logging_middleware.go
+++ /dev/null
@@ -1,295 +0,0 @@
-package middleware
-
-// Code generated by gowrap. DO NOT EDIT.
-// template: ../../../assets/templates/middleware/access_log
-// gowrap: http://github.com/hexdigest/gowrap
-
-//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/clients -i Clients -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/pkg/clients"
-	"go.uber.org/zap"
-	"go.uber.org/zap/zapcore"
-)
-
-// loggingMiddleware implements clients.Clients that is instrumented with logging
-type loggingMiddleware struct {
-	logger *zap.Logger
-	next   clients.Clients
-}
-
-// LoggingMiddleware instruments an implementation of the clients.Clients with simple logging
-func LoggingMiddleware(logger *zap.Logger) Middleware {
-	return func(next clients.Clients) clients.Clients {
-		return &loggingMiddleware{
-			next:   next,
-			logger: logger,
-		}
-	}
-}
-
-func (m *loggingMiddleware) Create(ctx context.Context, client *clients.Client) (created *clients.Client, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":    ctx,
-		"client": client} {
-		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...)
-
-	created, err = m.next.Create(ctx, client)
-
-	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))
-	}
-
-	m.logger.Debug("Create.Response", fields...)
-
-	return created, err
-}
-
-func (m *loggingMiddleware) Delete(ctx context.Context, spaceId string, id string) (err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":     ctx,
-		"spaceId": spaceId,
-		"id":      id} {
-		if k == "ctx" {
-			fields = append(fields, zap.String("principal", fmt.Sprint(auth.GetPrincipal(ctx))))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
-	}
-
-	m.logger.Debug("Delete.Request", fields...)
-
-	err = m.next.Delete(ctx, spaceId, id)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"err": err} {
-		if k == "err" {
-			err, _ := v.(error)
-			fields = append(fields, zap.Error(err))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
-	}
-
-	m.logger.Debug("Delete.Response", fields...)
-
-	return err
-}
-
-func (m *loggingMiddleware) Enable(ctx context.Context, spaceId string, id string, enable bool) (err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":     ctx,
-		"spaceId": spaceId,
-		"id":      id,
-		"enable":  enable} {
-		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("Enable.Request", fields...)
-
-	err = m.next.Enable(ctx, spaceId, id, enable)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"err": err} {
-		if k == "err" {
-			err, _ := v.(error)
-			fields = append(fields, zap.Error(err))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
-	}
-
-	m.logger.Debug("Enable.Response", fields...)
-
-	return err
-}
-
-func (m *loggingMiddleware) Get(ctx context.Context, spaceId string, id string) (client *clients.Client, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":     ctx,
-		"spaceId": spaceId,
-		"id":      id} {
-		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...)
-
-	client, err = m.next.Get(ctx, spaceId, id)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"client": client,
-		"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 client, err
-}
-
-func (m *loggingMiddleware) GetBy(ctx context.Context, spaceId string, params *clients.GetByParams) (client *clients.Client, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":     ctx,
-		"spaceId": spaceId,
-		"params":  params} {
-		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("GetBy.Request", fields...)
-
-	client, err = m.next.GetBy(ctx, spaceId, params)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"client": client,
-		"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("GetBy.Response", fields...)
-
-	return client, err
-}
-
-func (m *loggingMiddleware) List(ctx context.Context, spaceId string) (clients []*clients.Client, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":     ctx,
-		"spaceId": spaceId} {
-		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...)
-
-	clients, err = m.next.List(ctx, spaceId)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"clients": clients,
-		"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("List.Response", fields...)
-
-	return clients, err
-}
-
-func (m *loggingMiddleware) Update(ctx context.Context, client *clients.Client) (err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":    ctx,
-		"client": client} {
-		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...)
-
-	err = m.next.Update(ctx, client)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"err": err} {
-		if k == "err" {
-			err, _ := v.(error)
-			fields = append(fields, zap.Error(err))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
-	}
-
-	m.logger.Debug("Update.Response", fields...)
-
-	return err
-}
diff --git a/pkg/clients/middleware/middleware.go b/pkg/clients/middleware/middleware.go
index a49c9b3ebb0c041c23178d457ea5ddf2d2357d91..0c72c1660e15f147a773e2da5e2f31b469b90260 100644
--- a/pkg/clients/middleware/middleware.go
+++ b/pkg/clients/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/clients -i Clients -t ../../../assets/templates/middleware/middleware -o middleware.go -l ""
+//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/clients -i Clients -t ../../../assets/templates/middleware/middleware.tmpl -o middleware.go -l ""
 
 import (
 	"git.perx.ru/perxis/perxis-go/pkg/clients"
@@ -17,12 +17,12 @@ func WithLog(s clients.Clients, logger *zap.Logger, log_access bool) clients.Cli
 	if logger == nil {
 		logger = zap.NewNop()
 	}
-
 	logger = logger.Named("Clients")
-	s = ErrorLoggingMiddleware(logger)(s)
 	if log_access {
-		s = LoggingMiddleware(logger)(s)
+		s = AccessLoggingMiddleware(logger)(s)
 	}
+	s = ErrorLoggingMiddleware(logger)(s)
+
 	s = RecoveringMiddleware(logger)(s)
 	return s
 }
diff --git a/pkg/collaborators/middleware/access_logging_middleware.go b/pkg/collaborators/middleware/access_logging_middleware.go
new file mode 100644
index 0000000000000000000000000000000000000000..505624ba33f2f91280d3b1dce338262f4a7405d3
--- /dev/null
+++ b/pkg/collaborators/middleware/access_logging_middleware.go
@@ -0,0 +1,129 @@
+// 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/collaborators -i Collaborators -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/collaborators"
+	"go.uber.org/zap"
+)
+
+// accessLoggingMiddleware implements collaborators.Collaborators that is instrumented with logging
+type accessLoggingMiddleware struct {
+	logger *zap.Logger
+	next   collaborators.Collaborators
+}
+
+// AccessLoggingMiddleware instruments an implementation of the collaborators.Collaborators with simple logging
+func AccessLoggingMiddleware(logger *zap.Logger) Middleware {
+	return func(next collaborators.Collaborators) collaborators.Collaborators {
+		return &accessLoggingMiddleware{
+			next:   next,
+			logger: logger,
+		}
+	}
+}
+
+func (m *accessLoggingMiddleware) Get(ctx context.Context, spaceId string, subject string) (role string, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Get.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("spaceId", spaceId),
+		zap.Reflect("subject", subject),
+	)
+
+	role, err = m.next.Get(ctx, spaceId, subject)
+
+	m.logger.Debug("Get.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("role", role),
+		zap.Error(err),
+	)
+
+	return role, err
+}
+
+func (m *accessLoggingMiddleware) ListCollaborators(ctx context.Context, spaceId string) (collaborators []*collaborators.Collaborator, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("ListCollaborators.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("spaceId", spaceId),
+	)
+
+	collaborators, err = m.next.ListCollaborators(ctx, spaceId)
+
+	m.logger.Debug("ListCollaborators.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("collaborators", collaborators),
+		zap.Error(err),
+	)
+
+	return collaborators, err
+}
+
+func (m *accessLoggingMiddleware) ListSpaces(ctx context.Context, subject string) (spaces []*collaborators.Collaborator, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("ListSpaces.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("subject", subject),
+	)
+
+	spaces, err = m.next.ListSpaces(ctx, subject)
+
+	m.logger.Debug("ListSpaces.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("spaces", spaces),
+		zap.Error(err),
+	)
+
+	return spaces, err
+}
+
+func (m *accessLoggingMiddleware) Remove(ctx context.Context, spaceId string, subject string) (err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Remove.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("spaceId", spaceId),
+		zap.Reflect("subject", subject),
+	)
+
+	err = m.next.Remove(ctx, spaceId, subject)
+
+	m.logger.Debug("Remove.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	)
+
+	return err
+}
+
+func (m *accessLoggingMiddleware) Set(ctx context.Context, spaceId string, subject string, role string) (err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Set.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("spaceId", spaceId),
+		zap.Reflect("subject", subject),
+		zap.Reflect("role", role),
+	)
+
+	err = m.next.Set(ctx, spaceId, subject, role)
+
+	m.logger.Debug("Set.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	)
+
+	return err
+}
diff --git a/pkg/collaborators/middleware/error_logging_middleware.go b/pkg/collaborators/middleware/error_logging_middleware.go
index b0bc4518dad30925e41562f334e1891e2c4fb633..6f67ce3ff960815793c8c59d588b012980559334 100644
--- a/pkg/collaborators/middleware/error_logging_middleware.go
+++ b/pkg/collaborators/middleware/error_logging_middleware.go
@@ -1,9 +1,9 @@
-package middleware
-
 // 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/collaborators -i Collaborators -t ../../../assets/templates/middleware/error_log -o error_logging_middleware.go -l ""
 
 import (
diff --git a/pkg/collaborators/middleware/logging_middleware.go b/pkg/collaborators/middleware/logging_middleware.go
deleted file mode 100644
index 4b4569a48b0900027cf1453779913996b5146525..0000000000000000000000000000000000000000
--- a/pkg/collaborators/middleware/logging_middleware.go
+++ /dev/null
@@ -1,221 +0,0 @@
-package middleware
-
-// Code generated by gowrap. DO NOT EDIT.
-// template: ../../../assets/templates/middleware/access_log
-// gowrap: http://github.com/hexdigest/gowrap
-
-//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/collaborators -i Collaborators -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/pkg/collaborators"
-	"go.uber.org/zap"
-	"go.uber.org/zap/zapcore"
-)
-
-// loggingMiddleware implements collaborators.Collaborators that is instrumented with logging
-type loggingMiddleware struct {
-	logger *zap.Logger
-	next   collaborators.Collaborators
-}
-
-// LoggingMiddleware instruments an implementation of the collaborators.Collaborators with simple logging
-func LoggingMiddleware(logger *zap.Logger) Middleware {
-	return func(next collaborators.Collaborators) collaborators.Collaborators {
-		return &loggingMiddleware{
-			next:   next,
-			logger: logger,
-		}
-	}
-}
-
-func (m *loggingMiddleware) Get(ctx context.Context, spaceId string, subject string) (role string, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":     ctx,
-		"spaceId": spaceId,
-		"subject": subject} {
-		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...)
-
-	role, err = m.next.Get(ctx, spaceId, subject)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"role": role,
-		"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 role, err
-}
-
-func (m *loggingMiddleware) ListCollaborators(ctx context.Context, spaceId string) (collaborators []*collaborators.Collaborator, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":     ctx,
-		"spaceId": spaceId} {
-		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("ListCollaborators.Request", fields...)
-
-	collaborators, err = m.next.ListCollaborators(ctx, spaceId)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"collaborators": collaborators,
-		"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("ListCollaborators.Response", fields...)
-
-	return collaborators, err
-}
-
-func (m *loggingMiddleware) ListSpaces(ctx context.Context, subject string) (spaces []*collaborators.Collaborator, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":     ctx,
-		"subject": subject} {
-		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("ListSpaces.Request", fields...)
-
-	spaces, err = m.next.ListSpaces(ctx, subject)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"spaces": spaces,
-		"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("ListSpaces.Response", fields...)
-
-	return spaces, err
-}
-
-func (m *loggingMiddleware) Remove(ctx context.Context, spaceId string, subject string) (err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":     ctx,
-		"spaceId": spaceId,
-		"subject": subject} {
-		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("Remove.Request", fields...)
-
-	err = m.next.Remove(ctx, spaceId, subject)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"err": err} {
-		if k == "err" {
-			err, _ := v.(error)
-			fields = append(fields, zap.Error(err))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
-	}
-
-	m.logger.Debug("Remove.Response", fields...)
-
-	return err
-}
-
-func (m *loggingMiddleware) Set(ctx context.Context, spaceId string, subject string, role string) (err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":     ctx,
-		"spaceId": spaceId,
-		"subject": subject,
-		"role":    role} {
-		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("Set.Request", fields...)
-
-	err = m.next.Set(ctx, spaceId, subject, role)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"err": err} {
-		if k == "err" {
-			err, _ := v.(error)
-			fields = append(fields, zap.Error(err))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
-	}
-
-	m.logger.Debug("Set.Response", fields...)
-
-	return err
-}
diff --git a/pkg/collaborators/middleware/middleware.go b/pkg/collaborators/middleware/middleware.go
index 60931c8494b76be2fe0657ec0d204377e58e0c11..28f0bc687c5ee66f2395303efa502149f8394644 100644
--- a/pkg/collaborators/middleware/middleware.go
+++ b/pkg/collaborators/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/collaborators -i Collaborators -t ../../../assets/templates/middleware/middleware -o middleware.go -l ""
+//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/collaborators -i Collaborators -t ../../../assets/templates/middleware/middleware.tmpl -o middleware.go -l ""
 
 import (
 	"git.perx.ru/perxis/perxis-go/pkg/collaborators"
@@ -17,12 +17,12 @@ func WithLog(s collaborators.Collaborators, logger *zap.Logger, log_access bool)
 	if logger == nil {
 		logger = zap.NewNop()
 	}
-
 	logger = logger.Named("Collaborators")
-	s = ErrorLoggingMiddleware(logger)(s)
 	if log_access {
-		s = LoggingMiddleware(logger)(s)
+		s = AccessLoggingMiddleware(logger)(s)
 	}
+	s = ErrorLoggingMiddleware(logger)(s)
+
 	s = RecoveringMiddleware(logger)(s)
 	return s
 }
diff --git a/pkg/collaborators/middleware/recovering_middleware.go b/pkg/collaborators/middleware/recovering_middleware.go
index 0521cce7a30c9b86cb0fe0a04d058c70a9c48cf4..2217ca1f5827cf228c537223c3181701948b50a5 100644
--- a/pkg/collaborators/middleware/recovering_middleware.go
+++ b/pkg/collaborators/middleware/recovering_middleware.go
@@ -1,5 +1,5 @@
 // Code generated by gowrap. DO NOT EDIT.
-// template: ../../../../assets/templates/middleware/recovery
+// template: ../../../assets/templates/middleware/recovery
 // gowrap: http://github.com/hexdigest/gowrap
 
 package middleware
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..bf04a4aac6e1f5304e982dac0b6c77662715a1cc 100644
--- a/pkg/collections/middleware/logging_middleware.go
+++ b/pkg/collections/middleware/logging_middleware.go
@@ -1,303 +1,135 @@
 package middleware
 
-// Code generated by gowrap. DO NOT EDIT.
-// template: ../../../assets/templates/middleware/access_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/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/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)),
+	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) 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)),
+	if err != nil {
+		logger.Error("Failed to get", zap.Error(err))
+		return
 	}
 
-	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))
-	}
-
-	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)),
-	}
-
-	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 set schema", zap.Error(err), logzap.Channels(logzap.Userlog, logzap.Syslog))
+		return
 	}
 
-	m.logger.Debug("SetSchema.Response", fields...)
-
+	logger.Info("Successfully set schema", 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)),
+	if err != nil {
+		logger.Error("Failed to set state", zap.Error(err))
+		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("SetState.Response", fields...)
-
+	logger.Info("Successfully set state", logzap.Channels(logzap.Userlog))
 	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 187f256c9617d39dd2f157a18c2fd43498dadf36..6961c709da405a34710e76ff6c1ad6f34c218f1b 100644
--- a/pkg/delivery/transport/grpc/protobuf_type_converters.microgen.go
+++ b/pkg/delivery/transport/grpc/protobuf_type_converters.microgen.go
@@ -302,35 +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),
-		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),
-		},
-		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/environments/middleware/access_logging_middleware.go b/pkg/environments/middleware/access_logging_middleware.go
new file mode 100644
index 0000000000000000000000000000000000000000..e357e2ec9786edb9f67a7b14445558f86938e924
--- /dev/null
+++ b/pkg/environments/middleware/access_logging_middleware.go
@@ -0,0 +1,187 @@
+// 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/environments -i Environments -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/environments"
+	"go.uber.org/zap"
+)
+
+// accessLoggingMiddleware implements environments.Environments that is instrumented with logging
+type accessLoggingMiddleware struct {
+	logger *zap.Logger
+	next   environments.Environments
+}
+
+// AccessLoggingMiddleware instruments an implementation of the environments.Environments with simple logging
+func AccessLoggingMiddleware(logger *zap.Logger) Middleware {
+	return func(next environments.Environments) environments.Environments {
+		return &accessLoggingMiddleware{
+			next:   next,
+			logger: logger,
+		}
+	}
+}
+
+func (m *accessLoggingMiddleware) Create(ctx context.Context, env *environments.Environment) (created *environments.Environment, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Create.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("env", env),
+	)
+
+	created, err = m.next.Create(ctx, env)
+
+	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) (err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Delete.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("spaceId", spaceId),
+		zap.Reflect("envId", envId),
+	)
+
+	err = m.next.Delete(ctx, spaceId, envId)
+
+	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) (env *environments.Environment, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Get.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("spaceId", spaceId),
+		zap.Reflect("envId", envId),
+	)
+
+	env, err = m.next.Get(ctx, spaceId, envId)
+
+	m.logger.Debug("Get.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("env", env),
+		zap.Error(err),
+	)
+
+	return env, err
+}
+
+func (m *accessLoggingMiddleware) List(ctx context.Context, spaceId string) (envs []*environments.Environment, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("List.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("spaceId", spaceId),
+	)
+
+	envs, err = m.next.List(ctx, spaceId)
+
+	m.logger.Debug("List.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("envs", envs),
+		zap.Error(err),
+	)
+
+	return envs, err
+}
+
+func (m *accessLoggingMiddleware) Migrate(ctx context.Context, spaceId string, envId string, options ...*environments.MigrateOptions) (err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Migrate.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("spaceId", spaceId),
+		zap.Reflect("envId", envId),
+		zap.Reflect("options", options),
+	)
+
+	err = m.next.Migrate(ctx, spaceId, envId, options...)
+
+	m.logger.Debug("Migrate.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	)
+
+	return err
+}
+
+func (m *accessLoggingMiddleware) RemoveAlias(ctx context.Context, spaceId string, envId string, alias string) (err error) {
+	begin := time.Now()
+
+	m.logger.Debug("RemoveAlias.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("spaceId", spaceId),
+		zap.Reflect("envId", envId),
+		zap.Reflect("alias", alias),
+	)
+
+	err = m.next.RemoveAlias(ctx, spaceId, envId, alias)
+
+	m.logger.Debug("RemoveAlias.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	)
+
+	return err
+}
+
+func (m *accessLoggingMiddleware) SetAlias(ctx context.Context, spaceId string, envId string, alias string) (err error) {
+	begin := time.Now()
+
+	m.logger.Debug("SetAlias.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("spaceId", spaceId),
+		zap.Reflect("envId", envId),
+		zap.Reflect("alias", alias),
+	)
+
+	err = m.next.SetAlias(ctx, spaceId, envId, alias)
+
+	m.logger.Debug("SetAlias.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	)
+
+	return err
+}
+
+func (m *accessLoggingMiddleware) Update(ctx context.Context, env *environments.Environment) (err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Update.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("env", env),
+	)
+
+	err = m.next.Update(ctx, env)
+
+	m.logger.Debug("Update.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	)
+
+	return err
+}
diff --git a/pkg/environments/middleware/error_logging_middleware.go b/pkg/environments/middleware/error_logging_middleware.go
index 91ff984763f696cde9f8c152f96842ae995a411e..812de4033878fe1c6d774730c68d2e6e00489751 100644
--- a/pkg/environments/middleware/error_logging_middleware.go
+++ b/pkg/environments/middleware/error_logging_middleware.go
@@ -1,9 +1,9 @@
-package middleware
-
 // 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/environments -i Environments -t ../../../assets/templates/middleware/error_log -o error_logging_middleware.go -l ""
 
 import (
diff --git a/pkg/environments/middleware/logging_middleware.go b/pkg/environments/middleware/logging_middleware.go
deleted file mode 100644
index 74692e333c12a3230a03b0cf584010745fb52329..0000000000000000000000000000000000000000
--- a/pkg/environments/middleware/logging_middleware.go
+++ /dev/null
@@ -1,333 +0,0 @@
-package middleware
-
-// Code generated by gowrap. DO NOT EDIT.
-// template: ../../../assets/templates/middleware/access_log
-// gowrap: http://github.com/hexdigest/gowrap
-
-//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/environments -i Environments -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/pkg/environments"
-	"go.uber.org/zap"
-	"go.uber.org/zap/zapcore"
-)
-
-// loggingMiddleware implements environments.Environments that is instrumented with logging
-type loggingMiddleware struct {
-	logger *zap.Logger
-	next   environments.Environments
-}
-
-// LoggingMiddleware instruments an implementation of the environments.Environments with simple logging
-func LoggingMiddleware(logger *zap.Logger) Middleware {
-	return func(next environments.Environments) environments.Environments {
-		return &loggingMiddleware{
-			next:   next,
-			logger: logger,
-		}
-	}
-}
-
-func (m *loggingMiddleware) Create(ctx context.Context, env *environments.Environment) (created *environments.Environment, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx": ctx,
-		"env": env} {
-		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...)
-
-	created, err = m.next.Create(ctx, env)
-
-	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))
-	}
-
-	m.logger.Debug("Create.Response", fields...)
-
-	return created, err
-}
-
-func (m *loggingMiddleware) Delete(ctx context.Context, spaceId string, envId string) (err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":     ctx,
-		"spaceId": spaceId,
-		"envId":   envId} {
-		if k == "ctx" {
-			fields = append(fields, zap.String("principal", fmt.Sprint(auth.GetPrincipal(ctx))))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
-	}
-
-	m.logger.Debug("Delete.Request", fields...)
-
-	err = m.next.Delete(ctx, spaceId, envId)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"err": err} {
-		if k == "err" {
-			err, _ := v.(error)
-			fields = append(fields, zap.Error(err))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
-	}
-
-	m.logger.Debug("Delete.Response", fields...)
-
-	return err
-}
-
-func (m *loggingMiddleware) Get(ctx context.Context, spaceId string, envId string) (env *environments.Environment, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":     ctx,
-		"spaceId": spaceId,
-		"envId":   envId} {
-		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...)
-
-	env, err = m.next.Get(ctx, spaceId, envId)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"env": env,
-		"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 env, err
-}
-
-func (m *loggingMiddleware) List(ctx context.Context, spaceId string) (envs []*environments.Environment, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":     ctx,
-		"spaceId": spaceId} {
-		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...)
-
-	envs, err = m.next.List(ctx, spaceId)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"envs": envs,
-		"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("List.Response", fields...)
-
-	return envs, err
-}
-
-func (m *loggingMiddleware) Migrate(ctx context.Context, spaceId string, envId string, options ...*environments.MigrateOptions) (err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":     ctx,
-		"spaceId": spaceId,
-		"envId":   envId,
-		"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("Migrate.Request", fields...)
-
-	err = m.next.Migrate(ctx, spaceId, envId, 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))
-	}
-
-	m.logger.Debug("Migrate.Response", fields...)
-
-	return err
-}
-
-func (m *loggingMiddleware) RemoveAlias(ctx context.Context, spaceId string, envId string, alias string) (err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":     ctx,
-		"spaceId": spaceId,
-		"envId":   envId,
-		"alias":   alias} {
-		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("RemoveAlias.Request", fields...)
-
-	err = m.next.RemoveAlias(ctx, spaceId, envId, alias)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"err": err} {
-		if k == "err" {
-			err, _ := v.(error)
-			fields = append(fields, zap.Error(err))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
-	}
-
-	m.logger.Debug("RemoveAlias.Response", fields...)
-
-	return err
-}
-
-func (m *loggingMiddleware) SetAlias(ctx context.Context, spaceId string, envId string, alias string) (err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":     ctx,
-		"spaceId": spaceId,
-		"envId":   envId,
-		"alias":   alias} {
-		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("SetAlias.Request", fields...)
-
-	err = m.next.SetAlias(ctx, spaceId, envId, alias)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"err": err} {
-		if k == "err" {
-			err, _ := v.(error)
-			fields = append(fields, zap.Error(err))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
-	}
-
-	m.logger.Debug("SetAlias.Response", fields...)
-
-	return err
-}
-
-func (m *loggingMiddleware) Update(ctx context.Context, env *environments.Environment) (err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx": ctx,
-		"env": env} {
-		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...)
-
-	err = m.next.Update(ctx, env)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"err": err} {
-		if k == "err" {
-			err, _ := v.(error)
-			fields = append(fields, zap.Error(err))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
-	}
-
-	m.logger.Debug("Update.Response", fields...)
-
-	return err
-}
diff --git a/pkg/environments/middleware/middleware.go b/pkg/environments/middleware/middleware.go
index cfc89b98b7b17b5335afb81ea3dc5d686a9c892b..7c887417848d7cdea79681b73d7c1954318ad009 100644
--- a/pkg/environments/middleware/middleware.go
+++ b/pkg/environments/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/environments -i Environments -t ../../../assets/templates/middleware/middleware -o middleware.go -l ""
+//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/environments -i Environments -t ../../../assets/templates/middleware/middleware.tmpl -o middleware.go -l ""
 
 import (
 	"git.perx.ru/perxis/perxis-go/pkg/environments"
@@ -17,12 +17,12 @@ func WithLog(s environments.Environments, logger *zap.Logger, log_access bool) e
 	if logger == nil {
 		logger = zap.NewNop()
 	}
-
 	logger = logger.Named("Environments")
-	s = ErrorLoggingMiddleware(logger)(s)
 	if log_access {
-		s = LoggingMiddleware(logger)(s)
+		s = AccessLoggingMiddleware(logger)(s)
 	}
+	s = ErrorLoggingMiddleware(logger)(s)
+
 	s = RecoveringMiddleware(logger)(s)
 	return s
 }
diff --git a/pkg/extension/middleware/access_logging_middleware.go b/pkg/extension/middleware/access_logging_middleware.go
new file mode 100644
index 0000000000000000000000000000000000000000..0fbd63de8991c449fc96ad49527f5e079bd88773
--- /dev/null
+++ b/pkg/extension/middleware/access_logging_middleware.go
@@ -0,0 +1,196 @@
+// 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/extension -i Manager -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/extension"
+	"go.uber.org/zap"
+)
+
+// accessLoggingMiddleware implements extension.Manager that is instrumented with logging
+type accessLoggingMiddleware struct {
+	logger *zap.Logger
+	next   extension.Manager
+}
+
+// AccessLoggingMiddleware instruments an implementation of the extension.Manager with simple logging
+func AccessLoggingMiddleware(logger *zap.Logger) Middleware {
+	return func(next extension.Manager) extension.Manager {
+		return &accessLoggingMiddleware{
+			next:   next,
+			logger: logger,
+		}
+	}
+}
+
+func (m *accessLoggingMiddleware) Action(ctx context.Context, in *extension.ActionRequest) (ap1 *extension.ActionResponse, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Action.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("in", in),
+	)
+
+	ap1, err = m.next.Action(ctx, in)
+
+	m.logger.Debug("Action.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("ap1", ap1),
+		zap.Error(err),
+	)
+
+	return ap1, err
+}
+
+func (m *accessLoggingMiddleware) Check(ctx context.Context, in *extension.CheckRequest) (err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Check.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("in", in),
+	)
+
+	err = m.next.Check(ctx, in)
+
+	m.logger.Debug("Check.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	)
+
+	return err
+}
+
+func (m *accessLoggingMiddleware) GetDescriptor() (ep1 *extension.ExtensionDescriptor) {
+	begin := time.Now()
+
+	m.logger.Debug("GetDescriptor.Request")
+
+	ep1 = m.next.GetDescriptor()
+
+	m.logger.Debug("GetDescriptor.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("ep1", ep1),
+	)
+
+	return ep1
+}
+
+func (m *accessLoggingMiddleware) Install(ctx context.Context, in *extension.InstallRequest) (err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Install.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("in", in),
+	)
+
+	err = m.next.Install(ctx, in)
+
+	m.logger.Debug("Install.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	)
+
+	return err
+}
+
+func (m *accessLoggingMiddleware) ListExtensions(ctx context.Context, space string, env string, filter *extension.ListExtensionsFilter) (ipa1 []*extension.Info, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("ListExtensions.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("space", space),
+		zap.Reflect("env", env),
+		zap.Reflect("filter", filter),
+	)
+
+	ipa1, err = m.next.ListExtensions(ctx, space, env, filter)
+
+	m.logger.Debug("ListExtensions.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("ipa1", ipa1),
+		zap.Error(err),
+	)
+
+	return ipa1, err
+}
+
+func (m *accessLoggingMiddleware) ListRegisteredExtensions(ctx context.Context, extensions ...string) (epa1 []*extension.ExtensionConnector, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("ListRegisteredExtensions.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("extensions", extensions),
+	)
+
+	epa1, err = m.next.ListRegisteredExtensions(ctx, extensions...)
+
+	m.logger.Debug("ListRegisteredExtensions.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("epa1", epa1),
+		zap.Error(err),
+	)
+
+	return epa1, err
+}
+
+func (m *accessLoggingMiddleware) RegisterExtensions(ctx context.Context, ext ...*extension.ExtensionConnector) (err error) {
+	begin := time.Now()
+
+	m.logger.Debug("RegisterExtensions.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("ext", ext),
+	)
+
+	err = m.next.RegisterExtensions(ctx, ext...)
+
+	m.logger.Debug("RegisterExtensions.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	)
+
+	return err
+}
+
+func (m *accessLoggingMiddleware) Uninstall(ctx context.Context, in *extension.UninstallRequest) (err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Uninstall.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("in", in),
+	)
+
+	err = m.next.Uninstall(ctx, in)
+
+	m.logger.Debug("Uninstall.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	)
+
+	return err
+}
+
+func (m *accessLoggingMiddleware) UnregisterExtensions(ctx context.Context, ext ...*extension.ExtensionConnector) (err error) {
+	begin := time.Now()
+
+	m.logger.Debug("UnregisterExtensions.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("ext", ext),
+	)
+
+	err = m.next.UnregisterExtensions(ctx, ext...)
+
+	m.logger.Debug("UnregisterExtensions.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	)
+
+	return err
+}
diff --git a/pkg/extension/middleware/error_logging_middleware.go b/pkg/extension/middleware/error_logging_middleware.go
index e7dec72ba015ffe7e75a7bf0736f3a20cd7f4204..474003790010e858eed9fb9ac2af71f17fb35094 100644
--- a/pkg/extension/middleware/error_logging_middleware.go
+++ b/pkg/extension/middleware/error_logging_middleware.go
@@ -1,9 +1,9 @@
-package middleware
-
 // 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/extension -i Manager -t ../../../assets/templates/middleware/error_log -o error_logging_middleware.go -l ""
 
 import (
diff --git a/pkg/extension/middleware/logging_middleware.go b/pkg/extension/middleware/logging_middleware.go
deleted file mode 100644
index 8ae37929d26a9dbccf40ab1030d73b708d3d96ba..0000000000000000000000000000000000000000
--- a/pkg/extension/middleware/logging_middleware.go
+++ /dev/null
@@ -1,354 +0,0 @@
-package middleware
-
-// Code generated by gowrap. DO NOT EDIT.
-// template: ../../../assets/templates/middleware/access_log
-// gowrap: http://github.com/hexdigest/gowrap
-
-//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/extension -i Manager -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/pkg/extension"
-	"go.uber.org/zap"
-	"go.uber.org/zap/zapcore"
-)
-
-// loggingMiddleware implements extension.Manager that is instrumented with logging
-type loggingMiddleware struct {
-	logger *zap.Logger
-	next   extension.Manager
-}
-
-// LoggingMiddleware instruments an implementation of the extension.Manager with simple logging
-func LoggingMiddleware(logger *zap.Logger) Middleware {
-	return func(next extension.Manager) extension.Manager {
-		return &loggingMiddleware{
-			next:   next,
-			logger: logger,
-		}
-	}
-}
-
-func (m *loggingMiddleware) Action(ctx context.Context, in *extension.ActionRequest) (ap1 *extension.ActionResponse, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx": ctx,
-		"in":  in} {
-		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("Action.Request", fields...)
-
-	ap1, err = m.next.Action(ctx, in)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"ap1": ap1,
-		"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("Action.Response", fields...)
-
-	return ap1, err
-}
-
-func (m *loggingMiddleware) Check(ctx context.Context, in *extension.CheckRequest) (err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx": ctx,
-		"in":  in} {
-		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("Check.Request", fields...)
-
-	err = m.next.Check(ctx, in)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"err": err} {
-		if k == "err" {
-			err, _ := v.(error)
-			fields = append(fields, zap.Error(err))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
-	}
-
-	m.logger.Debug("Check.Response", fields...)
-
-	return err
-}
-
-func (m *loggingMiddleware) GetDescriptor() (ep1 *extension.ExtensionDescriptor) {
-	begin := time.Now()
-	var fields []zapcore.Field
-
-	m.logger.Debug("GetDescriptor.Request", fields...)
-
-	ep1 = m.next.GetDescriptor()
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"ep1": ep1} {
-		if k == "err" {
-			err, _ := v.(error)
-			fields = append(fields, zap.Error(err))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
-	}
-
-	m.logger.Debug("GetDescriptor.Response", fields...)
-
-	return ep1
-}
-
-func (m *loggingMiddleware) Install(ctx context.Context, in *extension.InstallRequest) (err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx": ctx,
-		"in":  in} {
-		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("Install.Request", fields...)
-
-	err = m.next.Install(ctx, in)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"err": err} {
-		if k == "err" {
-			err, _ := v.(error)
-			fields = append(fields, zap.Error(err))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
-	}
-
-	m.logger.Debug("Install.Response", fields...)
-
-	return err
-}
-
-func (m *loggingMiddleware) ListExtensions(ctx context.Context, space string, env string, filter *extension.ListExtensionsFilter) (ipa1 []*extension.Info, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":    ctx,
-		"space":  space,
-		"env":    env,
-		"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("ListExtensions.Request", fields...)
-
-	ipa1, err = m.next.ListExtensions(ctx, space, env, filter)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"ipa1": ipa1,
-		"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("ListExtensions.Response", fields...)
-
-	return ipa1, err
-}
-
-func (m *loggingMiddleware) ListRegisteredExtensions(ctx context.Context, extensions ...string) (epa1 []*extension.ExtensionConnector, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":        ctx,
-		"extensions": extensions} {
-		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("ListRegisteredExtensions.Request", fields...)
-
-	epa1, err = m.next.ListRegisteredExtensions(ctx, extensions...)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"epa1": epa1,
-		"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("ListRegisteredExtensions.Response", fields...)
-
-	return epa1, err
-}
-
-func (m *loggingMiddleware) RegisterExtensions(ctx context.Context, ext ...*extension.ExtensionConnector) (err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx": ctx,
-		"ext": ext} {
-		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("RegisterExtensions.Request", fields...)
-
-	err = m.next.RegisterExtensions(ctx, ext...)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"err": err} {
-		if k == "err" {
-			err, _ := v.(error)
-			fields = append(fields, zap.Error(err))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
-	}
-
-	m.logger.Debug("RegisterExtensions.Response", fields...)
-
-	return err
-}
-
-func (m *loggingMiddleware) Uninstall(ctx context.Context, in *extension.UninstallRequest) (err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx": ctx,
-		"in":  in} {
-		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("Uninstall.Request", fields...)
-
-	err = m.next.Uninstall(ctx, in)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"err": err} {
-		if k == "err" {
-			err, _ := v.(error)
-			fields = append(fields, zap.Error(err))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
-	}
-
-	m.logger.Debug("Uninstall.Response", fields...)
-
-	return err
-}
-
-func (m *loggingMiddleware) UnregisterExtensions(ctx context.Context, ext ...*extension.ExtensionConnector) (err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx": ctx,
-		"ext": ext} {
-		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("UnregisterExtensions.Request", fields...)
-
-	err = m.next.UnregisterExtensions(ctx, ext...)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"err": err} {
-		if k == "err" {
-			err, _ := v.(error)
-			fields = append(fields, zap.Error(err))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
-	}
-
-	m.logger.Debug("UnregisterExtensions.Response", fields...)
-
-	return err
-}
diff --git a/pkg/extension/middleware/middleware.go b/pkg/extension/middleware/middleware.go
index 52b8bff724419d007ece694ade348182a738ce82..f308b6a1d0af97853b6105788cd28977f6c637ac 100644
--- a/pkg/extension/middleware/middleware.go
+++ b/pkg/extension/middleware/middleware.go
@@ -1,10 +1,10 @@
-package middleware
-
 // Code generated by gowrap. DO NOT EDIT.
-// template: ../../../assets/templates/middleware/middleware
+// template: ../../../assets/templates/middleware/middleware.tmpl
 // gowrap: http://github.com/hexdigest/gowrap
 
-//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/extension -i Manager -t ../../../assets/templates/middleware/middleware -o middleware.go -l ""
+package middleware
+
+//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/extension -i Manager -t ../../../assets/templates/middleware/middleware.tmpl -o middleware.go -l ""
 
 import (
 	"git.perx.ru/perxis/perxis-go/pkg/extension"
@@ -17,12 +17,12 @@ func WithLog(s extension.Manager, logger *zap.Logger, log_access bool) extension
 	if logger == nil {
 		logger = zap.NewNop()
 	}
-
 	logger = logger.Named("Manager")
-	s = ErrorLoggingMiddleware(logger)(s)
 	if log_access {
-		s = LoggingMiddleware(logger)(s)
+		s = AccessLoggingMiddleware(logger)(s)
 	}
+	s = ErrorLoggingMiddleware(logger)(s)
+
 	s = RecoveringMiddleware(logger)(s)
 	return s
 }
diff --git a/pkg/extension/middleware/recovering_middleware.go b/pkg/extension/middleware/recovering_middleware.go
index d75af3364a31dd2e0961166a3a7dca573079f4e1..54b78da481917fda0d5614f6d9158c455d4d03e6 100644
--- a/pkg/extension/middleware/recovering_middleware.go
+++ b/pkg/extension/middleware/recovering_middleware.go
@@ -1,9 +1,9 @@
-package middleware
-
 // Code generated by gowrap. DO NOT EDIT.
 // template: ../../../assets/templates/middleware/recovery
 // gowrap: http://github.com/hexdigest/gowrap
 
+package middleware
+
 //go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/extension -i Manager -t ../../../assets/templates/middleware/recovery -o recovering_middleware.go -l ""
 
 import (
diff --git a/pkg/files/middleware/access_logging_middleware.go b/pkg/files/middleware/access_logging_middleware.go
new file mode 100644
index 0000000000000000000000000000000000000000..df932a8282989e17936438b66b714dd9b4b14df6
--- /dev/null
+++ b/pkg/files/middleware/access_logging_middleware.go
@@ -0,0 +1,163 @@
+// 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/files -i Files -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/files"
+	"go.uber.org/zap"
+)
+
+// accessLoggingMiddleware implements files.Files that is instrumented with logging
+type accessLoggingMiddleware struct {
+	logger *zap.Logger
+	next   files.Files
+}
+
+// AccessLoggingMiddleware instruments an implementation of the files.Files with simple logging
+func AccessLoggingMiddleware(logger *zap.Logger) Middleware {
+	return func(next files.Files) files.Files {
+		return &accessLoggingMiddleware{
+			next:   next,
+			logger: logger,
+		}
+	}
+}
+
+func (m *accessLoggingMiddleware) AbortUpload(ctx context.Context, upload *files.MultipartUpload) (err error) {
+	begin := time.Now()
+
+	m.logger.Debug("AbortUpload.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("upload", upload),
+	)
+
+	err = m.next.AbortUpload(ctx, upload)
+
+	m.logger.Debug("AbortUpload.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	)
+
+	return err
+}
+
+func (m *accessLoggingMiddleware) CompleteUpload(ctx context.Context, upload *files.MultipartUpload) (u *files.MultipartUpload, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("CompleteUpload.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("upload", upload),
+	)
+
+	u, err = m.next.CompleteUpload(ctx, upload)
+
+	m.logger.Debug("CompleteUpload.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("u", u),
+		zap.Error(err),
+	)
+
+	return u, err
+}
+
+func (m *accessLoggingMiddleware) DeleteFile(ctx context.Context, file *files.File) (err error) {
+	begin := time.Now()
+
+	m.logger.Debug("DeleteFile.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("file", file),
+	)
+
+	err = m.next.DeleteFile(ctx, file)
+
+	m.logger.Debug("DeleteFile.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	)
+
+	return err
+}
+
+func (m *accessLoggingMiddleware) GetFile(ctx context.Context, file *files.File) (f *files.File, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("GetFile.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("file", file),
+	)
+
+	f, err = m.next.GetFile(ctx, file)
+
+	m.logger.Debug("GetFile.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("f", f),
+		zap.Error(err),
+	)
+
+	return f, err
+}
+
+func (m *accessLoggingMiddleware) MoveUpload(ctx context.Context, upload *files.MultipartUpload) (file *files.File, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("MoveUpload.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("upload", upload),
+	)
+
+	file, err = m.next.MoveUpload(ctx, upload)
+
+	m.logger.Debug("MoveUpload.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("file", file),
+		zap.Error(err),
+	)
+
+	return file, err
+}
+
+func (m *accessLoggingMiddleware) StartUpload(ctx context.Context, upload *files.MultipartUpload) (u *files.MultipartUpload, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("StartUpload.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("upload", upload),
+	)
+
+	u, err = m.next.StartUpload(ctx, upload)
+
+	m.logger.Debug("StartUpload.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("u", u),
+		zap.Error(err),
+	)
+
+	return u, err
+}
+
+func (m *accessLoggingMiddleware) Upload(ctx context.Context, file *files.File) (u *files.Upload, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Upload.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("file", file),
+	)
+
+	u, err = m.next.Upload(ctx, file)
+
+	m.logger.Debug("Upload.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("u", u),
+		zap.Error(err),
+	)
+
+	return u, err
+}
diff --git a/pkg/files/middleware/logging_middleware.go b/pkg/files/middleware/logging_middleware.go
deleted file mode 100644
index b295fa68cc8367021d5fa5e00a0c850cc27c7cee..0000000000000000000000000000000000000000
--- a/pkg/files/middleware/logging_middleware.go
+++ /dev/null
@@ -1,291 +0,0 @@
-// 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/files -i Files -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/pkg/files"
-	"go.uber.org/zap"
-	"go.uber.org/zap/zapcore"
-)
-
-// loggingMiddleware implements files.Files that is instrumented with logging
-type loggingMiddleware struct {
-	logger *zap.Logger
-	next   files.Files
-}
-
-// LoggingMiddleware instruments an implementation of the files.Files with simple logging
-func LoggingMiddleware(logger *zap.Logger) Middleware {
-	return func(next files.Files) files.Files {
-		return &loggingMiddleware{
-			next:   next,
-			logger: logger,
-		}
-	}
-}
-
-func (m *loggingMiddleware) AbortUpload(ctx context.Context, upload *files.MultipartUpload) (err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":    ctx,
-		"upload": upload} {
-		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("AbortUpload.Request", fields...)
-
-	err = m.next.AbortUpload(ctx, upload)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"err": err} {
-		if k == "err" {
-			err, _ := v.(error)
-			fields = append(fields, zap.Error(err))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
-	}
-
-	m.logger.Debug("AbortUpload.Response", fields...)
-
-	return err
-}
-
-func (m *loggingMiddleware) CompleteUpload(ctx context.Context, upload *files.MultipartUpload) (u *files.MultipartUpload, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":    ctx,
-		"upload": upload} {
-		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("CompleteUpload.Request", fields...)
-
-	u, err = m.next.CompleteUpload(ctx, upload)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"u":   u,
-		"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("CompleteUpload.Response", fields...)
-
-	return u, err
-}
-
-func (m *loggingMiddleware) DeleteFile(ctx context.Context, file *files.File) (err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":  ctx,
-		"file": file} {
-		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("DeleteFile.Request", fields...)
-
-	err = m.next.DeleteFile(ctx, file)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"err": err} {
-		if k == "err" {
-			err, _ := v.(error)
-			fields = append(fields, zap.Error(err))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
-	}
-
-	m.logger.Debug("DeleteFile.Response", fields...)
-
-	return err
-}
-
-func (m *loggingMiddleware) GetFile(ctx context.Context, file *files.File) (f *files.File, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":  ctx,
-		"file": file} {
-		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("GetFile.Request", fields...)
-
-	f, err = m.next.GetFile(ctx, file)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"f":   f,
-		"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("GetFile.Response", fields...)
-
-	return f, err
-}
-
-func (m *loggingMiddleware) MoveUpload(ctx context.Context, upload *files.MultipartUpload) (file *files.File, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":    ctx,
-		"upload": upload} {
-		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("MoveUpload.Request", fields...)
-
-	file, err = m.next.MoveUpload(ctx, upload)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"file": file,
-		"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("MoveUpload.Response", fields...)
-
-	return file, err
-}
-
-func (m *loggingMiddleware) StartUpload(ctx context.Context, upload *files.MultipartUpload) (u *files.MultipartUpload, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":    ctx,
-		"upload": upload} {
-		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("StartUpload.Request", fields...)
-
-	u, err = m.next.StartUpload(ctx, upload)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"u":   u,
-		"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("StartUpload.Response", fields...)
-
-	return u, err
-}
-
-func (m *loggingMiddleware) Upload(ctx context.Context, file *files.File) (u *files.Upload, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":  ctx,
-		"file": file} {
-		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("Upload.Request", fields...)
-
-	u, err = m.next.Upload(ctx, file)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"u":   u,
-		"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("Upload.Response", fields...)
-
-	return u, err
-}
diff --git a/pkg/files/middleware/middleware.go b/pkg/files/middleware/middleware.go
index 39c64f8764e904b53fcce18bf8b990424ad03149..68d4c21f67b202f8639963930c71dcd6ab1cc3f9 100644
--- a/pkg/files/middleware/middleware.go
+++ b/pkg/files/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/files -i Files -t ../../../assets/templates/middleware/middleware -o middleware.go -l ""
+//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/files -i Files -t ../../../assets/templates/middleware/middleware.tmpl -o middleware.go -l ""
 
 import (
 	"git.perx.ru/perxis/perxis-go/pkg/files"
@@ -17,12 +17,12 @@ func WithLog(s files.Files, logger *zap.Logger, log_access bool) files.Files {
 	if logger == nil {
 		logger = zap.NewNop()
 	}
-
 	logger = logger.Named("Files")
-	s = ErrorLoggingMiddleware(logger)(s)
 	if log_access {
-		s = LoggingMiddleware(logger)(s)
+		s = AccessLoggingMiddleware(logger)(s)
 	}
+	s = ErrorLoggingMiddleware(logger)(s)
+
 	s = RecoveringMiddleware(logger)(s)
 	return s
 }
diff --git a/pkg/invitations/middleware/access_logging_middleware.go b/pkg/invitations/middleware/access_logging_middleware.go
new file mode 100644
index 0000000000000000000000000000000000000000..24a854995bed29e849ccf873f41ccd1af073cb69
--- /dev/null
+++ b/pkg/invitations/middleware/access_logging_middleware.go
@@ -0,0 +1,129 @@
+// 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/invitations -i Invitations -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/invitations"
+	"git.perx.ru/perxis/perxis-go/pkg/options"
+	"go.uber.org/zap"
+)
+
+// accessLoggingMiddleware implements invitations.Invitations that is instrumented with logging
+type accessLoggingMiddleware struct {
+	logger *zap.Logger
+	next   invitations.Invitations
+}
+
+// AccessLoggingMiddleware instruments an implementation of the invitations.Invitations with simple logging
+func AccessLoggingMiddleware(logger *zap.Logger) Middleware {
+	return func(next invitations.Invitations) invitations.Invitations {
+		return &accessLoggingMiddleware{
+			next:   next,
+			logger: logger,
+		}
+	}
+}
+
+func (m *accessLoggingMiddleware) Accept(ctx context.Context, invitationId string, userId string) (err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Accept.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("invitationId", invitationId),
+		zap.Reflect("userId", userId),
+	)
+
+	err = m.next.Accept(ctx, invitationId, userId)
+
+	m.logger.Debug("Accept.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	)
+
+	return err
+}
+
+func (m *accessLoggingMiddleware) Create(ctx context.Context, invitation *invitations.Invitation) (created *invitations.Invitation, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Create.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("invitation", invitation),
+	)
+
+	created, err = m.next.Create(ctx, invitation)
+
+	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, invitationId string) (err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Delete.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("invitationId", invitationId),
+	)
+
+	err = m.next.Delete(ctx, invitationId)
+
+	m.logger.Debug("Delete.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	)
+
+	return err
+}
+
+func (m *accessLoggingMiddleware) Find(ctx context.Context, filter *invitations.Filter, opts *options.FindOptions) (invitations []*invitations.Invitation, total int, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Find.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("filter", filter),
+		zap.Reflect("opts", opts),
+	)
+
+	invitations, total, err = m.next.Find(ctx, filter, opts)
+
+	m.logger.Debug("Find.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("invitations", invitations),
+		zap.Reflect("total", total),
+		zap.Error(err),
+	)
+
+	return invitations, total, err
+}
+
+func (m *accessLoggingMiddleware) Get(ctx context.Context, invitationId string) (invitation *invitations.Invitation, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Get.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("invitationId", invitationId),
+	)
+
+	invitation, err = m.next.Get(ctx, invitationId)
+
+	m.logger.Debug("Get.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("invitation", invitation),
+		zap.Error(err),
+	)
+
+	return invitation, err
+}
diff --git a/pkg/invitations/middleware/error_logging_middleware.go b/pkg/invitations/middleware/error_logging_middleware.go
index ab0a2122f23c660281716d5dbc697cc6e13de3fe..da6603acdda802808abac634896ceb7d3b92cd1b 100644
--- a/pkg/invitations/middleware/error_logging_middleware.go
+++ b/pkg/invitations/middleware/error_logging_middleware.go
@@ -1,9 +1,9 @@
-package middleware
-
 // 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/invitations -i Invitations -t ../../../assets/templates/middleware/error_log -o error_logging_middleware.go -l ""
 
 import (
diff --git a/pkg/invitations/middleware/logging_middleware.go b/pkg/invitations/middleware/logging_middleware.go
deleted file mode 100644
index ac358b3e1e635bc7f410d5b6a318b4eddc231d38..0000000000000000000000000000000000000000
--- a/pkg/invitations/middleware/logging_middleware.go
+++ /dev/null
@@ -1,221 +0,0 @@
-package middleware
-
-// Code generated by gowrap. DO NOT EDIT.
-// template: ../../../assets/templates/middleware/access_log
-// gowrap: http://github.com/hexdigest/gowrap
-
-//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/invitations -i Invitations -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/pkg/invitations"
-	"git.perx.ru/perxis/perxis-go/pkg/options"
-	"go.uber.org/zap"
-	"go.uber.org/zap/zapcore"
-)
-
-// loggingMiddleware implements invitations.Invitations that is instrumented with logging
-type loggingMiddleware struct {
-	logger *zap.Logger
-	next   invitations.Invitations
-}
-
-// LoggingMiddleware instruments an implementation of the invitations.Invitations with simple logging
-func LoggingMiddleware(logger *zap.Logger) Middleware {
-	return func(next invitations.Invitations) invitations.Invitations {
-		return &loggingMiddleware{
-			next:   next,
-			logger: logger,
-		}
-	}
-}
-
-func (m *loggingMiddleware) Accept(ctx context.Context, invitationId string, userId string) (err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":          ctx,
-		"invitationId": invitationId,
-		"userId":       userId} {
-		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("Accept.Request", fields...)
-
-	err = m.next.Accept(ctx, invitationId, userId)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"err": err} {
-		if k == "err" {
-			err, _ := v.(error)
-			fields = append(fields, zap.Error(err))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
-	}
-
-	m.logger.Debug("Accept.Response", fields...)
-
-	return err
-}
-
-func (m *loggingMiddleware) Create(ctx context.Context, invitation *invitations.Invitation) (created *invitations.Invitation, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":        ctx,
-		"invitation": invitation} {
-		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...)
-
-	created, err = m.next.Create(ctx, invitation)
-
-	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))
-	}
-
-	m.logger.Debug("Create.Response", fields...)
-
-	return created, err
-}
-
-func (m *loggingMiddleware) Delete(ctx context.Context, invitationId string) (err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":          ctx,
-		"invitationId": invitationId} {
-		if k == "ctx" {
-			fields = append(fields, zap.String("principal", fmt.Sprint(auth.GetPrincipal(ctx))))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
-	}
-
-	m.logger.Debug("Delete.Request", fields...)
-
-	err = m.next.Delete(ctx, invitationId)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"err": err} {
-		if k == "err" {
-			err, _ := v.(error)
-			fields = append(fields, zap.Error(err))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
-	}
-
-	m.logger.Debug("Delete.Response", fields...)
-
-	return err
-}
-
-func (m *loggingMiddleware) Find(ctx context.Context, filter *invitations.Filter, opts *options.FindOptions) (invitations []*invitations.Invitation, total int, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":    ctx,
-		"filter": filter,
-		"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("Find.Request", fields...)
-
-	invitations, total, err = m.next.Find(ctx, filter, opts)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"invitations": invitations,
-		"total":       total,
-		"err":         err} {
-		if k == "err" {
-			err, _ := v.(error)
-			fields = append(fields, zap.Error(err))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
-	}
-
-	m.logger.Debug("Find.Response", fields...)
-
-	return invitations, total, err
-}
-
-func (m *loggingMiddleware) Get(ctx context.Context, invitationId string) (invitation *invitations.Invitation, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":          ctx,
-		"invitationId": invitationId} {
-		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...)
-
-	invitation, err = m.next.Get(ctx, invitationId)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"invitation": invitation,
-		"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 invitation, err
-}
diff --git a/pkg/invitations/middleware/middleware.go b/pkg/invitations/middleware/middleware.go
index d4a336ba62cdbef2da5a06e4d71ecdf20491fe49..f59604ba948ade1b4fe03bc3e3cadd8de6f7cfcd 100644
--- a/pkg/invitations/middleware/middleware.go
+++ b/pkg/invitations/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/invitations -i Invitations -t ../../../assets/templates/middleware/middleware -o middleware.go -l ""
+//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/invitations -i Invitations -t ../../../assets/templates/middleware/middleware.tmpl -o middleware.go -l ""
 
 import (
 	"git.perx.ru/perxis/perxis-go/pkg/invitations"
@@ -17,12 +17,12 @@ func WithLog(s invitations.Invitations, logger *zap.Logger, log_access bool) inv
 	if logger == nil {
 		logger = zap.NewNop()
 	}
-
 	logger = logger.Named("Invitations")
-	s = ErrorLoggingMiddleware(logger)(s)
 	if log_access {
-		s = LoggingMiddleware(logger)(s)
+		s = AccessLoggingMiddleware(logger)(s)
 	}
+	s = ErrorLoggingMiddleware(logger)(s)
+
 	s = RecoveringMiddleware(logger)(s)
 	return s
 }
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..d97fe63c69fbc02a946bb03c665d61836368c6dc 100644
--- a/pkg/items/events.go
+++ b/pkg/items/events.go
@@ -12,6 +12,9 @@ const (
 	EventPublishItem   = "publish_item"
 	EventUnpublishItem = "unpublish_item"
 	EventDeleteItem    = "delete_item"
+	EventUndeleteItem  = "item_undelete"
+	EventArchiveItem   = "item_archive"
+	EventUnarchiveItem = "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..fe7870a831d9b90a6145ad20f4c55cb602e8ed98 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.EventArchiveItem),
+		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.EventCreateItem),
+	)
 
 	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.EventDeleteItem),
+		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 find published", 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.EventPublishItem),
+		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.EventUnarchiveItem),
+		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.EventUndeleteItem),
+		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.EventUnpublishItem),
+		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.EventUpdateItem),
+		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..4ef6a1fd2a0e2aec25d759d75d2e40d699ad7e06 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,12 @@ 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 84fac6da574895acf26075dd14ddd1f051c55bb3..c8c4bb16d2da0d2d10e3d98422aaf345ffbf7c21 100644
--- a/pkg/items/pagination.go
+++ b/pkg/items/pagination.go
@@ -16,9 +16,9 @@ type BatchProcessor struct {
 	FindPublishedOptions         *FindPublishedOptions
 	Filter                       *Filter
 
-	pageSize, pageNum int
-	sort              []string
-	processed         int
+	limit     int
+	sort      []string
+	processed int
 }
 
 func (b *BatchProcessor) getBatch(ctx context.Context) ([]*Item, bool, error) {
@@ -37,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 {
@@ -52,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
@@ -84,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 212b1ecd2bfb27db8b49d0f965b484226265f189..ade05bfffeb4a1c745f80338e7faac2287b067a2 100644
--- a/pkg/items/transport/grpc/protobuf_type_converters.microgen.go
+++ b/pkg/items/transport/grpc/protobuf_type_converters.microgen.go
@@ -144,35 +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),
-		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),
-		},
-		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/locales/middleware/access_logging_middleware.go b/pkg/locales/middleware/access_logging_middleware.go
new file mode 100644
index 0000000000000000000000000000000000000000..eabb71c37756d0a7d39d6abe217f394036a69922
--- /dev/null
+++ b/pkg/locales/middleware/access_logging_middleware.go
@@ -0,0 +1,89 @@
+// 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/locales -i Locales -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/locales"
+	"go.uber.org/zap"
+)
+
+// accessLoggingMiddleware implements locales.Locales that is instrumented with logging
+type accessLoggingMiddleware struct {
+	logger *zap.Logger
+	next   locales.Locales
+}
+
+// AccessLoggingMiddleware instruments an implementation of the locales.Locales with simple logging
+func AccessLoggingMiddleware(logger *zap.Logger) Middleware {
+	return func(next locales.Locales) locales.Locales {
+		return &accessLoggingMiddleware{
+			next:   next,
+			logger: logger,
+		}
+	}
+}
+
+func (m *accessLoggingMiddleware) Create(ctx context.Context, locale *locales.Locale) (created *locales.Locale, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Create.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("locale", locale),
+	)
+
+	created, err = m.next.Create(ctx, locale)
+
+	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, localeId string) (err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Delete.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("spaceId", spaceId),
+		zap.Reflect("localeId", localeId),
+	)
+
+	err = m.next.Delete(ctx, spaceId, localeId)
+
+	m.logger.Debug("Delete.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	)
+
+	return err
+}
+
+func (m *accessLoggingMiddleware) List(ctx context.Context, spaceId string) (locales []*locales.Locale, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("List.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("spaceId", spaceId),
+	)
+
+	locales, err = m.next.List(ctx, spaceId)
+
+	m.logger.Debug("List.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("locales", locales),
+		zap.Error(err),
+	)
+
+	return locales, err
+}
diff --git a/pkg/locales/middleware/error_logging_middleware.go b/pkg/locales/middleware/error_logging_middleware.go
index 3251fc7c972217ca4480ecbc26cb65cc055504ea..a00f23a29aa8b5330e1097c670e6cc0edf5b9983 100644
--- a/pkg/locales/middleware/error_logging_middleware.go
+++ b/pkg/locales/middleware/error_logging_middleware.go
@@ -1,9 +1,9 @@
-package middleware
-
 // 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/locales -i Locales -t ../../../assets/templates/middleware/error_log -o error_logging_middleware.go -l ""
 
 import (
diff --git a/pkg/locales/middleware/logging_middleware.go b/pkg/locales/middleware/logging_middleware.go
deleted file mode 100644
index 2db3ef39beb37cabf73adbfb9a3e1122f9e85387..0000000000000000000000000000000000000000
--- a/pkg/locales/middleware/logging_middleware.go
+++ /dev/null
@@ -1,145 +0,0 @@
-package middleware
-
-// Code generated by gowrap. DO NOT EDIT.
-// template: ../../../assets/templates/middleware/access_log
-// gowrap: http://github.com/hexdigest/gowrap
-
-//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/locales -i Locales -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/pkg/locales"
-	"go.uber.org/zap"
-	"go.uber.org/zap/zapcore"
-)
-
-// loggingMiddleware implements locales.Locales that is instrumented with logging
-type loggingMiddleware struct {
-	logger *zap.Logger
-	next   locales.Locales
-}
-
-// LoggingMiddleware instruments an implementation of the locales.Locales with simple logging
-func LoggingMiddleware(logger *zap.Logger) Middleware {
-	return func(next locales.Locales) locales.Locales {
-		return &loggingMiddleware{
-			next:   next,
-			logger: logger,
-		}
-	}
-}
-
-func (m *loggingMiddleware) Create(ctx context.Context, locale *locales.Locale) (created *locales.Locale, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":    ctx,
-		"locale": locale} {
-		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...)
-
-	created, err = m.next.Create(ctx, locale)
-
-	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))
-	}
-
-	m.logger.Debug("Create.Response", fields...)
-
-	return created, err
-}
-
-func (m *loggingMiddleware) Delete(ctx context.Context, spaceId string, localeId string) (err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":      ctx,
-		"spaceId":  spaceId,
-		"localeId": localeId} {
-		if k == "ctx" {
-			fields = append(fields, zap.String("principal", fmt.Sprint(auth.GetPrincipal(ctx))))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
-	}
-
-	m.logger.Debug("Delete.Request", fields...)
-
-	err = m.next.Delete(ctx, spaceId, localeId)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"err": err} {
-		if k == "err" {
-			err, _ := v.(error)
-			fields = append(fields, zap.Error(err))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
-	}
-
-	m.logger.Debug("Delete.Response", fields...)
-
-	return err
-}
-
-func (m *loggingMiddleware) List(ctx context.Context, spaceId string) (locales []*locales.Locale, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":     ctx,
-		"spaceId": spaceId} {
-		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...)
-
-	locales, err = m.next.List(ctx, spaceId)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"locales": locales,
-		"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("List.Response", fields...)
-
-	return locales, err
-}
diff --git a/pkg/locales/middleware/middleware.go b/pkg/locales/middleware/middleware.go
index 721829405e98efc0c47e75f8670f4e74b784998d..2598e397afafb552c16be249cfcf1ed62149339b 100644
--- a/pkg/locales/middleware/middleware.go
+++ b/pkg/locales/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/locales -i Locales -t ../../../assets/templates/middleware/middleware -o middleware.go -l ""
+//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/locales -i Locales -t ../../../assets/templates/middleware/middleware.tmpl -o middleware.go -l ""
 
 import (
 	"git.perx.ru/perxis/perxis-go/pkg/locales"
@@ -17,12 +17,12 @@ func WithLog(s locales.Locales, logger *zap.Logger, log_access bool) locales.Loc
 	if logger == nil {
 		logger = zap.NewNop()
 	}
-
 	logger = logger.Named("Locales")
-	s = ErrorLoggingMiddleware(logger)(s)
 	if log_access {
-		s = LoggingMiddleware(logger)(s)
+		s = AccessLoggingMiddleware(logger)(s)
 	}
+	s = ErrorLoggingMiddleware(logger)(s)
+
 	s = RecoveringMiddleware(logger)(s)
 	return s
 }
diff --git a/pkg/members/middleware/access_logging_middleware.go b/pkg/members/middleware/access_logging_middleware.go
new file mode 100644
index 0000000000000000000000000000000000000000..0a062331a51f9f93d0d7c877637636d3640ef8a7
--- /dev/null
+++ b/pkg/members/middleware/access_logging_middleware.go
@@ -0,0 +1,147 @@
+// 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/members -i Members -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/members"
+	"go.uber.org/zap"
+)
+
+// accessLoggingMiddleware implements members.Members that is instrumented with logging
+type accessLoggingMiddleware struct {
+	logger *zap.Logger
+	next   members.Members
+}
+
+// AccessLoggingMiddleware instruments an implementation of the members.Members with simple logging
+func AccessLoggingMiddleware(logger *zap.Logger) Middleware {
+	return func(next members.Members) members.Members {
+		return &accessLoggingMiddleware{
+			next:   next,
+			logger: logger,
+		}
+	}
+}
+
+func (m *accessLoggingMiddleware) Get(ctx context.Context, orgId string, userId string) (role members.Role, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Get.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("orgId", orgId),
+		zap.Reflect("userId", userId),
+	)
+
+	role, err = m.next.Get(ctx, orgId, userId)
+
+	m.logger.Debug("Get.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("role", role),
+		zap.Error(err),
+	)
+
+	return role, err
+}
+
+func (m *accessLoggingMiddleware) ListMembers(ctx context.Context, orgId string) (members []*members.Member, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("ListMembers.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("orgId", orgId),
+	)
+
+	members, err = m.next.ListMembers(ctx, orgId)
+
+	m.logger.Debug("ListMembers.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("members", members),
+		zap.Error(err),
+	)
+
+	return members, err
+}
+
+func (m *accessLoggingMiddleware) ListOrganizations(ctx context.Context, userId string) (organizations []*members.Member, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("ListOrganizations.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("userId", userId),
+	)
+
+	organizations, err = m.next.ListOrganizations(ctx, userId)
+
+	m.logger.Debug("ListOrganizations.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("organizations", organizations),
+		zap.Error(err),
+	)
+
+	return organizations, err
+}
+
+func (m *accessLoggingMiddleware) Remove(ctx context.Context, orgId string, userId string) (err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Remove.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("orgId", orgId),
+		zap.Reflect("userId", userId),
+	)
+
+	err = m.next.Remove(ctx, orgId, userId)
+
+	m.logger.Debug("Remove.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	)
+
+	return err
+}
+
+func (m *accessLoggingMiddleware) RemoveAll(ctx context.Context, orgId string) (err error) {
+	begin := time.Now()
+
+	m.logger.Debug("RemoveAll.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("orgId", orgId),
+	)
+
+	err = m.next.RemoveAll(ctx, orgId)
+
+	m.logger.Debug("RemoveAll.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	)
+
+	return err
+}
+
+func (m *accessLoggingMiddleware) Set(ctx context.Context, orgId string, userId string, role members.Role) (err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Set.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("orgId", orgId),
+		zap.Reflect("userId", userId),
+		zap.Reflect("role", role),
+	)
+
+	err = m.next.Set(ctx, orgId, userId, role)
+
+	m.logger.Debug("Set.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	)
+
+	return err
+}
diff --git a/pkg/members/middleware/error_logging_middleware.go b/pkg/members/middleware/error_logging_middleware.go
index 79a92a1f2ac6f3d5854fcdf6c1f709e21ee160a7..d3c1a594983ac1bf25a8a397c12580d445b063b6 100644
--- a/pkg/members/middleware/error_logging_middleware.go
+++ b/pkg/members/middleware/error_logging_middleware.go
@@ -1,9 +1,9 @@
-package middleware
-
 // 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/members -i Members -t ../../../assets/templates/middleware/error_log -o error_logging_middleware.go -l ""
 
 import (
diff --git a/pkg/members/middleware/logging_middleware.go b/pkg/members/middleware/logging_middleware.go
deleted file mode 100644
index 3136597a451cc8938c4dc326b01b9e022658293e..0000000000000000000000000000000000000000
--- a/pkg/members/middleware/logging_middleware.go
+++ /dev/null
@@ -1,257 +0,0 @@
-package middleware
-
-// Code generated by gowrap. DO NOT EDIT.
-// template: ../../../assets/templates/middleware/access_log
-// gowrap: http://github.com/hexdigest/gowrap
-
-//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/members -i Members -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/pkg/members"
-	"go.uber.org/zap"
-	"go.uber.org/zap/zapcore"
-)
-
-// loggingMiddleware implements members.Members that is instrumented with logging
-type loggingMiddleware struct {
-	logger *zap.Logger
-	next   members.Members
-}
-
-// LoggingMiddleware instruments an implementation of the members.Members with simple logging
-func LoggingMiddleware(logger *zap.Logger) Middleware {
-	return func(next members.Members) members.Members {
-		return &loggingMiddleware{
-			next:   next,
-			logger: logger,
-		}
-	}
-}
-
-func (m *loggingMiddleware) Get(ctx context.Context, orgId string, userId string) (role members.Role, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":    ctx,
-		"orgId":  orgId,
-		"userId": userId} {
-		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...)
-
-	role, err = m.next.Get(ctx, orgId, userId)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"role": role,
-		"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 role, err
-}
-
-func (m *loggingMiddleware) ListMembers(ctx context.Context, orgId string) (members []*members.Member, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":   ctx,
-		"orgId": orgId} {
-		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("ListMembers.Request", fields...)
-
-	members, err = m.next.ListMembers(ctx, orgId)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"members": members,
-		"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("ListMembers.Response", fields...)
-
-	return members, err
-}
-
-func (m *loggingMiddleware) ListOrganizations(ctx context.Context, userId string) (organizations []*members.Member, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":    ctx,
-		"userId": userId} {
-		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("ListOrganizations.Request", fields...)
-
-	organizations, err = m.next.ListOrganizations(ctx, userId)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"organizations": organizations,
-		"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("ListOrganizations.Response", fields...)
-
-	return organizations, err
-}
-
-func (m *loggingMiddleware) Remove(ctx context.Context, orgId string, userId string) (err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":    ctx,
-		"orgId":  orgId,
-		"userId": userId} {
-		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("Remove.Request", fields...)
-
-	err = m.next.Remove(ctx, orgId, userId)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"err": err} {
-		if k == "err" {
-			err, _ := v.(error)
-			fields = append(fields, zap.Error(err))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
-	}
-
-	m.logger.Debug("Remove.Response", fields...)
-
-	return err
-}
-
-func (m *loggingMiddleware) RemoveAll(ctx context.Context, orgId string) (err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":   ctx,
-		"orgId": orgId} {
-		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("RemoveAll.Request", fields...)
-
-	err = m.next.RemoveAll(ctx, orgId)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"err": err} {
-		if k == "err" {
-			err, _ := v.(error)
-			fields = append(fields, zap.Error(err))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
-	}
-
-	m.logger.Debug("RemoveAll.Response", fields...)
-
-	return err
-}
-
-func (m *loggingMiddleware) Set(ctx context.Context, orgId string, userId string, role members.Role) (err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":    ctx,
-		"orgId":  orgId,
-		"userId": userId,
-		"role":   role} {
-		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("Set.Request", fields...)
-
-	err = m.next.Set(ctx, orgId, userId, role)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"err": err} {
-		if k == "err" {
-			err, _ := v.(error)
-			fields = append(fields, zap.Error(err))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
-	}
-
-	m.logger.Debug("Set.Response", fields...)
-
-	return err
-}
diff --git a/pkg/members/middleware/middleware.go b/pkg/members/middleware/middleware.go
index 517bfa624a35e247d1e93f44db75a5eccaf0c721..bb491623865e21496adedb1e91987bc2205a3ce4 100644
--- a/pkg/members/middleware/middleware.go
+++ b/pkg/members/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/members -i Members -t ../../../assets/templates/middleware/middleware -o middleware.go -l ""
+//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/members -i Members -t ../../../assets/templates/middleware/middleware.tmpl -o middleware.go -l ""
 
 import (
 	"git.perx.ru/perxis/perxis-go/pkg/members"
@@ -17,12 +17,12 @@ func WithLog(s members.Members, logger *zap.Logger, log_access bool) members.Mem
 	if logger == nil {
 		logger = zap.NewNop()
 	}
-
 	logger = logger.Named("Members")
-	s = ErrorLoggingMiddleware(logger)(s)
 	if log_access {
-		s = LoggingMiddleware(logger)(s)
+		s = AccessLoggingMiddleware(logger)(s)
 	}
+	s = ErrorLoggingMiddleware(logger)(s)
+
 	s = RecoveringMiddleware(logger)(s)
 	return s
 }
diff --git a/pkg/members/observer/middleware/access_logging_middleware.go b/pkg/members/observer/middleware/access_logging_middleware.go
new file mode 100644
index 0000000000000000000000000000000000000000..48e9da969978baf7abdad1ae3c9a92582f56d299
--- /dev/null
+++ b/pkg/members/observer/middleware/access_logging_middleware.go
@@ -0,0 +1,52 @@
+// 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/members/observer -i Observer -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/collaborators"
+	"git.perx.ru/perxis/perxis-go/pkg/members/observer"
+	"go.uber.org/zap"
+)
+
+// accessLoggingMiddleware implements observer.Observer that is instrumented with logging
+type accessLoggingMiddleware struct {
+	logger *zap.Logger
+	next   observer.Observer
+}
+
+// AccessLoggingMiddleware instruments an implementation of the observer.Observer with simple logging
+func AccessLoggingMiddleware(logger *zap.Logger) Middleware {
+	return func(next observer.Observer) observer.Observer {
+		return &accessLoggingMiddleware{
+			next:   next,
+			logger: logger,
+		}
+	}
+}
+
+func (m *accessLoggingMiddleware) OnCollaboratorSet(ctx context.Context, collaborator *collaborators.Collaborator) (delayedTaskID string, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("OnCollaboratorSet.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("collaborator", collaborator),
+	)
+
+	delayedTaskID, err = m.next.OnCollaboratorSet(ctx, collaborator)
+
+	m.logger.Debug("OnCollaboratorSet.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("delayedTaskID", delayedTaskID),
+		zap.Error(err),
+	)
+
+	return delayedTaskID, err
+}
diff --git a/pkg/members/observer/middleware/error_logging_middleware.go b/pkg/members/observer/middleware/error_logging_middleware.go
index b7184b475d626a0f4a2a53cd427a672841b38249..462eff251d5aec0648d4ef7e8d60e200896cbe6e 100644
--- a/pkg/members/observer/middleware/error_logging_middleware.go
+++ b/pkg/members/observer/middleware/error_logging_middleware.go
@@ -1,9 +1,9 @@
-package middleware
-
 // 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/members/observer -i Observer -t ../../../../assets/templates/middleware/error_log -o error_logging_middleware.go -l ""
 
 import (
diff --git a/pkg/members/observer/middleware/logging_middleware.go b/pkg/members/observer/middleware/logging_middleware.go
deleted file mode 100644
index 09f203fd21f3a3d2ecabd3279c3d1ca2a323194d..0000000000000000000000000000000000000000
--- a/pkg/members/observer/middleware/logging_middleware.go
+++ /dev/null
@@ -1,72 +0,0 @@
-package middleware
-
-// Code generated by gowrap. DO NOT EDIT.
-// template: ../../../../assets/templates/middleware/access_log
-// gowrap: http://github.com/hexdigest/gowrap
-
-//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/members/observer -i Observer -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/pkg/collaborators"
-	"git.perx.ru/perxis/perxis-go/pkg/members/observer"
-	"go.uber.org/zap"
-	"go.uber.org/zap/zapcore"
-)
-
-// loggingMiddleware implements observer.Observer that is instrumented with logging
-type loggingMiddleware struct {
-	logger *zap.Logger
-	next   observer.Observer
-}
-
-// LoggingMiddleware instruments an implementation of the observer.Observer with simple logging
-func LoggingMiddleware(logger *zap.Logger) Middleware {
-	return func(next observer.Observer) observer.Observer {
-		return &loggingMiddleware{
-			next:   next,
-			logger: logger,
-		}
-	}
-}
-
-func (m *loggingMiddleware) OnCollaboratorSet(ctx context.Context, collaborator *collaborators.Collaborator) (delayedTaskID string, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":          ctx,
-		"collaborator": collaborator} {
-		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("OnCollaboratorSet.Request", fields...)
-
-	delayedTaskID, err = m.next.OnCollaboratorSet(ctx, collaborator)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"delayedTaskID": delayedTaskID,
-		"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("OnCollaboratorSet.Response", fields...)
-
-	return delayedTaskID, err
-}
diff --git a/pkg/members/observer/middleware/middleware.go b/pkg/members/observer/middleware/middleware.go
index f68b58c7398b341f8f3850ddb96538027a58d354..ba2dff4fee8f9ad426d951d518c18e07dc05134e 100644
--- a/pkg/members/observer/middleware/middleware.go
+++ b/pkg/members/observer/middleware/middleware.go
@@ -1,10 +1,10 @@
-package middleware
-
 // Code generated by gowrap. DO NOT EDIT.
-// template: ../../../../assets/templates/middleware/middleware
+// template: ../../../../assets/templates/middleware/middleware.tmpl
 // gowrap: http://github.com/hexdigest/gowrap
 
-//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/members/observer -i Observer -t ../../../../assets/templates/middleware/middleware -o middleware.go -l ""
+package middleware
+
+//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/members/observer -i Observer -t ../../../../assets/templates/middleware/middleware.tmpl -o middleware.go -l ""
 
 import (
 	"git.perx.ru/perxis/perxis-go/pkg/members/observer"
@@ -17,12 +17,12 @@ func WithLog(s observer.Observer, logger *zap.Logger, log_access bool) observer.
 	if logger == nil {
 		logger = zap.NewNop()
 	}
-
 	logger = logger.Named("Observer")
-	s = ErrorLoggingMiddleware(logger)(s)
 	if log_access {
-		s = LoggingMiddleware(logger)(s)
+		s = AccessLoggingMiddleware(logger)(s)
 	}
+	s = ErrorLoggingMiddleware(logger)(s)
+
 	s = RecoveringMiddleware(logger)(s)
 	return s
 }
diff --git a/pkg/members/observer/middleware/recovering_middleware.go b/pkg/members/observer/middleware/recovering_middleware.go
index bd576ecd2699dea927925b935bc024630420f997..f4d96995ded61101fde4a8faabf21da84000f1fb 100644
--- a/pkg/members/observer/middleware/recovering_middleware.go
+++ b/pkg/members/observer/middleware/recovering_middleware.go
@@ -1,9 +1,9 @@
-package middleware
-
 // Code generated by gowrap. DO NOT EDIT.
 // template: ../../../../assets/templates/middleware/recovery
 // gowrap: http://github.com/hexdigest/gowrap
 
+package middleware
+
 //go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/members/observer -i Observer -t ../../../../assets/templates/middleware/recovery -o recovering_middleware.go -l ""
 
 import (
diff --git a/pkg/options/options.go b/pkg/options/options.go
index 7f8b0cd624db7e6c4c35fe62333a67ba633fbe7e..d18ca338c01a7d0e2117a0e09f47d1a21421bdd1 100644
--- a/pkg/options/options.go
+++ b/pkg/options/options.go
@@ -1,6 +1,10 @@
 package options
 
-import "time"
+import (
+	"time"
+
+	commonpb "git.perx.ru/perxis/perxis-go/proto/common"
+)
 
 // SortOptions настройки сортировки результатов
 type SortOptions struct {
@@ -9,8 +13,8 @@ type SortOptions struct {
 
 // PaginationOptions настройки возвращаемых страниц результатов
 type PaginationOptions struct {
-	PageNum  int
-	PageSize int
+	Limit  int
+	Offset int
 }
 
 // FieldOptions настройки включения/исключения полей из результатов запроса
@@ -31,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,
@@ -78,6 +88,46 @@ func MergeFindOptions(opts ...interface{}) *FindOptions {
 	return fo
 }
 
+func FindOptionsFromPB(protoOpts *commonpb.FindOptions) *FindOptions {
+	if protoOpts == nil {
+		return nil
+	}
+	fo := &FindOptions{
+		SortOptions: SortOptions{
+			Sort: protoOpts.Sort,
+		},
+		PaginationOptions: PaginationOptions{
+			Limit:    int(protoOpts.Limit),
+			Offset:   int(protoOpts.Offset),
+		},
+		FieldOptions: FieldOptions{
+			Fields:        protoOpts.Fields,
+			ExcludeFields: protoOpts.ExcludeFields,
+		},
+	}
+
+	if fo.Offset == 0 && fo.Limit == 0 {
+		fo.Offset = int(protoOpts.PageSize * protoOpts.PageNum)
+		fo.Limit = int(protoOpts.PageSize)
+	}
+
+	return fo
+}
+
+func FindOptionsToPB(opts *FindOptions) *commonpb.FindOptions {
+	if opts == nil {
+		return nil
+	}
+	fo := &commonpb.FindOptions{
+		Sort:          opts.Sort,
+		Offset:        int32(opts.Offset),
+		Limit:         int32(opts.Limit),
+		Fields:        opts.Fields,
+		ExcludeFields: opts.ExcludeFields,
+	}
+	return fo
+}
+
 type TimeFilter struct {
 	Before, After time.Time
 }
@@ -98,11 +148,11 @@ func MergeSortOptions(options ...SortOptions) SortOptions {
 func MergePaginationOptions(options ...PaginationOptions) PaginationOptions {
 	fo := PaginationOptions{}
 	for _, opt := range options {
-		if opt.PageSize == 0 && opt.PageNum == 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
 	}
 	return fo
 }
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/middleware/access_logging_middleware.go b/pkg/organizations/middleware/access_logging_middleware.go
new file mode 100644
index 0000000000000000000000000000000000000000..be6e937d1dc76ba9a6d4d80acd5c6343e35eacd7
--- /dev/null
+++ b/pkg/organizations/middleware/access_logging_middleware.go
@@ -0,0 +1,128 @@
+// 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/organizations -i Organizations -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/options"
+	"git.perx.ru/perxis/perxis-go/pkg/organizations"
+	"go.uber.org/zap"
+)
+
+// accessLoggingMiddleware implements organizations.Organizations that is instrumented with logging
+type accessLoggingMiddleware struct {
+	logger *zap.Logger
+	next   organizations.Organizations
+}
+
+// AccessLoggingMiddleware instruments an implementation of the organizations.Organizations with simple logging
+func AccessLoggingMiddleware(logger *zap.Logger) Middleware {
+	return func(next organizations.Organizations) organizations.Organizations {
+		return &accessLoggingMiddleware{
+			next:   next,
+			logger: logger,
+		}
+	}
+}
+
+func (m *accessLoggingMiddleware) Create(ctx context.Context, org *organizations.Organization) (created *organizations.Organization, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Create.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("org", org),
+	)
+
+	created, err = m.next.Create(ctx, org)
+
+	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, orgId string) (err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Delete.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("orgId", orgId),
+	)
+
+	err = m.next.Delete(ctx, orgId)
+
+	m.logger.Debug("Delete.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	)
+
+	return err
+}
+
+func (m *accessLoggingMiddleware) Find(ctx context.Context, filter *organizations.Filter, opts *options.FindOptions) (orgs []*organizations.Organization, total int, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Find.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("filter", filter),
+		zap.Reflect("opts", opts),
+	)
+
+	orgs, total, err = m.next.Find(ctx, filter, opts)
+
+	m.logger.Debug("Find.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("orgs", orgs),
+		zap.Reflect("total", total),
+		zap.Error(err),
+	)
+
+	return orgs, total, err
+}
+
+func (m *accessLoggingMiddleware) Get(ctx context.Context, orgId string) (org *organizations.Organization, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Get.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("orgId", orgId),
+	)
+
+	org, err = m.next.Get(ctx, orgId)
+
+	m.logger.Debug("Get.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("org", org),
+		zap.Error(err),
+	)
+
+	return org, err
+}
+
+func (m *accessLoggingMiddleware) Update(ctx context.Context, org *organizations.Organization) (err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Update.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("org", org),
+	)
+
+	err = m.next.Update(ctx, org)
+
+	m.logger.Debug("Update.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	)
+
+	return err
+}
diff --git a/pkg/organizations/middleware/error_logging_middleware.go b/pkg/organizations/middleware/error_logging_middleware.go
index 1ededdd2a8f391c56909cc33ec799175380a939a..2d6db8198b885e157ba8ebcc855bb6d9dadb519c 100644
--- a/pkg/organizations/middleware/error_logging_middleware.go
+++ b/pkg/organizations/middleware/error_logging_middleware.go
@@ -1,9 +1,9 @@
-package middleware
-
 // 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/organizations -i Organizations -t ../../../assets/templates/middleware/error_log -o error_logging_middleware.go -l ""
 
 import (
diff --git a/pkg/organizations/middleware/logging_middleware.go b/pkg/organizations/middleware/logging_middleware.go
deleted file mode 100644
index b1e6bc3b914ea49d0781d7ae39ddcc5c795fff45..0000000000000000000000000000000000000000
--- a/pkg/organizations/middleware/logging_middleware.go
+++ /dev/null
@@ -1,220 +0,0 @@
-package middleware
-
-// Code generated by gowrap. DO NOT EDIT.
-// template: ../../../assets/templates/middleware/access_log
-// gowrap: http://github.com/hexdigest/gowrap
-
-//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/organizations -i Organizations -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/pkg/options"
-	"git.perx.ru/perxis/perxis-go/pkg/organizations"
-	"go.uber.org/zap"
-	"go.uber.org/zap/zapcore"
-)
-
-// loggingMiddleware implements organizations.Organizations that is instrumented with logging
-type loggingMiddleware struct {
-	logger *zap.Logger
-	next   organizations.Organizations
-}
-
-// LoggingMiddleware instruments an implementation of the organizations.Organizations with simple logging
-func LoggingMiddleware(logger *zap.Logger) Middleware {
-	return func(next organizations.Organizations) organizations.Organizations {
-		return &loggingMiddleware{
-			next:   next,
-			logger: logger,
-		}
-	}
-}
-
-func (m *loggingMiddleware) Create(ctx context.Context, org *organizations.Organization) (created *organizations.Organization, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx": ctx,
-		"org": org} {
-		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...)
-
-	created, err = m.next.Create(ctx, org)
-
-	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))
-	}
-
-	m.logger.Debug("Create.Response", fields...)
-
-	return created, err
-}
-
-func (m *loggingMiddleware) Delete(ctx context.Context, orgId string) (err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":   ctx,
-		"orgId": orgId} {
-		if k == "ctx" {
-			fields = append(fields, zap.String("principal", fmt.Sprint(auth.GetPrincipal(ctx))))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
-	}
-
-	m.logger.Debug("Delete.Request", fields...)
-
-	err = m.next.Delete(ctx, orgId)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"err": err} {
-		if k == "err" {
-			err, _ := v.(error)
-			fields = append(fields, zap.Error(err))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
-	}
-
-	m.logger.Debug("Delete.Response", fields...)
-
-	return err
-}
-
-func (m *loggingMiddleware) Find(ctx context.Context, filter *organizations.Filter, opts *options.FindOptions) (orgs []*organizations.Organization, total int, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":    ctx,
-		"filter": filter,
-		"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("Find.Request", fields...)
-
-	orgs, total, err = m.next.Find(ctx, filter, opts)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"orgs":  orgs,
-		"total": total,
-		"err":   err} {
-		if k == "err" {
-			err, _ := v.(error)
-			fields = append(fields, zap.Error(err))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
-	}
-
-	m.logger.Debug("Find.Response", fields...)
-
-	return orgs, total, err
-}
-
-func (m *loggingMiddleware) Get(ctx context.Context, orgId string) (org *organizations.Organization, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":   ctx,
-		"orgId": orgId} {
-		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...)
-
-	org, err = m.next.Get(ctx, orgId)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"org": org,
-		"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 org, err
-}
-
-func (m *loggingMiddleware) Update(ctx context.Context, org *organizations.Organization) (err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx": ctx,
-		"org": org} {
-		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...)
-
-	err = m.next.Update(ctx, org)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"err": err} {
-		if k == "err" {
-			err, _ := v.(error)
-			fields = append(fields, zap.Error(err))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
-	}
-
-	m.logger.Debug("Update.Response", fields...)
-
-	return err
-}
diff --git a/pkg/organizations/middleware/middleware.go b/pkg/organizations/middleware/middleware.go
index f6cf5c5527ede8c7d285cc280642ca6aa420970c..906a4c356b434fa2926a3e970bf0f32d0c0fa936 100644
--- a/pkg/organizations/middleware/middleware.go
+++ b/pkg/organizations/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/organizations -i Organizations -t ../../../assets/templates/middleware/middleware -o middleware.go -l ""
+//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/organizations -i Organizations -t ../../../assets/templates/middleware/middleware.tmpl -o middleware.go -l ""
 
 import (
 	"git.perx.ru/perxis/perxis-go/pkg/organizations"
@@ -17,12 +17,12 @@ func WithLog(s organizations.Organizations, logger *zap.Logger, log_access bool)
 	if logger == nil {
 		logger = zap.NewNop()
 	}
-
 	logger = logger.Named("Organizations")
-	s = ErrorLoggingMiddleware(logger)(s)
 	if log_access {
-		s = LoggingMiddleware(logger)(s)
+		s = AccessLoggingMiddleware(logger)(s)
 	}
+	s = ErrorLoggingMiddleware(logger)(s)
+
 	s = RecoveringMiddleware(logger)(s)
 	return s
 }
diff --git a/pkg/organizations/transport/grpc/protobuf_type_converters.microgen.go b/pkg/organizations/transport/grpc/protobuf_type_converters.microgen.go
index 15fce8f5a7966f8fa2958fef30f62805923f512b..9a35f86e871d4e9b7792aa31590fd8f1b1fd0e12 100644
--- a/pkg/organizations/transport/grpc/protobuf_type_converters.microgen.go
+++ b/pkg/organizations/transport/grpc/protobuf_type_converters.microgen.go
@@ -61,29 +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),
-	}, 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
 }
 
 func ListPtrOrganizationToProto(orgs []*organizations.Organization) ([]*pb.Organization, error) {
diff --git a/pkg/references/middleware/access_logging_middleware.go b/pkg/references/middleware/access_logging_middleware.go
new file mode 100644
index 0000000000000000000000000000000000000000..c628dc11fccc175b91f0ed7fdc7f180e3a91bfb4
--- /dev/null
+++ b/pkg/references/middleware/access_logging_middleware.go
@@ -0,0 +1,80 @@
+// 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/references -i References -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/references"
+	"go.uber.org/zap"
+)
+
+// accessLoggingMiddleware implements references.References that is instrumented with logging
+type accessLoggingMiddleware struct {
+	logger *zap.Logger
+	next   references.References
+}
+
+// AccessLoggingMiddleware instruments an implementation of the references.References with simple logging
+func AccessLoggingMiddleware(logger *zap.Logger) Middleware {
+	return func(next references.References) references.References {
+		return &accessLoggingMiddleware{
+			next:   next,
+			logger: logger,
+		}
+	}
+}
+
+func (m *accessLoggingMiddleware) Get(ctx context.Context, spaceId string, envId string, references []*references.Reference) (items []*items.Item, notfound []*references.Reference, 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("references", references),
+	)
+
+	items, notfound, err = m.next.Get(ctx, spaceId, envId, references)
+
+	m.logger.Debug("Get.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("items", items),
+		zap.Reflect("notfound", notfound),
+		zap.Error(err),
+	)
+
+	return items, notfound, err
+}
+
+func (m *accessLoggingMiddleware) Publish(ctx context.Context, spaceId string, envId string, references []*references.Reference, recursive bool, force bool) (published []*references.Reference, notfound []*references.Reference, unpublished []*references.Reference, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Publish.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("spaceId", spaceId),
+		zap.Reflect("envId", envId),
+		zap.Reflect("references", references),
+		zap.Reflect("recursive", recursive),
+		zap.Reflect("force", force),
+	)
+
+	published, notfound, unpublished, err = m.next.Publish(ctx, spaceId, envId, references, recursive, force)
+
+	m.logger.Debug("Publish.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("published", published),
+		zap.Reflect("notfound", notfound),
+		zap.Reflect("unpublished", unpublished),
+		zap.Error(err),
+	)
+
+	return published, notfound, unpublished, err
+}
diff --git a/pkg/references/middleware/error_logging_middleware.go b/pkg/references/middleware/error_logging_middleware.go
index 0cfbf919e5acd70dc453494d15e9e8778afcca85..b55b11679d819e42f30234d27f6f140b2d024aa8 100644
--- a/pkg/references/middleware/error_logging_middleware.go
+++ b/pkg/references/middleware/error_logging_middleware.go
@@ -1,10 +1,10 @@
-package middleware
+// Code generated by gowrap. DO NOT EDIT.
+// template: ../../../assets/templates/middleware/error_log
+// gowrap: http://github.com/hexdigest/gowrap
 
-// DO NOT EDIT!
-// This code is generated with http://github.com/hexdigest/gowrap tool
-// using ../../../assets/templates/middleware/error_log template
+package middleware
 
-//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/references -i References -t ../../../assets/templates/middleware/error_log -o error_logging_middleware.go
+//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/references -i References -t ../../../assets/templates/middleware/error_log -o error_logging_middleware.go -l ""
 
 import (
 	"context"
diff --git a/pkg/references/middleware/logging_middleware.go b/pkg/references/middleware/logging_middleware.go
deleted file mode 100644
index a0f010ae99c2f9ce1eb726e5caba316379328a42..0000000000000000000000000000000000000000
--- a/pkg/references/middleware/logging_middleware.go
+++ /dev/null
@@ -1,118 +0,0 @@
-package middleware
-
-// DO NOT EDIT!
-// This code is generated with http://github.com/hexdigest/gowrap tool
-// using ../../../assets/templates/middleware/access_log template
-
-//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/references -i References -t ../../../assets/templates/middleware/access_log -o logging_middleware.go
-
-import (
-	"context"
-	"fmt"
-	"time"
-
-	"git.perx.ru/perxis/perxis-go/pkg/auth"
-	"git.perx.ru/perxis/perxis-go/pkg/items"
-	"git.perx.ru/perxis/perxis-go/pkg/references"
-	"go.uber.org/zap"
-	"go.uber.org/zap/zapcore"
-)
-
-// loggingMiddleware implements references.References that is instrumented with logging
-type loggingMiddleware struct {
-	logger *zap.Logger
-	next   references.References
-}
-
-// LoggingMiddleware instruments an implementation of the references.References with simple logging
-func LoggingMiddleware(logger *zap.Logger) Middleware {
-	return func(next references.References) references.References {
-		return &loggingMiddleware{
-			next:   next,
-			logger: logger,
-		}
-	}
-}
-
-func (m *loggingMiddleware) Get(ctx context.Context, spaceId string, envId string, references []*references.Reference) (items []*items.Item, notfound []*references.Reference, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":        ctx,
-		"spaceId":    spaceId,
-		"envId":      envId,
-		"references": references} {
-		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...)
-
-	items, notfound, err = m.next.Get(ctx, spaceId, envId, references)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"items":    items,
-		"notfound": notfound,
-		"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 items, notfound, err
-}
-
-func (m *loggingMiddleware) Publish(ctx context.Context, spaceId string, envId string, references []*references.Reference, recursive bool, force bool) (published []*references.Reference, notfound []*references.Reference, unpublished []*references.Reference, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":        ctx,
-		"spaceId":    spaceId,
-		"envId":      envId,
-		"references": references,
-		"recursive":  recursive,
-		"force":      force} {
-		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...)
-
-	published, notfound, unpublished, err = m.next.Publish(ctx, spaceId, envId, references, recursive, force)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"published":   published,
-		"notfound":    notfound,
-		"unpublished": unpublished,
-		"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...)
-
-	return published, notfound, unpublished, err
-}
diff --git a/pkg/references/middleware/middleware.go b/pkg/references/middleware/middleware.go
index 6bd830146952e16ccabf780230d03dc6b9473a74..529a61b60265f0f77bc0a4a3be38151f7ba5334e 100644
--- a/pkg/references/middleware/middleware.go
+++ b/pkg/references/middleware/middleware.go
@@ -1,10 +1,10 @@
-package middleware
+// Code generated by gowrap. DO NOT EDIT.
+// template: ../../../assets/templates/middleware/middleware.tmpl
+// gowrap: http://github.com/hexdigest/gowrap
 
-// DO NOT EDIT!
-// This code is generated with http://github.com/hexdigest/gowrap tool
-// using ../../../assets/templates/middleware/middleware template
+package middleware
 
-//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/references -i References -t ../../../assets/templates/middleware/middleware -o middleware.go
+//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/references -i References -t ../../../assets/templates/middleware/middleware.tmpl -o middleware.go -l ""
 
 import (
 	"git.perx.ru/perxis/perxis-go/pkg/references"
@@ -17,12 +17,12 @@ func WithLog(s references.References, logger *zap.Logger, log_access bool) refer
 	if logger == nil {
 		logger = zap.NewNop()
 	}
-
 	logger = logger.Named("References")
-	s = ErrorLoggingMiddleware(logger)(s)
 	if log_access {
-		s = LoggingMiddleware(logger)(s)
+		s = AccessLoggingMiddleware(logger)(s)
 	}
+	s = ErrorLoggingMiddleware(logger)(s)
+
 	s = RecoveringMiddleware(logger)(s)
 	return s
 }
diff --git a/pkg/references/middleware/recovering_middleware.go b/pkg/references/middleware/recovering_middleware.go
index 070d9d22fca08a8394362b8eea6a31628d56902c..1331a96787b8ba98322ab2e4ee5764d296504066 100644
--- a/pkg/references/middleware/recovering_middleware.go
+++ b/pkg/references/middleware/recovering_middleware.go
@@ -1,10 +1,10 @@
-package middleware
+// Code generated by gowrap. DO NOT EDIT.
+// template: ../../../assets/templates/middleware/recovery
+// gowrap: http://github.com/hexdigest/gowrap
 
-// DO NOT EDIT!
-// This code is generated with http://github.com/hexdigest/gowrap tool
-// using ../../../assets/templates/middleware/recovery template
+package middleware
 
-//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/references -i References -t ../../../assets/templates/middleware/recovery -o recovering_middleware.go
+//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/references -i References -t ../../../assets/templates/middleware/recovery -o recovering_middleware.go -l ""
 
 import (
 	"context"
diff --git a/pkg/roles/middleware/access_logging_middleware.go b/pkg/roles/middleware/access_logging_middleware.go
new file mode 100644
index 0000000000000000000000000000000000000000..310cdeda7c585c571263bebb6c42739a8d3074de
--- /dev/null
+++ b/pkg/roles/middleware/access_logging_middleware.go
@@ -0,0 +1,127 @@
+// 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/roles -i Roles -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/roles"
+	"go.uber.org/zap"
+)
+
+// accessLoggingMiddleware implements roles.Roles that is instrumented with logging
+type accessLoggingMiddleware struct {
+	logger *zap.Logger
+	next   roles.Roles
+}
+
+// AccessLoggingMiddleware instruments an implementation of the roles.Roles with simple logging
+func AccessLoggingMiddleware(logger *zap.Logger) Middleware {
+	return func(next roles.Roles) roles.Roles {
+		return &accessLoggingMiddleware{
+			next:   next,
+			logger: logger,
+		}
+	}
+}
+
+func (m *accessLoggingMiddleware) Create(ctx context.Context, role *roles.Role) (created *roles.Role, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Create.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("role", role),
+	)
+
+	created, err = m.next.Create(ctx, role)
+
+	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, roleId string) (err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Delete.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("spaceId", spaceId),
+		zap.Reflect("roleId", roleId),
+	)
+
+	err = m.next.Delete(ctx, spaceId, roleId)
+
+	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, roleId string) (role *roles.Role, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Get.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("spaceId", spaceId),
+		zap.Reflect("roleId", roleId),
+	)
+
+	role, err = m.next.Get(ctx, spaceId, roleId)
+
+	m.logger.Debug("Get.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("role", role),
+		zap.Error(err),
+	)
+
+	return role, err
+}
+
+func (m *accessLoggingMiddleware) List(ctx context.Context, spaceId string) (roles []*roles.Role, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("List.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("spaceId", spaceId),
+	)
+
+	roles, err = m.next.List(ctx, spaceId)
+
+	m.logger.Debug("List.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("roles", roles),
+		zap.Error(err),
+	)
+
+	return roles, err
+}
+
+func (m *accessLoggingMiddleware) Update(ctx context.Context, role *roles.Role) (err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Update.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("role", role),
+	)
+
+	err = m.next.Update(ctx, role)
+
+	m.logger.Debug("Update.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	)
+
+	return err
+}
diff --git a/pkg/roles/middleware/error_logging_middleware.go b/pkg/roles/middleware/error_logging_middleware.go
index 48f12c3d6416ade72e1567c976e78b6237a3d323..bb6073d11fef1d2763ca85855274edf4a8c71839 100644
--- a/pkg/roles/middleware/error_logging_middleware.go
+++ b/pkg/roles/middleware/error_logging_middleware.go
@@ -1,9 +1,9 @@
-package middleware
-
 // 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/roles -i Roles -t ../../../assets/templates/middleware/error_log -o error_logging_middleware.go -l ""
 
 import (
diff --git a/pkg/roles/middleware/logging_middleware.go b/pkg/roles/middleware/logging_middleware.go
deleted file mode 100644
index 31d9d49245bd64eb0e2ba542e54e32f01805d007..0000000000000000000000000000000000000000
--- a/pkg/roles/middleware/logging_middleware.go
+++ /dev/null
@@ -1,219 +0,0 @@
-package middleware
-
-// Code generated by gowrap. DO NOT EDIT.
-// template: ../../../assets/templates/middleware/access_log
-// gowrap: http://github.com/hexdigest/gowrap
-
-//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/roles -i Roles -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/pkg/roles"
-	"go.uber.org/zap"
-	"go.uber.org/zap/zapcore"
-)
-
-// loggingMiddleware implements roles.Roles that is instrumented with logging
-type loggingMiddleware struct {
-	logger *zap.Logger
-	next   roles.Roles
-}
-
-// LoggingMiddleware instruments an implementation of the roles.Roles with simple logging
-func LoggingMiddleware(logger *zap.Logger) Middleware {
-	return func(next roles.Roles) roles.Roles {
-		return &loggingMiddleware{
-			next:   next,
-			logger: logger,
-		}
-	}
-}
-
-func (m *loggingMiddleware) Create(ctx context.Context, role *roles.Role) (created *roles.Role, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":  ctx,
-		"role": role} {
-		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...)
-
-	created, err = m.next.Create(ctx, role)
-
-	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))
-	}
-
-	m.logger.Debug("Create.Response", fields...)
-
-	return created, err
-}
-
-func (m *loggingMiddleware) Delete(ctx context.Context, spaceId string, roleId string) (err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":     ctx,
-		"spaceId": spaceId,
-		"roleId":  roleId} {
-		if k == "ctx" {
-			fields = append(fields, zap.String("principal", fmt.Sprint(auth.GetPrincipal(ctx))))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
-	}
-
-	m.logger.Debug("Delete.Request", fields...)
-
-	err = m.next.Delete(ctx, spaceId, roleId)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"err": err} {
-		if k == "err" {
-			err, _ := v.(error)
-			fields = append(fields, zap.Error(err))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
-	}
-
-	m.logger.Debug("Delete.Response", fields...)
-
-	return err
-}
-
-func (m *loggingMiddleware) Get(ctx context.Context, spaceId string, roleId string) (role *roles.Role, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":     ctx,
-		"spaceId": spaceId,
-		"roleId":  roleId} {
-		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...)
-
-	role, err = m.next.Get(ctx, spaceId, roleId)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"role": role,
-		"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 role, err
-}
-
-func (m *loggingMiddleware) List(ctx context.Context, spaceId string) (roles []*roles.Role, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":     ctx,
-		"spaceId": spaceId} {
-		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...)
-
-	roles, err = m.next.List(ctx, spaceId)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"roles": roles,
-		"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("List.Response", fields...)
-
-	return roles, err
-}
-
-func (m *loggingMiddleware) Update(ctx context.Context, role *roles.Role) (err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":  ctx,
-		"role": role} {
-		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...)
-
-	err = m.next.Update(ctx, role)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"err": err} {
-		if k == "err" {
-			err, _ := v.(error)
-			fields = append(fields, zap.Error(err))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
-	}
-
-	m.logger.Debug("Update.Response", fields...)
-
-	return err
-}
diff --git a/pkg/roles/middleware/middleware.go b/pkg/roles/middleware/middleware.go
index 6b1a8b16e0178a22a57a6f44226aa32c6dacf4eb..aaeb2da895d5aa71768e577315e549daa6a247c4 100644
--- a/pkg/roles/middleware/middleware.go
+++ b/pkg/roles/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/roles -i Roles -t ../../../assets/templates/middleware/middleware -o middleware.go -l ""
+//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/roles -i Roles -t ../../../assets/templates/middleware/middleware.tmpl -o middleware.go -l ""
 
 import (
 	"git.perx.ru/perxis/perxis-go/pkg/roles"
@@ -17,12 +17,12 @@ func WithLog(s roles.Roles, logger *zap.Logger, log_access bool) roles.Roles {
 	if logger == nil {
 		logger = zap.NewNop()
 	}
-
 	logger = logger.Named("Roles")
-	s = ErrorLoggingMiddleware(logger)(s)
 	if log_access {
-		s = LoggingMiddleware(logger)(s)
+		s = AccessLoggingMiddleware(logger)(s)
 	}
+	s = ErrorLoggingMiddleware(logger)(s)
+
 	s = RecoveringMiddleware(logger)(s)
 	return s
 }
diff --git a/pkg/spaces/middleware/access_logging_middleware.go b/pkg/spaces/middleware/access_logging_middleware.go
new file mode 100644
index 0000000000000000000000000000000000000000..7fca51ede8a87ba31ceb7293537e32c2db4c08d7
--- /dev/null
+++ b/pkg/spaces/middleware/access_logging_middleware.go
@@ -0,0 +1,219 @@
+// 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/spaces -i Spaces -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/spaces"
+	"go.uber.org/zap"
+)
+
+// accessLoggingMiddleware implements spaces.Spaces that is instrumented with logging
+type accessLoggingMiddleware struct {
+	logger *zap.Logger
+	next   spaces.Spaces
+}
+
+// AccessLoggingMiddleware instruments an implementation of the spaces.Spaces with simple logging
+func AccessLoggingMiddleware(logger *zap.Logger) Middleware {
+	return func(next spaces.Spaces) spaces.Spaces {
+		return &accessLoggingMiddleware{
+			next:   next,
+			logger: logger,
+		}
+	}
+}
+
+func (m *accessLoggingMiddleware) AbortTransfer(ctx context.Context, spaceID string) (err error) {
+	begin := time.Now()
+
+	m.logger.Debug("AbortTransfer.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("spaceID", spaceID),
+	)
+
+	err = m.next.AbortTransfer(ctx, spaceID)
+
+	m.logger.Debug("AbortTransfer.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	)
+
+	return err
+}
+
+func (m *accessLoggingMiddleware) Create(ctx context.Context, space *spaces.Space) (created *spaces.Space, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Create.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("space", space),
+	)
+
+	created, err = m.next.Create(ctx, space)
+
+	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) (err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Delete.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("spaceId", spaceId),
+	)
+
+	err = m.next.Delete(ctx, spaceId)
+
+	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) (space *spaces.Space, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Get.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("spaceId", spaceId),
+	)
+
+	space, err = m.next.Get(ctx, spaceId)
+
+	m.logger.Debug("Get.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("space", space),
+		zap.Error(err),
+	)
+
+	return space, err
+}
+
+func (m *accessLoggingMiddleware) List(ctx context.Context, orgId string) (spaces []*spaces.Space, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("List.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("orgId", orgId),
+	)
+
+	spaces, err = m.next.List(ctx, orgId)
+
+	m.logger.Debug("List.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("spaces", spaces),
+		zap.Error(err),
+	)
+
+	return spaces, err
+}
+
+func (m *accessLoggingMiddleware) ListTransfers(ctx context.Context, orgID string) (spaces []*spaces.Space, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("ListTransfers.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("orgID", orgID),
+	)
+
+	spaces, err = m.next.ListTransfers(ctx, orgID)
+
+	m.logger.Debug("ListTransfers.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("spaces", spaces),
+		zap.Error(err),
+	)
+
+	return spaces, err
+}
+
+func (m *accessLoggingMiddleware) Move(ctx context.Context, spaceID string, orgID string) (err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Move.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("spaceID", spaceID),
+		zap.Reflect("orgID", orgID),
+	)
+
+	err = m.next.Move(ctx, spaceID, orgID)
+
+	m.logger.Debug("Move.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	)
+
+	return err
+}
+
+func (m *accessLoggingMiddleware) Transfer(ctx context.Context, spaceID string, transferToOrg string) (err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Transfer.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("spaceID", spaceID),
+		zap.Reflect("transferToOrg", transferToOrg),
+	)
+
+	err = m.next.Transfer(ctx, spaceID, transferToOrg)
+
+	m.logger.Debug("Transfer.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	)
+
+	return err
+}
+
+func (m *accessLoggingMiddleware) Update(ctx context.Context, space *spaces.Space) (err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Update.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("space", space),
+	)
+
+	err = m.next.Update(ctx, space)
+
+	m.logger.Debug("Update.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	)
+
+	return err
+}
+
+func (m *accessLoggingMiddleware) UpdateConfig(ctx context.Context, spaceId string, config *spaces.Config) (err error) {
+	begin := time.Now()
+
+	m.logger.Debug("UpdateConfig.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("spaceId", spaceId),
+		zap.Reflect("config", config),
+	)
+
+	err = m.next.UpdateConfig(ctx, spaceId, config)
+
+	m.logger.Debug("UpdateConfig.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	)
+
+	return err
+}
diff --git a/pkg/spaces/middleware/error_logging_middleware.go b/pkg/spaces/middleware/error_logging_middleware.go
index b82cc16f7b7652dcec33bb262346d727c719d390..2b11838b51f1a033e9963a729c6378401e193d0c 100644
--- a/pkg/spaces/middleware/error_logging_middleware.go
+++ b/pkg/spaces/middleware/error_logging_middleware.go
@@ -1,9 +1,9 @@
-package middleware
-
 // 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/spaces -i Spaces -t ../../../assets/templates/middleware/error_log -o error_logging_middleware.go -l ""
 
 import (
diff --git a/pkg/spaces/middleware/logging_middleware.go b/pkg/spaces/middleware/logging_middleware.go
deleted file mode 100644
index 499ed2d23d12737be2af49ec02835956d4b6eb83..0000000000000000000000000000000000000000
--- a/pkg/spaces/middleware/logging_middleware.go
+++ /dev/null
@@ -1,401 +0,0 @@
-package middleware
-
-// Code generated by gowrap. DO NOT EDIT.
-// template: ../../../assets/templates/middleware/access_log
-// gowrap: http://github.com/hexdigest/gowrap
-
-//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/spaces -i Spaces -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/pkg/spaces"
-	"go.uber.org/zap"
-	"go.uber.org/zap/zapcore"
-)
-
-// loggingMiddleware implements spaces.Spaces that is instrumented with logging
-type loggingMiddleware struct {
-	logger *zap.Logger
-	next   spaces.Spaces
-}
-
-// LoggingMiddleware instruments an implementation of the spaces.Spaces with simple logging
-func LoggingMiddleware(logger *zap.Logger) Middleware {
-	return func(next spaces.Spaces) spaces.Spaces {
-		return &loggingMiddleware{
-			next:   next,
-			logger: logger,
-		}
-	}
-}
-
-func (m *loggingMiddleware) AbortTransfer(ctx context.Context, spaceID string) (err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":     ctx,
-		"spaceID": spaceID} {
-		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("AbortTransfer.Request", fields...)
-
-	err = m.next.AbortTransfer(ctx, spaceID)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"err": err} {
-		if k == "err" {
-			err, _ := v.(error)
-			fields = append(fields, zap.Error(err))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
-	}
-
-	m.logger.Debug("AbortTransfer.Response", fields...)
-
-	return err
-}
-
-func (m *loggingMiddleware) Create(ctx context.Context, space *spaces.Space) (created *spaces.Space, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":   ctx,
-		"space": space} {
-		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...)
-
-	created, err = m.next.Create(ctx, space)
-
-	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))
-	}
-
-	m.logger.Debug("Create.Response", fields...)
-
-	return created, err
-}
-
-func (m *loggingMiddleware) Delete(ctx context.Context, spaceId string) (err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":     ctx,
-		"spaceId": spaceId} {
-		if k == "ctx" {
-			fields = append(fields, zap.String("principal", fmt.Sprint(auth.GetPrincipal(ctx))))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
-	}
-
-	m.logger.Debug("Delete.Request", fields...)
-
-	err = m.next.Delete(ctx, spaceId)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"err": err} {
-		if k == "err" {
-			err, _ := v.(error)
-			fields = append(fields, zap.Error(err))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
-	}
-
-	m.logger.Debug("Delete.Response", fields...)
-
-	return err
-}
-
-func (m *loggingMiddleware) Get(ctx context.Context, spaceId string) (space *spaces.Space, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":     ctx,
-		"spaceId": spaceId} {
-		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...)
-
-	space, err = m.next.Get(ctx, spaceId)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"space": space,
-		"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 space, err
-}
-
-func (m *loggingMiddleware) List(ctx context.Context, orgId string) (spaces []*spaces.Space, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":   ctx,
-		"orgId": orgId} {
-		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...)
-
-	spaces, err = m.next.List(ctx, orgId)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"spaces": spaces,
-		"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("List.Response", fields...)
-
-	return spaces, err
-}
-
-func (m *loggingMiddleware) ListTransfers(ctx context.Context, orgID string) (spaces []*spaces.Space, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":   ctx,
-		"orgID": orgID} {
-		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("ListTransfers.Request", fields...)
-
-	spaces, err = m.next.ListTransfers(ctx, orgID)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"spaces": spaces,
-		"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("ListTransfers.Response", fields...)
-
-	return spaces, err
-}
-
-func (m *loggingMiddleware) Move(ctx context.Context, spaceID string, orgID string) (err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":     ctx,
-		"spaceID": spaceID,
-		"orgID":   orgID} {
-		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("Move.Request", fields...)
-
-	err = m.next.Move(ctx, spaceID, orgID)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"err": err} {
-		if k == "err" {
-			err, _ := v.(error)
-			fields = append(fields, zap.Error(err))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
-	}
-
-	m.logger.Debug("Move.Response", fields...)
-
-	return err
-}
-
-func (m *loggingMiddleware) Transfer(ctx context.Context, spaceID string, transferToOrg string) (err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":           ctx,
-		"spaceID":       spaceID,
-		"transferToOrg": transferToOrg} {
-		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("Transfer.Request", fields...)
-
-	err = m.next.Transfer(ctx, spaceID, transferToOrg)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"err": err} {
-		if k == "err" {
-			err, _ := v.(error)
-			fields = append(fields, zap.Error(err))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
-	}
-
-	m.logger.Debug("Transfer.Response", fields...)
-
-	return err
-}
-
-func (m *loggingMiddleware) Update(ctx context.Context, space *spaces.Space) (err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":   ctx,
-		"space": space} {
-		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...)
-
-	err = m.next.Update(ctx, space)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"err": err} {
-		if k == "err" {
-			err, _ := v.(error)
-			fields = append(fields, zap.Error(err))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
-	}
-
-	m.logger.Debug("Update.Response", fields...)
-
-	return err
-}
-
-func (m *loggingMiddleware) UpdateConfig(ctx context.Context, spaceId string, config *spaces.Config) (err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":     ctx,
-		"spaceId": spaceId,
-		"config":  config} {
-		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("UpdateConfig.Request", fields...)
-
-	err = m.next.UpdateConfig(ctx, spaceId, config)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"err": err} {
-		if k == "err" {
-			err, _ := v.(error)
-			fields = append(fields, zap.Error(err))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
-	}
-
-	m.logger.Debug("UpdateConfig.Response", fields...)
-
-	return err
-}
diff --git a/pkg/spaces/middleware/middleware.go b/pkg/spaces/middleware/middleware.go
index 7379b4ae182293f7750504663e59005551e3a63d..73c3b8c3538e6bf9a6457617afc35f68f429aaf3 100644
--- a/pkg/spaces/middleware/middleware.go
+++ b/pkg/spaces/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/spaces -i Spaces -t ../../../assets/templates/middleware/middleware -o middleware.go -l ""
+//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/spaces -i Spaces -t ../../../assets/templates/middleware/middleware.tmpl -o middleware.go -l ""
 
 import (
 	"git.perx.ru/perxis/perxis-go/pkg/spaces"
@@ -17,12 +17,12 @@ func WithLog(s spaces.Spaces, logger *zap.Logger, log_access bool) spaces.Spaces
 	if logger == nil {
 		logger = zap.NewNop()
 	}
-
 	logger = logger.Named("Spaces")
-	s = ErrorLoggingMiddleware(logger)(s)
 	if log_access {
-		s = LoggingMiddleware(logger)(s)
+		s = AccessLoggingMiddleware(logger)(s)
 	}
+	s = ErrorLoggingMiddleware(logger)(s)
+
 	s = RecoveringMiddleware(logger)(s)
 	return s
 }
diff --git a/pkg/spaces/middleware/recovering_middleware.go b/pkg/spaces/middleware/recovering_middleware.go
index ad13a929c8c1ebd9a6132043a00617352af7e6d8..7b9b64ed276aa9a91dd9e07ee1b463ff8bb06003 100644
--- a/pkg/spaces/middleware/recovering_middleware.go
+++ b/pkg/spaces/middleware/recovering_middleware.go
@@ -1,9 +1,9 @@
-package middleware
-
 // Code generated by gowrap. DO NOT EDIT.
 // template: ../../../assets/templates/middleware/recovery
 // gowrap: http://github.com/hexdigest/gowrap
 
+package middleware
+
 //go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/spaces -i Spaces -t ../../../assets/templates/middleware/recovery -o recovering_middleware.go -l ""
 
 import (
diff --git a/pkg/users/middleware/access_logging_middleware.go b/pkg/users/middleware/access_logging_middleware.go
new file mode 100644
index 0000000000000000000000000000000000000000..dd8d4a14c73915d553381b239770fcf05c02aca5
--- /dev/null
+++ b/pkg/users/middleware/access_logging_middleware.go
@@ -0,0 +1,147 @@
+// 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/users -i Users -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/options"
+	"git.perx.ru/perxis/perxis-go/pkg/users"
+	"go.uber.org/zap"
+)
+
+// accessLoggingMiddleware implements users.Users that is instrumented with logging
+type accessLoggingMiddleware struct {
+	logger *zap.Logger
+	next   users.Users
+}
+
+// AccessLoggingMiddleware instruments an implementation of the users.Users with simple logging
+func AccessLoggingMiddleware(logger *zap.Logger) Middleware {
+	return func(next users.Users) users.Users {
+		return &accessLoggingMiddleware{
+			next:   next,
+			logger: logger,
+		}
+	}
+}
+
+func (m *accessLoggingMiddleware) Create(ctx context.Context, create *users.User) (user *users.User, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Create.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("create", create),
+	)
+
+	user, err = m.next.Create(ctx, create)
+
+	m.logger.Debug("Create.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("user", user),
+		zap.Error(err),
+	)
+
+	return user, err
+}
+
+func (m *accessLoggingMiddleware) Delete(ctx context.Context, userId string) (err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Delete.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("userId", userId),
+	)
+
+	err = m.next.Delete(ctx, userId)
+
+	m.logger.Debug("Delete.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	)
+
+	return err
+}
+
+func (m *accessLoggingMiddleware) Find(ctx context.Context, filter *users.Filter, options *options.FindOptions) (users []*users.User, total int, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Find.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("filter", filter),
+		zap.Reflect("options", options),
+	)
+
+	users, total, err = m.next.Find(ctx, filter, options)
+
+	m.logger.Debug("Find.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("users", users),
+		zap.Reflect("total", total),
+		zap.Error(err),
+	)
+
+	return users, total, err
+}
+
+func (m *accessLoggingMiddleware) Get(ctx context.Context, userId string) (user *users.User, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Get.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("userId", userId),
+	)
+
+	user, err = m.next.Get(ctx, userId)
+
+	m.logger.Debug("Get.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("user", user),
+		zap.Error(err),
+	)
+
+	return user, err
+}
+
+func (m *accessLoggingMiddleware) GetByIdentity(ctx context.Context, identity string) (user *users.User, err error) {
+	begin := time.Now()
+
+	m.logger.Debug("GetByIdentity.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("identity", identity),
+	)
+
+	user, err = m.next.GetByIdentity(ctx, identity)
+
+	m.logger.Debug("GetByIdentity.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Reflect("user", user),
+		zap.Error(err),
+	)
+
+	return user, err
+}
+
+func (m *accessLoggingMiddleware) Update(ctx context.Context, update *users.User) (err error) {
+	begin := time.Now()
+
+	m.logger.Debug("Update.Request",
+		zap.Reflect("principal", auth.GetPrincipal(ctx)),
+		zap.Reflect("update", update),
+	)
+
+	err = m.next.Update(ctx, update)
+
+	m.logger.Debug("Update.Response",
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	)
+
+	return err
+}
diff --git a/pkg/users/middleware/error_logging_middleware.go b/pkg/users/middleware/error_logging_middleware.go
index 688083dac93de779db6cdabd02002836901a0883..c499b9f3bf60726a10d30ced82e24bced6702236 100644
--- a/pkg/users/middleware/error_logging_middleware.go
+++ b/pkg/users/middleware/error_logging_middleware.go
@@ -1,9 +1,9 @@
-package middleware
-
 // 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/users -i Users -t ../../../assets/templates/middleware/error_log -o error_logging_middleware.go -l ""
 
 import (
diff --git a/pkg/users/middleware/logging_middleware.go b/pkg/users/middleware/logging_middleware.go
deleted file mode 100644
index 0b9438ed939131c97679c5a3364a1e715be63b75..0000000000000000000000000000000000000000
--- a/pkg/users/middleware/logging_middleware.go
+++ /dev/null
@@ -1,257 +0,0 @@
-package middleware
-
-// Code generated by gowrap. DO NOT EDIT.
-// template: ../../../assets/templates/middleware/access_log
-// gowrap: http://github.com/hexdigest/gowrap
-
-//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/users -i Users -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/pkg/options"
-	"git.perx.ru/perxis/perxis-go/pkg/users"
-	"go.uber.org/zap"
-	"go.uber.org/zap/zapcore"
-)
-
-// loggingMiddleware implements users.Users that is instrumented with logging
-type loggingMiddleware struct {
-	logger *zap.Logger
-	next   users.Users
-}
-
-// LoggingMiddleware instruments an implementation of the users.Users with simple logging
-func LoggingMiddleware(logger *zap.Logger) Middleware {
-	return func(next users.Users) users.Users {
-		return &loggingMiddleware{
-			next:   next,
-			logger: logger,
-		}
-	}
-}
-
-func (m *loggingMiddleware) Create(ctx context.Context, create *users.User) (user *users.User, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":    ctx,
-		"create": create} {
-		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...)
-
-	user, err = m.next.Create(ctx, create)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"user": user,
-		"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...)
-
-	return user, err
-}
-
-func (m *loggingMiddleware) Delete(ctx context.Context, userId string) (err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":    ctx,
-		"userId": userId} {
-		if k == "ctx" {
-			fields = append(fields, zap.String("principal", fmt.Sprint(auth.GetPrincipal(ctx))))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
-	}
-
-	m.logger.Debug("Delete.Request", fields...)
-
-	err = m.next.Delete(ctx, userId)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"err": err} {
-		if k == "err" {
-			err, _ := v.(error)
-			fields = append(fields, zap.Error(err))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
-	}
-
-	m.logger.Debug("Delete.Response", fields...)
-
-	return err
-}
-
-func (m *loggingMiddleware) Find(ctx context.Context, filter *users.Filter, options *options.FindOptions) (users []*users.User, total int, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":     ctx,
-		"filter":  filter,
-		"options": options} {
-		if k == "ctx" {
-			fields = append(fields, zap.String("principal", fmt.Sprint(auth.GetPrincipal(ctx))))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
-	}
-
-	m.logger.Debug("Find.Request", fields...)
-
-	users, total, err = m.next.Find(ctx, filter, options)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"users": users,
-		"total": total,
-		"err":   err} {
-		if k == "err" {
-			err, _ := v.(error)
-			fields = append(fields, zap.Error(err))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
-	}
-
-	m.logger.Debug("Find.Response", fields...)
-
-	return users, total, err
-}
-
-func (m *loggingMiddleware) Get(ctx context.Context, userId string) (user *users.User, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":    ctx,
-		"userId": userId} {
-		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...)
-
-	user, err = m.next.Get(ctx, userId)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"user": user,
-		"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 user, err
-}
-
-func (m *loggingMiddleware) GetByIdentity(ctx context.Context, identity string) (user *users.User, err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":      ctx,
-		"identity": identity} {
-		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("GetByIdentity.Request", fields...)
-
-	user, err = m.next.GetByIdentity(ctx, identity)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"user": user,
-		"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("GetByIdentity.Response", fields...)
-
-	return user, err
-}
-
-func (m *loggingMiddleware) Update(ctx context.Context, update *users.User) (err error) {
-	begin := time.Now()
-	var fields []zapcore.Field
-	for k, v := range map[string]interface{}{
-		"ctx":    ctx,
-		"update": update} {
-		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...)
-
-	err = m.next.Update(ctx, update)
-
-	fields = []zapcore.Field{
-		zap.Duration("time", time.Since(begin)),
-	}
-
-	for k, v := range map[string]interface{}{
-		"err": err} {
-		if k == "err" {
-			err, _ := v.(error)
-			fields = append(fields, zap.Error(err))
-			continue
-		}
-		fields = append(fields, zap.Reflect(k, v))
-	}
-
-	m.logger.Debug("Update.Response", fields...)
-
-	return err
-}
diff --git a/pkg/users/middleware/middleware.go b/pkg/users/middleware/middleware.go
index 63bc862271dc6d7002ea2db479aa359885ad5ebd..d94190827de3028c96000df15bf06c719e30cf92 100644
--- a/pkg/users/middleware/middleware.go
+++ b/pkg/users/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/users -i Users -t ../../../assets/templates/middleware/middleware -o middleware.go -l ""
+//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/users -i Users -t ../../../assets/templates/middleware/middleware.tmpl -o middleware.go -l ""
 
 import (
 	"git.perx.ru/perxis/perxis-go/pkg/users"
@@ -17,12 +17,12 @@ func WithLog(s users.Users, logger *zap.Logger, log_access bool) users.Users {
 	if logger == nil {
 		logger = zap.NewNop()
 	}
-
 	logger = logger.Named("Users")
-	s = ErrorLoggingMiddleware(logger)(s)
 	if log_access {
-		s = LoggingMiddleware(logger)(s)
+		s = AccessLoggingMiddleware(logger)(s)
 	}
+	s = ErrorLoggingMiddleware(logger)(s)
+
 	s = RecoveringMiddleware(logger)(s)
 	return s
 }
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/proto/common/common.pb.go b/proto/common/common.pb.go
index 31926abe8e19b189bc87a67684e2476a43898811..ea585fe3435096d877fa4bbc3649fe96a4b0649f 100644
--- a/proto/common/common.pb.go
+++ b/proto/common/common.pb.go
@@ -194,10 +194,12 @@ type FindOptions struct {
 	unknownFields protoimpl.UnknownFields
 
 	Sort          []string `protobuf:"bytes,1,rep,name=sort,proto3" json:"sort,omitempty"`
-	PageNum       int32    `protobuf:"varint,2,opt,name=page_num,json=pageNum,proto3" json:"page_num,omitempty"`
-	PageSize      int32    `protobuf:"varint,3,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"`
+	PageNum       int32    `protobuf:"varint,2,opt,name=page_num,json=pageNum,proto3" json:"page_num,omitempty"`    // Deprecated
+	PageSize      int32    `protobuf:"varint,3,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` // Deprecated
 	Fields        []string `protobuf:"bytes,4,rep,name=fields,proto3" json:"fields,omitempty"`
 	ExcludeFields bool     `protobuf:"varint,5,opt,name=exclude_fields,json=excludeFields,proto3" json:"exclude_fields,omitempty"`
+	Offset        int32    `protobuf:"varint,6,opt,name=offset,proto3" json:"offset,omitempty"`
+	Limit         int32    `protobuf:"varint,7,opt,name=limit,proto3" json:"limit,omitempty"`
 }
 
 func (x *FindOptions) Reset() {
@@ -267,6 +269,20 @@ func (x *FindOptions) GetExcludeFields() bool {
 	return false
 }
 
+func (x *FindOptions) GetOffset() int32 {
+	if x != nil {
+		return x.Offset
+	}
+	return 0
+}
+
+func (x *FindOptions) GetLimit() int32 {
+	if x != nil {
+		return x.Limit
+	}
+	return 0
+}
+
 type Rule struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -541,7 +557,7 @@ var file_common_common_proto_rawDesc = []byte{
 	0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x2c, 0x0a, 0x05, 0x76,
 	0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x6f, 0x6f,
 	0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x56, 0x61, 0x6c,
-	0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x98, 0x01, 0x0a, 0x0b, 0x46, 0x69,
+	0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0xc6, 0x01, 0x0a, 0x0b, 0x46, 0x69,
 	0x6e, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x6f, 0x72,
 	0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x73, 0x6f, 0x72, 0x74, 0x12, 0x19, 0x0a,
 	0x08, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x6e, 0x75, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52,
@@ -551,59 +567,62 @@ var file_common_common_proto_rawDesc = []byte{
 	0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x12, 0x25, 0x0a,
 	0x0e, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18,
 	0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x46, 0x69,
-	0x65, 0x6c, 0x64, 0x73, 0x22, 0x90, 0x03, 0x0a, 0x04, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x23, 0x0a,
-	0x0d, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01,
-	0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e,
-	0x49, 0x64, 0x12, 0x28, 0x0a, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20,
-	0x03, 0x28, 0x0e, 0x32, 0x0e, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x41, 0x63, 0x74,
-	0x69, 0x6f, 0x6e, 0x52, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x26, 0x0a, 0x06,
-	0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0e, 0x2e, 0x63,
-	0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x52, 0x06, 0x61, 0x63,
-	0x63, 0x65, 0x73, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x68, 0x69, 0x64, 0x64, 0x65, 0x6e, 0x5f, 0x66,
-	0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x68, 0x69, 0x64,
-	0x64, 0x65, 0x6e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x61,
-	0x64, 0x6f, 0x6e, 0x6c, 0x79, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x06, 0x20, 0x03,
-	0x28, 0x09, 0x52, 0x0e, 0x72, 0x65, 0x61, 0x64, 0x6f, 0x6e, 0x6c, 0x79, 0x46, 0x69, 0x65, 0x6c,
-	0x64, 0x73, 0x12, 0x29, 0x0a, 0x10, 0x77, 0x72, 0x69, 0x74, 0x65, 0x6f, 0x6e, 0x6c, 0x79, 0x5f,
-	0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x77, 0x72,
-	0x69, 0x74, 0x65, 0x6f, 0x6e, 0x6c, 0x79, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x12, 0x1f, 0x0a,
-	0x0b, 0x72, 0x65, 0x61, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x08, 0x20, 0x01,
-	0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x61, 0x64, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x21,
-	0x0a, 0x0c, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x09,
-	0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x77, 0x72, 0x69, 0x74, 0x65, 0x46, 0x69, 0x6c, 0x74, 0x65,
-	0x72, 0x12, 0x28, 0x0a, 0x10, 0x64, 0x65, 0x6e, 0x79, 0x5f, 0x72, 0x65, 0x61, 0x64, 0x5f, 0x66,
-	0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x64, 0x65, 0x6e,
-	0x79, 0x52, 0x65, 0x61, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x64,
-	0x65, 0x6e, 0x79, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73,
-	0x18, 0x0b, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x64, 0x65, 0x6e, 0x79, 0x57, 0x72, 0x69, 0x74,
-	0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x22, 0x57, 0x0a, 0x0c, 0x43, 0x6f, 0x6c, 0x6c, 0x61,
-	0x62, 0x6f, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x70, 0x61, 0x63, 0x65,
-	0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x70, 0x61, 0x63, 0x65,
-	0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x02, 0x20,
-	0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x12, 0x0a, 0x04,
-	0x72, 0x6f, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x72, 0x6f, 0x6c, 0x65,
-	0x22, 0xab, 0x01, 0x0a, 0x07, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e,
-	0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01,
-	0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x56, 0x65, 0x72, 0x73,
-	0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69,
-	0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x70, 0x69, 0x56, 0x65, 0x72,
-	0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x74, 0x69,
-	0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x54,
-	0x69, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x18, 0x04, 0x20,
-	0x01, 0x28, 0x09, 0x52, 0x06, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x62,
-	0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28,
-	0x05, 0x52, 0x0b, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x2a, 0x25,
-	0x0a, 0x06, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x4e, 0x59, 0x10,
-	0x00, 0x12, 0x08, 0x0a, 0x04, 0x4d, 0x49, 0x4e, 0x45, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x52,
-	0x4f, 0x4c, 0x45, 0x10, 0x02, 0x2a, 0x43, 0x0a, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12,
-	0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06,
-	0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x52, 0x45, 0x41, 0x44,
-	0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x10, 0x03, 0x12, 0x0a,
-	0x0a, 0x06, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x10, 0x04, 0x42, 0x32, 0x5a, 0x30, 0x67, 0x69,
-	0x74, 0x2e, 0x70, 0x65, 0x72, 0x78, 0x2e, 0x72, 0x75, 0x2f, 0x70, 0x65, 0x72, 0x78, 0x69, 0x73,
-	0x2f, 0x70, 0x65, 0x72, 0x78, 0x69, 0x73, 0x2d, 0x67, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-	0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x3b, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x62, 0x06,
-	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x65, 0x6c, 0x64, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x06,
+	0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x14, 0x0a, 0x05,
+	0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x69, 0x6d,
+	0x69, 0x74, 0x22, 0x90, 0x03, 0x0a, 0x04, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x63,
+	0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
+	0x28, 0x09, 0x52, 0x0c, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64,
+	0x12, 0x28, 0x0a, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28,
+	0x0e, 0x32, 0x0e, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f,
+	0x6e, 0x52, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x26, 0x0a, 0x06, 0x61, 0x63,
+	0x63, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0e, 0x2e, 0x63, 0x6f, 0x6d,
+	0x6d, 0x6f, 0x6e, 0x2e, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x52, 0x06, 0x61, 0x63, 0x63, 0x65,
+	0x73, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x68, 0x69, 0x64, 0x64, 0x65, 0x6e, 0x5f, 0x66, 0x69, 0x65,
+	0x6c, 0x64, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x68, 0x69, 0x64, 0x64, 0x65,
+	0x6e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x61, 0x64, 0x6f,
+	0x6e, 0x6c, 0x79, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09,
+	0x52, 0x0e, 0x72, 0x65, 0x61, 0x64, 0x6f, 0x6e, 0x6c, 0x79, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73,
+	0x12, 0x29, 0x0a, 0x10, 0x77, 0x72, 0x69, 0x74, 0x65, 0x6f, 0x6e, 0x6c, 0x79, 0x5f, 0x66, 0x69,
+	0x65, 0x6c, 0x64, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x77, 0x72, 0x69, 0x74,
+	0x65, 0x6f, 0x6e, 0x6c, 0x79, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x72,
+	0x65, 0x61, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x0a, 0x72, 0x65, 0x61, 0x64, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x21, 0x0a, 0x0c,
+	0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x09, 0x20, 0x01,
+	0x28, 0x09, 0x52, 0x0b, 0x77, 0x72, 0x69, 0x74, 0x65, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12,
+	0x28, 0x0a, 0x10, 0x64, 0x65, 0x6e, 0x79, 0x5f, 0x72, 0x65, 0x61, 0x64, 0x5f, 0x66, 0x69, 0x65,
+	0x6c, 0x64, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x64, 0x65, 0x6e, 0x79, 0x52,
+	0x65, 0x61, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x64, 0x65, 0x6e,
+	0x79, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x0b,
+	0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x64, 0x65, 0x6e, 0x79, 0x57, 0x72, 0x69, 0x74, 0x65, 0x46,
+	0x69, 0x65, 0x6c, 0x64, 0x73, 0x22, 0x57, 0x0a, 0x0c, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x62, 0x6f,
+	0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x69,
+	0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x64,
+	0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28,
+	0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x6f,
+	0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x22, 0xab,
+	0x01, 0x0a, 0x07, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x65,
+	0x72, 0x76, 0x65, 0x72, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01,
+	0x28, 0x09, 0x52, 0x0d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f,
+	0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e,
+	0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x70, 0x69, 0x56, 0x65, 0x72, 0x73, 0x69,
+	0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65,
+	0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x54, 0x69, 0x6d,
+	0x65, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28,
+	0x09, 0x52, 0x06, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x75, 0x69,
+	0x6c, 0x64, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52,
+	0x0b, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x2a, 0x25, 0x0a, 0x06,
+	0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x4e, 0x59, 0x10, 0x00, 0x12,
+	0x08, 0x0a, 0x04, 0x4d, 0x49, 0x4e, 0x45, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x52, 0x4f, 0x4c,
+	0x45, 0x10, 0x02, 0x2a, 0x43, 0x0a, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x0a,
+	0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x52,
+	0x45, 0x41, 0x54, 0x45, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x52, 0x45, 0x41, 0x44, 0x10, 0x02,
+	0x12, 0x0a, 0x0a, 0x06, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x10, 0x03, 0x12, 0x0a, 0x0a, 0x06,
+	0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x10, 0x04, 0x42, 0x32, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x2e,
+	0x70, 0x65, 0x72, 0x78, 0x2e, 0x72, 0x75, 0x2f, 0x70, 0x65, 0x72, 0x78, 0x69, 0x73, 0x2f, 0x70,
+	0x65, 0x72, 0x78, 0x69, 0x73, 0x2d, 0x67, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x63,
+	0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x3b, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x62, 0x06, 0x70, 0x72,
+	0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
diff --git a/proto/logs/log.pb.go b/proto/logs/log.pb.go
new file mode 100644
index 0000000000000000000000000000000000000000..8beabf96d64b409bc99ebf0b6e6ecb64b1ae4aa6
--- /dev/null
+++ b/proto/logs/log.pb.go
@@ -0,0 +1,371 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.31.0
+// 	protoc        v4.25.1
+// source: logs/log.proto
+
+package logs
+
+import (
+	_ "git.perx.ru/perxis/perxis-go/proto/common"
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	anypb "google.golang.org/protobuf/types/known/anypb"
+	timestamppb "google.golang.org/protobuf/types/known/timestamppb"
+	reflect "reflect"
+	sync "sync"
+)
+
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+// LogLevel задает уровень журналирования.
+type LogLevel int32
+
+const (
+	// INFO - обозначает сообщения с нормальным, операционным уровнем журналирования.
+	LogLevel_INFO LogLevel = 0
+	// WARNING - обозначает сообщения, которые содержат потенциально вредные ситуации.
+	LogLevel_WARNING LogLevel = 1
+	// ERROR - обозначает другие ошибки в работе.
+	LogLevel_ERROR LogLevel = 2
+	// CRITICAL - обозначает серьезные ошибки, из-за которых программа может не выполнять некоторые функции.
+	LogLevel_CRITICAL LogLevel = 3
+	// FATAL - обозначает очень серьезные ошибки, которые могут привести к остановке приложения.
+	LogLevel_FATAL LogLevel = 4
+)
+
+// Enum value maps for LogLevel.
+var (
+	LogLevel_name = map[int32]string{
+		0: "INFO",
+		1: "WARNING",
+		2: "ERROR",
+		3: "CRITICAL",
+		4: "FATAL",
+	}
+	LogLevel_value = map[string]int32{
+		"INFO":     0,
+		"WARNING":  1,
+		"ERROR":    2,
+		"CRITICAL": 3,
+		"FATAL":    4,
+	}
+)
+
+func (x LogLevel) Enum() *LogLevel {
+	p := new(LogLevel)
+	*p = x
+	return p
+}
+
+func (x LogLevel) String() string {
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (LogLevel) Descriptor() protoreflect.EnumDescriptor {
+	return file_logs_log_proto_enumTypes[0].Descriptor()
+}
+
+func (LogLevel) Type() protoreflect.EnumType {
+	return &file_logs_log_proto_enumTypes[0]
+}
+
+func (x LogLevel) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use LogLevel.Descriptor instead.
+func (LogLevel) EnumDescriptor() ([]byte, []int) {
+	return file_logs_log_proto_rawDescGZIP(), []int{0}
+}
+
+// LogEntry представляет собой структуру данных для хранения информации о журнале.
+type LogEntry struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// id является уникальным идентификатором каждой записи в журнале.
+	Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
+	// timestamp указывает на временную метку, указывающую когда было создано данное сообщение.
+	Timestamp *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
+	Level     LogLevel               `protobuf:"varint,3,opt,name=level,proto3,enum=logs.LogLevel" json:"level,omitempty"` // message это основное сообщение, которое требуется записать в лог.
+	Message   string                 `protobuf:"bytes,4,opt,name=message,proto3" json:"message,omitempty"`
+	// category указывает на категорию события.
+	// Примеры:
+	// -
+	Category  string `protobuf:"bytes,5,opt,name=category,proto3" json:"category,omitempty"`
+	Component string `protobuf:"bytes,6,opt,name=component,proto3" json:"component,omitempty"`
+	// action описывает действие, которое было произведено. Это поле может принимать разные значения в зависимости от сервиса.
+	// Примеры:
+	// - item.create
+	// - item.update
+	// - organization.create
+	// - action.run
+	// - reference.create
+	Event string `protobuf:"bytes,7,opt,name=event,proto3" json:"event,omitempty"`
+	// object это идентификатор объекта связанного с событием
+	// Идентификатор объекта должен быть в формате GlobalID:
+	// <контекст>/<тип объекта>/<идентификатор объекта>
+	// РіРґРµ:
+	//   - <контекст> - представляет собой иднетификатор родительского объекта, если таковой имеется
+	//   - <тип объекта> - представляет собой тип объекта, например:
+	//     spaces, envs, cols, items, revs, fields, clients, roles, orgs, users
+	//   - <идентификатор объекта> - представляет собой идентификатор объекта
+	//
+	// Примеры:
+	// /spaces/<space_id> - пространство
+	// /spaces/<space_id>/envs/<env_id> - окружение
+	// /spaces/<space_id>/envs/<env_id>/cols/<collection_id> - коллекция
+	// /spaces/<space_id>/cols/<collection_id> - коллекция в окружении "master"
+	// /spaces/<space_id>/envs/<env_id>/schema/<collection_id> - схема коллекции
+	// /spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id> - элемент коллекции
+	// /spaces/<space_id>/cols/<collection_id>/items/<item_id> - элемент коллекции в окружении "master"
+	// /spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>/fields/<field_name> - поле элемента коллекции
+	// /spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>/revs/<rev_id> - ревизия элемента коллекции
+	// /spaces/<space_id>/clients/<client_id> - клиент
+	// /spaces/<space_id>/roles/<role_id> - роль
+	// /orgs/<org_id> - организация
+	// /users/<user_id> - пользователь
+	// /services/<service_id> - сервис
+	ObjectId string `protobuf:"bytes,8,opt,name=object_id,json=objectId,proto3" json:"object_id,omitempty"`
+	// caller содержит идентификатор сущности вызвавшей событиe, аналогично полю object
+	//
+	// Примеры:
+	// /users/<user_id> - пользователь
+	// /spaces/<space_id>/clients/<client_id> - клиент
+	// /services/<service_id> - сервис
+	CallerId string `protobuf:"bytes,9,opt,name=caller_id,json=callerId,proto3" json:"caller_id,omitempty"`
+	// attr содержит дополнительные связанные с событием атрибуты в формате Any
+	// позволяет добавить дополнительные данные в событие
+	Attr *anypb.Any `protobuf:"bytes,10,opt,name=attr,proto3" json:"attr,omitempty"`
+	// tags содержит теги связанные с событием, на усмотрение сервиса
+	Tags []string `protobuf:"bytes,11,rep,name=tags,proto3" json:"tags,omitempty"`
+}
+
+func (x *LogEntry) Reset() {
+	*x = LogEntry{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_logs_log_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *LogEntry) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*LogEntry) ProtoMessage() {}
+
+func (x *LogEntry) ProtoReflect() protoreflect.Message {
+	mi := &file_logs_log_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use LogEntry.ProtoReflect.Descriptor instead.
+func (*LogEntry) Descriptor() ([]byte, []int) {
+	return file_logs_log_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *LogEntry) GetId() string {
+	if x != nil {
+		return x.Id
+	}
+	return ""
+}
+
+func (x *LogEntry) GetTimestamp() *timestamppb.Timestamp {
+	if x != nil {
+		return x.Timestamp
+	}
+	return nil
+}
+
+func (x *LogEntry) GetLevel() LogLevel {
+	if x != nil {
+		return x.Level
+	}
+	return LogLevel_INFO
+}
+
+func (x *LogEntry) GetMessage() string {
+	if x != nil {
+		return x.Message
+	}
+	return ""
+}
+
+func (x *LogEntry) GetCategory() string {
+	if x != nil {
+		return x.Category
+	}
+	return ""
+}
+
+func (x *LogEntry) GetComponent() string {
+	if x != nil {
+		return x.Component
+	}
+	return ""
+}
+
+func (x *LogEntry) GetEvent() string {
+	if x != nil {
+		return x.Event
+	}
+	return ""
+}
+
+func (x *LogEntry) GetObjectId() string {
+	if x != nil {
+		return x.ObjectId
+	}
+	return ""
+}
+
+func (x *LogEntry) GetCallerId() string {
+	if x != nil {
+		return x.CallerId
+	}
+	return ""
+}
+
+func (x *LogEntry) GetAttr() *anypb.Any {
+	if x != nil {
+		return x.Attr
+	}
+	return nil
+}
+
+func (x *LogEntry) GetTags() []string {
+	if x != nil {
+		return x.Tags
+	}
+	return nil
+}
+
+var File_logs_log_proto protoreflect.FileDescriptor
+
+var file_logs_log_proto_rawDesc = []byte{
+	0x0a, 0x0e, 0x6c, 0x6f, 0x67, 0x73, 0x2f, 0x6c, 0x6f, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x12, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70,
+	0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d,
+	0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f,
+	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f,
+	0x74, 0x6f, 0x1a, 0x12, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x65, 0x72, 0x72, 0x6f, 0x72,
+	0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xdc, 0x02, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x45, 0x6e,
+	0x74, 0x72, 0x79, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x02, 0x69, 0x64, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70,
+	0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
+	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,
+	0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x24, 0x0a,
+	0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0e, 0x2e, 0x6c,
+	0x6f, 0x67, 0x73, 0x2e, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x05, 0x6c, 0x65,
+	0x76, 0x65, 0x6c, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x04,
+	0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1a, 0x0a,
+	0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6d,
+	0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f,
+	0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74,
+	0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1b, 0x0a,
+	0x09, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x08, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x61,
+	0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63,
+	0x61, 0x6c, 0x6c, 0x65, 0x72, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x04, 0x61, 0x74, 0x74, 0x72, 0x18,
+	0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
+	0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x04, 0x61, 0x74, 0x74,
+	0x72, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x61, 0x67, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x09, 0x52,
+	0x04, 0x74, 0x61, 0x67, 0x73, 0x2a, 0x45, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65,
+	0x6c, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x46, 0x4f, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x57,
+	0x41, 0x52, 0x4e, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f,
+	0x52, 0x10, 0x02, 0x12, 0x0c, 0x0a, 0x08, 0x43, 0x52, 0x49, 0x54, 0x49, 0x43, 0x41, 0x4c, 0x10,
+	0x03, 0x12, 0x09, 0x0a, 0x05, 0x46, 0x41, 0x54, 0x41, 0x4c, 0x10, 0x04, 0x42, 0x2e, 0x5a, 0x2c,
+	0x67, 0x69, 0x74, 0x2e, 0x70, 0x65, 0x72, 0x78, 0x2e, 0x72, 0x75, 0x2f, 0x70, 0x65, 0x72, 0x78,
+	0x69, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x78, 0x69, 0x73, 0x2d, 0x67, 0x6f, 0x2f, 0x70, 0x72, 0x6f,
+	0x74, 0x6f, 0x2f, 0x6c, 0x6f, 0x67, 0x73, 0x3b, 0x6c, 0x6f, 0x67, 0x73, 0x62, 0x06, 0x70, 0x72,
+	0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+	file_logs_log_proto_rawDescOnce sync.Once
+	file_logs_log_proto_rawDescData = file_logs_log_proto_rawDesc
+)
+
+func file_logs_log_proto_rawDescGZIP() []byte {
+	file_logs_log_proto_rawDescOnce.Do(func() {
+		file_logs_log_proto_rawDescData = protoimpl.X.CompressGZIP(file_logs_log_proto_rawDescData)
+	})
+	return file_logs_log_proto_rawDescData
+}
+
+var file_logs_log_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
+var file_logs_log_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
+var file_logs_log_proto_goTypes = []interface{}{
+	(LogLevel)(0),                 // 0: logs.LogLevel
+	(*LogEntry)(nil),              // 1: logs.LogEntry
+	(*timestamppb.Timestamp)(nil), // 2: google.protobuf.Timestamp
+	(*anypb.Any)(nil),             // 3: google.protobuf.Any
+}
+var file_logs_log_proto_depIdxs = []int32{
+	2, // 0: logs.LogEntry.timestamp:type_name -> google.protobuf.Timestamp
+	0, // 1: logs.LogEntry.level:type_name -> logs.LogLevel
+	3, // 2: logs.LogEntry.attr:type_name -> google.protobuf.Any
+	3, // [3:3] is the sub-list for method output_type
+	3, // [3:3] is the sub-list for method input_type
+	3, // [3:3] is the sub-list for extension type_name
+	3, // [3:3] is the sub-list for extension extendee
+	0, // [0:3] is the sub-list for field type_name
+}
+
+func init() { file_logs_log_proto_init() }
+func file_logs_log_proto_init() {
+	if File_logs_log_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_logs_log_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*LogEntry); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_logs_log_proto_rawDesc,
+			NumEnums:      1,
+			NumMessages:   1,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_logs_log_proto_goTypes,
+		DependencyIndexes: file_logs_log_proto_depIdxs,
+		EnumInfos:         file_logs_log_proto_enumTypes,
+		MessageInfos:      file_logs_log_proto_msgTypes,
+	}.Build()
+	File_logs_log_proto = out.File
+	file_logs_log_proto_rawDesc = nil
+	file_logs_log_proto_goTypes = nil
+	file_logs_log_proto_depIdxs = nil
+}
diff --git a/proto/logs/log_service.pb.go b/proto/logs/log_service.pb.go
new file mode 100644
index 0000000000000000000000000000000000000000..7036778591e3e51156c67eccffb05976fea10d38
--- /dev/null
+++ b/proto/logs/log_service.pb.go
@@ -0,0 +1,735 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.31.0
+// 	protoc        v4.25.1
+// source: logs/log_service.proto
+
+package logs
+
+import (
+	common "git.perx.ru/perxis/perxis-go/proto/common"
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	_ "google.golang.org/protobuf/types/known/timestamppb"
+	reflect "reflect"
+	sync "sync"
+)
+
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+// Запрос для лога
+type LogRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Запись лога
+	Entries []*LogEntry `protobuf:"bytes,1,rep,name=entries,proto3" json:"entries,omitempty"`
+}
+
+func (x *LogRequest) Reset() {
+	*x = LogRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_logs_log_service_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *LogRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*LogRequest) ProtoMessage() {}
+
+func (x *LogRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_logs_log_service_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use LogRequest.ProtoReflect.Descriptor instead.
+func (*LogRequest) Descriptor() ([]byte, []int) {
+	return file_logs_log_service_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *LogRequest) GetEntries() []*LogEntry {
+	if x != nil {
+		return x.Entries
+	}
+	return nil
+}
+
+// Ответ сервера на запрос лога
+type LogResponse struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Содержит информацию об ошибке, если таковая имеется
+	Error *common.Error `protobuf:"bytes,1,opt,name=error,proto3" json:"error,omitempty"`
+}
+
+func (x *LogResponse) Reset() {
+	*x = LogResponse{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_logs_log_service_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *LogResponse) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*LogResponse) ProtoMessage() {}
+
+func (x *LogResponse) ProtoReflect() protoreflect.Message {
+	mi := &file_logs_log_service_proto_msgTypes[1]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use LogResponse.ProtoReflect.Descriptor instead.
+func (*LogResponse) Descriptor() ([]byte, []int) {
+	return file_logs_log_service_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *LogResponse) GetError() *common.Error {
+	if x != nil {
+		return x.Error
+	}
+	return nil
+}
+
+type Filter struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Запрос на поиск логов
+	// Примеры:
+	// 1. `timestamp > '2019-01-01' AND timestamp < '2019-01-02'`
+	// 2. `timestamp > '2019-01-01' AND timestamp < '2019-01-02' AND level = 'error'`
+	// 3. `component = 'api' AND object_id = '123' AND object_type = 'item' AND space = 'spc1'`
+	// 4. `id in ['1', '2', '3']`
+	Q []string `protobuf:"bytes,3,rep,name=q,proto3" json:"q,omitempty"` // Список выражений для фильтрации
+}
+
+func (x *Filter) Reset() {
+	*x = Filter{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_logs_log_service_proto_msgTypes[2]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Filter) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Filter) ProtoMessage() {}
+
+func (x *Filter) ProtoReflect() protoreflect.Message {
+	mi := &file_logs_log_service_proto_msgTypes[2]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use Filter.ProtoReflect.Descriptor instead.
+func (*Filter) Descriptor() ([]byte, []int) {
+	return file_logs_log_service_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *Filter) GetQ() []string {
+	if x != nil {
+		return x.Q
+	}
+	return nil
+}
+
+// Запрос на поиск логов
+type FindRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Фильтры для поиска
+	Filter *Filter `protobuf:"bytes,2,opt,name=filter,proto3" json:"filter,omitempty"`
+	// Опции поиска
+	Options *common.FindOptions `protobuf:"bytes,3,opt,name=options,proto3" json:"options,omitempty"`
+}
+
+func (x *FindRequest) Reset() {
+	*x = FindRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_logs_log_service_proto_msgTypes[3]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *FindRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*FindRequest) ProtoMessage() {}
+
+func (x *FindRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_logs_log_service_proto_msgTypes[3]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use FindRequest.ProtoReflect.Descriptor instead.
+func (*FindRequest) Descriptor() ([]byte, []int) {
+	return file_logs_log_service_proto_rawDescGZIP(), []int{3}
+}
+
+func (x *FindRequest) GetFilter() *Filter {
+	if x != nil {
+		return x.Filter
+	}
+	return nil
+}
+
+func (x *FindRequest) GetOptions() *common.FindOptions {
+	if x != nil {
+		return x.Options
+	}
+	return nil
+}
+
+// Результат поиска
+type FindResult struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Найденные записи лога
+	Entries []*LogEntry `protobuf:"bytes,1,rep,name=entries,proto3" json:"entries,omitempty"`
+	// Использовавшийся для поиска фильтр
+	// Для
+	Filter *Filter `protobuf:"bytes,2,opt,name=filter,proto3" json:"filter,omitempty"`
+	// Использовавшиеся для поиска опции
+	Options *common.FindOptions `protobuf:"bytes,3,opt,name=options,proto3" json:"options,omitempty"`
+	// Общее количество найденных записей
+	Total uint32 `protobuf:"varint,4,opt,name=total,proto3" json:"total,omitempty"`
+}
+
+func (x *FindResult) Reset() {
+	*x = FindResult{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_logs_log_service_proto_msgTypes[4]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *FindResult) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*FindResult) ProtoMessage() {}
+
+func (x *FindResult) ProtoReflect() protoreflect.Message {
+	mi := &file_logs_log_service_proto_msgTypes[4]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use FindResult.ProtoReflect.Descriptor instead.
+func (*FindResult) Descriptor() ([]byte, []int) {
+	return file_logs_log_service_proto_rawDescGZIP(), []int{4}
+}
+
+func (x *FindResult) GetEntries() []*LogEntry {
+	if x != nil {
+		return x.Entries
+	}
+	return nil
+}
+
+func (x *FindResult) GetFilter() *Filter {
+	if x != nil {
+		return x.Filter
+	}
+	return nil
+}
+
+func (x *FindResult) GetOptions() *common.FindOptions {
+	if x != nil {
+		return x.Options
+	}
+	return nil
+}
+
+func (x *FindResult) GetTotal() uint32 {
+	if x != nil {
+		return x.Total
+	}
+	return 0
+}
+
+// Ответ сервера на запрос поиска
+type FindResponse struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Types that are assignable to Response:
+	//
+	//	*FindResponse_Result
+	//	*FindResponse_Error
+	Response isFindResponse_Response `protobuf_oneof:"response"`
+}
+
+func (x *FindResponse) Reset() {
+	*x = FindResponse{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_logs_log_service_proto_msgTypes[5]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *FindResponse) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*FindResponse) ProtoMessage() {}
+
+func (x *FindResponse) ProtoReflect() protoreflect.Message {
+	mi := &file_logs_log_service_proto_msgTypes[5]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use FindResponse.ProtoReflect.Descriptor instead.
+func (*FindResponse) Descriptor() ([]byte, []int) {
+	return file_logs_log_service_proto_rawDescGZIP(), []int{5}
+}
+
+func (m *FindResponse) GetResponse() isFindResponse_Response {
+	if m != nil {
+		return m.Response
+	}
+	return nil
+}
+
+func (x *FindResponse) GetResult() *FindResult {
+	if x, ok := x.GetResponse().(*FindResponse_Result); ok {
+		return x.Result
+	}
+	return nil
+}
+
+func (x *FindResponse) GetError() *common.Error {
+	if x, ok := x.GetResponse().(*FindResponse_Error); ok {
+		return x.Error
+	}
+	return nil
+}
+
+type isFindResponse_Response interface {
+	isFindResponse_Response()
+}
+
+type FindResponse_Result struct {
+	// Результаты поиска
+	Result *FindResult `protobuf:"bytes,1,opt,name=result,proto3,oneof"`
+}
+
+type FindResponse_Error struct {
+	// Информация об ошибке, если таковая имеется
+	Error *common.Error `protobuf:"bytes,2,opt,name=error,proto3,oneof"`
+}
+
+func (*FindResponse_Result) isFindResponse_Response() {}
+
+func (*FindResponse_Error) isFindResponse_Response() {}
+
+// Запрос на удаление логов
+type DeleteRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Фильтры для удаления
+	Filter *Filter `protobuf:"bytes,2,opt,name=filter,proto3" json:"filter,omitempty"`
+}
+
+func (x *DeleteRequest) Reset() {
+	*x = DeleteRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_logs_log_service_proto_msgTypes[6]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *DeleteRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*DeleteRequest) ProtoMessage() {}
+
+func (x *DeleteRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_logs_log_service_proto_msgTypes[6]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use DeleteRequest.ProtoReflect.Descriptor instead.
+func (*DeleteRequest) Descriptor() ([]byte, []int) {
+	return file_logs_log_service_proto_rawDescGZIP(), []int{6}
+}
+
+func (x *DeleteRequest) GetFilter() *Filter {
+	if x != nil {
+		return x.Filter
+	}
+	return nil
+}
+
+// Ответ сервера на запрос удаления
+type DeleteResponse struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Информация об ошибке, если таковая имеется
+	Error *common.Error `protobuf:"bytes,1,opt,name=error,proto3" json:"error,omitempty"`
+}
+
+func (x *DeleteResponse) Reset() {
+	*x = DeleteResponse{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_logs_log_service_proto_msgTypes[7]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *DeleteResponse) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*DeleteResponse) ProtoMessage() {}
+
+func (x *DeleteResponse) ProtoReflect() protoreflect.Message {
+	mi := &file_logs_log_service_proto_msgTypes[7]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use DeleteResponse.ProtoReflect.Descriptor instead.
+func (*DeleteResponse) Descriptor() ([]byte, []int) {
+	return file_logs_log_service_proto_rawDescGZIP(), []int{7}
+}
+
+func (x *DeleteResponse) GetError() *common.Error {
+	if x != nil {
+		return x.Error
+	}
+	return nil
+}
+
+var File_logs_log_service_proto protoreflect.FileDescriptor
+
+var file_logs_log_service_proto_rawDesc = []byte{
+	0x0a, 0x16, 0x6c, 0x6f, 0x67, 0x73, 0x2f, 0x6c, 0x6f, 0x67, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69,
+	0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x1a, 0x13,
+	0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72,
+	0x6f, 0x74, 0x6f, 0x1a, 0x12, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x65, 0x72, 0x72, 0x6f,
+	0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x0e, 0x6c, 0x6f, 0x67, 0x73, 0x2f, 0x6c, 0x6f,
+	0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f,
+	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,
+	0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x36, 0x0a, 0x0a, 0x4c, 0x6f, 0x67, 0x52,
+	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x28, 0x0a, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65,
+	0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6f, 0x67, 0x73, 0x2e, 0x4c,
+	0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73,
+	0x22, 0x32, 0x0a, 0x0b, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
+	0x23, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d,
+	0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x05, 0x65,
+	0x72, 0x72, 0x6f, 0x72, 0x22, 0x16, 0x0a, 0x06, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x0c,
+	0x0a, 0x01, 0x71, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x01, 0x71, 0x22, 0x62, 0x0a, 0x0b,
+	0x46, 0x69, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x06, 0x66,
+	0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6c, 0x6f,
+	0x67, 0x73, 0x2e, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65,
+	0x72, 0x12, 0x2d, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01,
+	0x28, 0x0b, 0x32, 0x13, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x46, 0x69, 0x6e, 0x64,
+	0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73,
+	0x22, 0xa1, 0x01, 0x0a, 0x0a, 0x46, 0x69, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12,
+	0x28, 0x0a, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b,
+	0x32, 0x0e, 0x2e, 0x6c, 0x6f, 0x67, 0x73, 0x2e, 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79,
+	0x52, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x12, 0x24, 0x0a, 0x06, 0x66, 0x69, 0x6c,
+	0x74, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6c, 0x6f, 0x67, 0x73,
+	0x2e, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12,
+	0x2d, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b,
+	0x32, 0x13, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x46, 0x69, 0x6e, 0x64, 0x4f, 0x70,
+	0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x14,
+	0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x74,
+	0x6f, 0x74, 0x61, 0x6c, 0x22, 0x6d, 0x0a, 0x0c, 0x46, 0x69, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70,
+	0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01,
+	0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x6c, 0x6f, 0x67, 0x73, 0x2e, 0x46, 0x69, 0x6e, 0x64,
+	0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x48, 0x00, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74,
+	0x12, 0x25, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
+	0x0d, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x48, 0x00,
+	0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x42, 0x0a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f,
+	0x6e, 0x73, 0x65, 0x22, 0x35, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71,
+	0x75, 0x65, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x02,
+	0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6c, 0x6f, 0x67, 0x73, 0x2e, 0x46, 0x69, 0x6c, 0x74,
+	0x65, 0x72, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x22, 0x35, 0x0a, 0x0e, 0x44, 0x65,
+	0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x05,
+	0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x63, 0x6f,
+	0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f,
+	0x72, 0x32, 0xa3, 0x01, 0x0a, 0x0b, 0x4c, 0x6f, 0x67, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63,
+	0x65, 0x12, 0x2c, 0x0a, 0x03, 0x4c, 0x6f, 0x67, 0x12, 0x10, 0x2e, 0x6c, 0x6f, 0x67, 0x73, 0x2e,
+	0x4c, 0x6f, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x6c, 0x6f, 0x67,
+	0x73, 0x2e, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12,
+	0x2f, 0x0a, 0x04, 0x46, 0x69, 0x6e, 0x64, 0x12, 0x11, 0x2e, 0x6c, 0x6f, 0x67, 0x73, 0x2e, 0x46,
+	0x69, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x6f, 0x67,
+	0x73, 0x2e, 0x46, 0x69, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00,
+	0x12, 0x35, 0x0a, 0x06, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x13, 0x2e, 0x6c, 0x6f, 0x67,
+	0x73, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
+	0x14, 0x2e, 0x6c, 0x6f, 0x67, 0x73, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73,
+	0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x2e, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x2e, 0x70,
+	0x65, 0x72, 0x78, 0x2e, 0x72, 0x75, 0x2f, 0x70, 0x65, 0x72, 0x78, 0x69, 0x73, 0x2f, 0x70, 0x65,
+	0x72, 0x78, 0x69, 0x73, 0x2d, 0x67, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x6c, 0x6f,
+	0x67, 0x73, 0x3b, 0x6c, 0x6f, 0x67, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+	file_logs_log_service_proto_rawDescOnce sync.Once
+	file_logs_log_service_proto_rawDescData = file_logs_log_service_proto_rawDesc
+)
+
+func file_logs_log_service_proto_rawDescGZIP() []byte {
+	file_logs_log_service_proto_rawDescOnce.Do(func() {
+		file_logs_log_service_proto_rawDescData = protoimpl.X.CompressGZIP(file_logs_log_service_proto_rawDescData)
+	})
+	return file_logs_log_service_proto_rawDescData
+}
+
+var file_logs_log_service_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
+var file_logs_log_service_proto_goTypes = []interface{}{
+	(*LogRequest)(nil),         // 0: logs.LogRequest
+	(*LogResponse)(nil),        // 1: logs.LogResponse
+	(*Filter)(nil),             // 2: logs.Filter
+	(*FindRequest)(nil),        // 3: logs.FindRequest
+	(*FindResult)(nil),         // 4: logs.FindResult
+	(*FindResponse)(nil),       // 5: logs.FindResponse
+	(*DeleteRequest)(nil),      // 6: logs.DeleteRequest
+	(*DeleteResponse)(nil),     // 7: logs.DeleteResponse
+	(*LogEntry)(nil),           // 8: logs.LogEntry
+	(*common.Error)(nil),       // 9: common.Error
+	(*common.FindOptions)(nil), // 10: common.FindOptions
+}
+var file_logs_log_service_proto_depIdxs = []int32{
+	8,  // 0: logs.LogRequest.entries:type_name -> logs.LogEntry
+	9,  // 1: logs.LogResponse.error:type_name -> common.Error
+	2,  // 2: logs.FindRequest.filter:type_name -> logs.Filter
+	10, // 3: logs.FindRequest.options:type_name -> common.FindOptions
+	8,  // 4: logs.FindResult.entries:type_name -> logs.LogEntry
+	2,  // 5: logs.FindResult.filter:type_name -> logs.Filter
+	10, // 6: logs.FindResult.options:type_name -> common.FindOptions
+	4,  // 7: logs.FindResponse.result:type_name -> logs.FindResult
+	9,  // 8: logs.FindResponse.error:type_name -> common.Error
+	2,  // 9: logs.DeleteRequest.filter:type_name -> logs.Filter
+	9,  // 10: logs.DeleteResponse.error:type_name -> common.Error
+	0,  // 11: logs.LogsService.Log:input_type -> logs.LogRequest
+	3,  // 12: logs.LogsService.Find:input_type -> logs.FindRequest
+	6,  // 13: logs.LogsService.Delete:input_type -> logs.DeleteRequest
+	1,  // 14: logs.LogsService.Log:output_type -> logs.LogResponse
+	5,  // 15: logs.LogsService.Find:output_type -> logs.FindResponse
+	7,  // 16: logs.LogsService.Delete:output_type -> logs.DeleteResponse
+	14, // [14:17] is the sub-list for method output_type
+	11, // [11:14] is the sub-list for method input_type
+	11, // [11:11] is the sub-list for extension type_name
+	11, // [11:11] is the sub-list for extension extendee
+	0,  // [0:11] is the sub-list for field type_name
+}
+
+func init() { file_logs_log_service_proto_init() }
+func file_logs_log_service_proto_init() {
+	if File_logs_log_service_proto != nil {
+		return
+	}
+	file_logs_log_proto_init()
+	if !protoimpl.UnsafeEnabled {
+		file_logs_log_service_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*LogRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_logs_log_service_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*LogResponse); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_logs_log_service_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Filter); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_logs_log_service_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*FindRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_logs_log_service_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*FindResult); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_logs_log_service_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*FindResponse); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_logs_log_service_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*DeleteRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_logs_log_service_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*DeleteResponse); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	file_logs_log_service_proto_msgTypes[5].OneofWrappers = []interface{}{
+		(*FindResponse_Result)(nil),
+		(*FindResponse_Error)(nil),
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_logs_log_service_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   8,
+			NumExtensions: 0,
+			NumServices:   1,
+		},
+		GoTypes:           file_logs_log_service_proto_goTypes,
+		DependencyIndexes: file_logs_log_service_proto_depIdxs,
+		MessageInfos:      file_logs_log_service_proto_msgTypes,
+	}.Build()
+	File_logs_log_service_proto = out.File
+	file_logs_log_service_proto_rawDesc = nil
+	file_logs_log_service_proto_goTypes = nil
+	file_logs_log_service_proto_depIdxs = nil
+}
diff --git a/proto/logs/log_service_grpc.pb.go b/proto/logs/log_service_grpc.pb.go
new file mode 100644
index 0000000000000000000000000000000000000000..ad55cee20f51d0135ab737a566f405696fe693fa
--- /dev/null
+++ b/proto/logs/log_service_grpc.pb.go
@@ -0,0 +1,189 @@
+// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
+// versions:
+// - protoc-gen-go-grpc v1.3.0
+// - protoc             v4.25.1
+// source: logs/log_service.proto
+
+package logs
+
+import (
+	context "context"
+	grpc "google.golang.org/grpc"
+	codes "google.golang.org/grpc/codes"
+	status "google.golang.org/grpc/status"
+)
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the grpc package it is being compiled against.
+// Requires gRPC-Go v1.32.0 or later.
+const _ = grpc.SupportPackageIsVersion7
+
+const (
+	LogsService_Log_FullMethodName    = "/logs.LogsService/Log"
+	LogsService_Find_FullMethodName   = "/logs.LogsService/Find"
+	LogsService_Delete_FullMethodName = "/logs.LogsService/Delete"
+)
+
+// LogsServiceClient is the client API for LogsService service.
+//
+// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
+type LogsServiceClient interface {
+	// Метод для записи логов
+	Log(ctx context.Context, in *LogRequest, opts ...grpc.CallOption) (*LogResponse, error)
+	// Метод для поиска логов по заданным параметрам
+	Find(ctx context.Context, in *FindRequest, opts ...grpc.CallOption) (*FindResponse, error)
+	// Метод для удаления логов по заданным параметрам
+	Delete(ctx context.Context, in *DeleteRequest, opts ...grpc.CallOption) (*DeleteResponse, error)
+}
+
+type logsServiceClient struct {
+	cc grpc.ClientConnInterface
+}
+
+func NewLogsServiceClient(cc grpc.ClientConnInterface) LogsServiceClient {
+	return &logsServiceClient{cc}
+}
+
+func (c *logsServiceClient) Log(ctx context.Context, in *LogRequest, opts ...grpc.CallOption) (*LogResponse, error) {
+	out := new(LogResponse)
+	err := c.cc.Invoke(ctx, LogsService_Log_FullMethodName, in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *logsServiceClient) Find(ctx context.Context, in *FindRequest, opts ...grpc.CallOption) (*FindResponse, error) {
+	out := new(FindResponse)
+	err := c.cc.Invoke(ctx, LogsService_Find_FullMethodName, in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *logsServiceClient) Delete(ctx context.Context, in *DeleteRequest, opts ...grpc.CallOption) (*DeleteResponse, error) {
+	out := new(DeleteResponse)
+	err := c.cc.Invoke(ctx, LogsService_Delete_FullMethodName, in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+// LogsServiceServer is the server API for LogsService service.
+// All implementations must embed UnimplementedLogsServiceServer
+// for forward compatibility
+type LogsServiceServer interface {
+	// Метод для записи логов
+	Log(context.Context, *LogRequest) (*LogResponse, error)
+	// Метод для поиска логов по заданным параметрам
+	Find(context.Context, *FindRequest) (*FindResponse, error)
+	// Метод для удаления логов по заданным параметрам
+	Delete(context.Context, *DeleteRequest) (*DeleteResponse, error)
+	mustEmbedUnimplementedLogsServiceServer()
+}
+
+// UnimplementedLogsServiceServer must be embedded to have forward compatible implementations.
+type UnimplementedLogsServiceServer struct {
+}
+
+func (UnimplementedLogsServiceServer) Log(context.Context, *LogRequest) (*LogResponse, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method Log not implemented")
+}
+func (UnimplementedLogsServiceServer) Find(context.Context, *FindRequest) (*FindResponse, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method Find not implemented")
+}
+func (UnimplementedLogsServiceServer) Delete(context.Context, *DeleteRequest) (*DeleteResponse, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method Delete not implemented")
+}
+func (UnimplementedLogsServiceServer) mustEmbedUnimplementedLogsServiceServer() {}
+
+// UnsafeLogsServiceServer may be embedded to opt out of forward compatibility for this service.
+// Use of this interface is not recommended, as added methods to LogsServiceServer will
+// result in compilation errors.
+type UnsafeLogsServiceServer interface {
+	mustEmbedUnimplementedLogsServiceServer()
+}
+
+func RegisterLogsServiceServer(s grpc.ServiceRegistrar, srv LogsServiceServer) {
+	s.RegisterService(&LogsService_ServiceDesc, srv)
+}
+
+func _LogsService_Log_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(LogRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(LogsServiceServer).Log(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: LogsService_Log_FullMethodName,
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(LogsServiceServer).Log(ctx, req.(*LogRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _LogsService_Find_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(FindRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(LogsServiceServer).Find(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: LogsService_Find_FullMethodName,
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(LogsServiceServer).Find(ctx, req.(*FindRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _LogsService_Delete_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(DeleteRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(LogsServiceServer).Delete(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: LogsService_Delete_FullMethodName,
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(LogsServiceServer).Delete(ctx, req.(*DeleteRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+// LogsService_ServiceDesc is the grpc.ServiceDesc for LogsService service.
+// It's only intended for direct use with grpc.RegisterService,
+// and not to be introspected or modified (even as a copy)
+var LogsService_ServiceDesc = grpc.ServiceDesc{
+	ServiceName: "logs.LogsService",
+	HandlerType: (*LogsServiceServer)(nil),
+	Methods: []grpc.MethodDesc{
+		{
+			MethodName: "Log",
+			Handler:    _LogsService_Log_Handler,
+		},
+		{
+			MethodName: "Find",
+			Handler:    _LogsService_Find_Handler,
+		},
+		{
+			MethodName: "Delete",
+			Handler:    _LogsService_Delete_Handler,
+		},
+	},
+	Streams:  []grpc.StreamDesc{},
+	Metadata: "logs/log_service.proto",
+}
diff --git a/zap/channels.go b/zap/channels.go
new file mode 100644
index 0000000000000000000000000000000000000000..5753163514b1a91605e54e41adfdac711a2518b4
--- /dev/null
+++ b/zap/channels.go
@@ -0,0 +1,51 @@
+package zap
+
+import (
+	"git.perx.ru/perxis/perxis-go/pkg/data"
+	"go.uber.org/zap/zapcore"
+)
+
+const (
+	channelKey = "channel"
+
+	Syslog  = "syslog"
+	Userlog = "userlog"
+	// ChannelsAll   = "*"
+)
+
+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.SkipType {
+				for _, v := range f.Interface.(stringArray) {
+					if data.Contains(v, channels) {
+						return true
+					}
+				}
+			}
+		}
+		return false
+	}
+}
+
+// WithDefaultChannel аналогичен WithChannel, но также устанавливает переданный канал в качестве канала по умолчанию.
+// Это означает, что если поле Channels в записи не указано, запись все равно будет передана в zapcore.Core.
+func WithDefaultChannel(core zapcore.Core, channel string) zapcore.Core {
+	return WithChannel(core, channel, true)
+}
+
+// WithChannel добавляет к переданному zapcore.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)
+}
+
+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
new file mode 100644
index 0000000000000000000000000000000000000000..ac4c5c9a33797413a7744e439a9d31f38191540e
--- /dev/null
+++ b/zap/channels_test.go
@@ -0,0 +1,58 @@
+package zap
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	"go.uber.org/zap/zapcore"
+	"go.uber.org/zap/zaptest/observer"
+)
+
+func TestWithChannel_WriteSingleChannel(t *testing.T) {
+	core, logs := observer.New(zapcore.InfoLevel)
+	core = WithChannel(core, "test")
+
+	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 TestWithChannel_WriteMultiplyChannels(t *testing.T) {
+	core, logs := observer.New(zapcore.InfoLevel)
+
+	core = zapcore.NewTee(
+		WithChannel(core, "test1"),
+		WithChannel(core, "test2"),
+	)
+
+	require.NoError(t, core.Write(zapcore.Entry{Message: "msg"}, []zapcore.Field{Channels("test1", "test2")})) // запись попадет сразу в 2 core
+
+	require.Equal(t, 2, logs.Len())
+}
+
+func TestWithDefaultChannels(t *testing.T) {
+	core, logs := observer.New(zapcore.InfoLevel)
+
+	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{}))
+
+	require.Equal(t, 2, logs.Len())
+}
+
+func TestWithChannels(t *testing.T) {
+	core, logs := observer.New(zapcore.InfoLevel)
+
+	core = WithChannels(core, "test1", "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("test2")}))
+
+	require.Equal(t, 2, logs.Len())
+}
diff --git a/zap/field.go b/zap/field.go
new file mode 100644
index 0000000000000000000000000000000000000000..308c21d6e83a4a80c35ab9866cb757e65c4b7a24
--- /dev/null
+++ b/zap/field.go
@@ -0,0 +1,68 @@
+package zap
+
+import (
+	"context"
+
+	"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"
+)
+
+type stringArray []string
+
+func (ss stringArray) MarshalLogArray(arr zapcore.ArrayEncoder) error {
+	for i := range ss {
+		arr.AppendString(ss[i])
+	}
+	return nil
+}
+
+// Channels возвращает поле, содержащее список каналов, в которые должна быть передана запись.
+func Channels(channels ...string) zap.Field {
+	return zap.Field{
+		Key:       channelKey,
+		Type:      zapcore.SkipType, // используем тип zapcore.SkipType, чтобы при кодировании поле игнорировалось
+		Interface: stringArray(channels),
+	}
+}
+
+func Category(category string) zap.Field {
+	return zap.String("category", category)
+}
+
+func Component(component string) zap.Field {
+	return zap.String("component", component)
+}
+
+func Event(event string) zap.Field {
+	return zap.String("event", event)
+}
+
+// Object возвращает поле и устанавливает передаваемый аргумент в качестве идентификатора объекта в формате ObjectId.
+// Поддерживает типы в формате ObjectId: id.Descriptor, string, map[string]any, системные объекты.
+func Object(v any) zap.Field {
+	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, _ := id.NewObjectId(v)
+	return zap.Reflect("caller", oid)
+}
+
+// CallerFromContext извлекает auth.Principal из контекста и устанавливает его в качестве "вызывающего" в формате Object.
+func CallerFromContext(ctx context.Context) zap.Field {
+	return Caller(auth.GetPrincipal(ctx))
+}
+
+func Attr(attr any) zap.Field {
+	return zap.Any("attr", attr)
+}
+
+func Tags(tags ...string) zap.Field {
+	return zap.Strings("tags", tags)
+}
diff --git a/zap/field_test.go b/zap/field_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..84efa584f85a66468e160334510e39ddee0246b0
--- /dev/null
+++ b/zap/field_test.go
@@ -0,0 +1,220 @@
+package zap
+
+import (
+	"context"
+	"testing"
+
+	"git.perx.ru/perxis/perxis-go/id"
+	"git.perx.ru/perxis/perxis-go/pkg/auth"
+	"git.perx.ru/perxis/perxis-go/pkg/items"
+	"git.perx.ru/perxis/perxis-go/pkg/users"
+	"github.com/stretchr/testify/assert"
+	"go.uber.org/zap"
+	"go.uber.org/zap/zapcore"
+)
+
+func TestChannels(t *testing.T) {
+	tests := []struct {
+		name  string
+		field zap.Field
+		want  zap.Field
+	}{
+		{name: "ok", field: Channels("master"), want: zap.Field{Key: channelKey, Type: zapcore.SkipType, Interface: stringArray{"master"}}},
+		{name: "invalid", field: Channels(), want: zap.Field{Key: channelKey, Type: zapcore.SkipType, Interface: stringArray(nil)}},
+	}
+
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			assert.Equal(t, tc.want, tc.field)
+		})
+	}
+}
+
+func TestChannelsEncode(t *testing.T) {
+	enc := zapcore.NewMapObjectEncoder()
+	field := Channels("master")
+	field.AddTo(enc)
+
+	assert.Empty(t, enc.Fields)
+}
+
+func TestCategory(t *testing.T) {
+	tests := []struct {
+		name  string
+		field zap.Field
+		want  zap.Field
+	}{
+		{name: "ok", field: Category("update"), want: zap.String("category", "update")},
+		{name: "invalid", field: Category(""), want: zap.String("category", "")},
+	}
+
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			assert.True(t, tc.want.Equals(tc.field))
+		})
+	}
+}
+
+func TestComponent(t *testing.T) {
+	tests := []struct {
+		name  string
+		field zap.Field
+		want  zap.Field
+	}{
+		{name: "ok", field: Component("Items"), want: zap.String("component", "Items")},
+		{name: "invalid", field: Component(""), want: zap.String("component", "")},
+	}
+
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			assert.True(t, tc.want.Equals(tc.field))
+		})
+	}
+}
+
+func TestEvent(t *testing.T) {
+	tests := []struct {
+		name  string
+		field zap.Field
+		want  zap.Field
+	}{
+		{name: "ok", field: Event("items.create"), want: zap.String("event", "items.create")},
+		{name: "invalid", field: Event(""), want: zap.String("event", "")},
+	}
+
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			assert.True(t, tc.want.Equals(tc.field))
+		})
+	}
+}
+
+func TestObject(t *testing.T) {
+	item := &items.Item{
+		ID:           "c4ca4238a0b923820dcc509a6f75849b",
+		SpaceID:      "c81e728d9d4c2f636f067f89cc14862c",
+		EnvID:        "eccbc87e4b5ce2fe28308fd9f2a7baf3",
+		CollectionID: "a87ff679a2f3e71d9181a67b7542122c",
+	}
+
+	oid := id.MustObjectId(item)
+	itemId := id.NewItemId(item.SpaceID, item.EnvID, item.CollectionID, item.ID)
+
+	tests := []struct {
+		name  string
+		field zap.Field
+		want  zap.Field
+	}{
+		{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.Reflect("object", (*id.ObjectId)(nil))},
+	}
+
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			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)
+			}
+		})
+	}
+}
+
+func TestCaller(t *testing.T) {
+	user := &users.User{
+		ID: "c4ca4238a0b923820dcc509a6f75849b",
+	}
+
+	oid := id.MustObjectId(user)
+	userId := id.NewUserId(user.ID)
+
+	tests := []struct {
+		name  string
+		field zap.Field
+		want  zap.Field
+	}{
+		{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.Reflect("caller", (*id.ObjectId)(nil))},
+	}
+
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			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)
+			}
+		})
+	}
+}
+
+func TestCallerFromContext(t *testing.T) {
+	ctx := auth.WithSystem(context.Background())
+	oid := id.MustObjectId(auth.GetPrincipal(ctx))
+
+	tests := []struct {
+		name  string
+		field zap.Field
+		want  zap.Field
+	}{
+		{name: "ok", field: CallerFromContext(ctx), want: zap.Reflect("caller", oid)},
+		{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) {
+			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)
+			}
+		})
+	}
+}
+
+func TestAttr(t *testing.T) {
+	tests := []struct {
+		name  string
+		field zap.Field
+		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.Reflect("attr", nil)},
+	}
+
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			assert.True(t, tc.want.Equals(tc.field))
+		})
+	}
+}
+
+func TestTags(t *testing.T) {
+	tests := []struct {
+		name  string
+		field zap.Field
+		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.Strings("tags", nil)},
+	}
+
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			assert.True(t, tc.want.Equals(tc.field))
+		})
+	}
+}
diff --git a/zap/filter_core.go b/zap/filter_core.go
new file mode 100644
index 0000000000000000000000000000000000000000..7e99d942e439afc8a05faaf55be29b8cb604b7fc
--- /dev/null
+++ b/zap/filter_core.go
@@ -0,0 +1,106 @@
+package zap
+
+import (
+	"go.uber.org/zap"
+	"go.uber.org/zap/zapcore"
+)
+
+type FilterFunc func(entry zapcore.Entry, fields []zapcore.Field) bool
+
+func ContainsField(field zapcore.Field) FilterFunc {
+	return func(_ zapcore.Entry, fields []zapcore.Field) bool {
+		for _, f := range fields {
+			if f.Equals(field) {
+				return true
+			}
+		}
+		return false
+	}
+}
+
+func ContainsKey(key string) FilterFunc {
+	return func(entry zapcore.Entry, fields []zapcore.Field) bool {
+		for _, f := range fields {
+			if f.Key == key {
+				return true
+			}
+		}
+		return false
+	}
+}
+
+func Or(filters ...FilterFunc) FilterFunc {
+	return func(entry zapcore.Entry, fields []zapcore.Field) bool {
+		for _, f := range filters {
+			if f(entry, fields) {
+				return true
+			}
+		}
+		return false
+	}
+}
+
+func Not(filter FilterFunc) FilterFunc {
+	return func(entry zapcore.Entry, fields []zapcore.Field) bool {
+		return !filter(entry, fields)
+	}
+}
+
+type filterCore struct {
+	zapcore.Core
+
+	filters []FilterFunc
+
+	// fields хранит контекст записей ядра, передаваемых при вызове With.
+	// В методе Write передаются только поля конкретной записи, но мы также хотим учитывать поля контекста ядра.
+	fields []zap.Field
+}
+
+// WithFilters - добавить фильтры, которые будут применяться при записи лога (вызове `core.Write`)
+// Метод `core.Write` будет вызван только в случае, когда результат всех фильтров `true`
+//
+// Обратить внимание, фильтр не применяется к полям, которые были добавлены в `core` через вызов `core.With`
+// до вызова WithFilters. Пример:
+//
+//	l, _ := zap.NewDevelopment()
+//	core := l.Core().With([]zapcore.Field{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 WithFilters(core zapcore.Core, filters ...FilterFunc) zapcore.Core {
+	return &filterCore{
+		Core:    core,
+		filters: filters,
+	}
+}
+
+func (core *filterCore) With(fields []zapcore.Field) zapcore.Core {
+	return &filterCore{
+		Core:    core.Core.With(fields),
+		filters: core.filters,
+		fields:  append(core.fields, fields...),
+	}
+}
+
+func (core *filterCore) Check(entry zapcore.Entry, checkedEntry *zapcore.CheckedEntry) *zapcore.CheckedEntry {
+	if core.Core.Enabled(entry.Level) {
+		return checkedEntry.AddCore(entry, core)
+	}
+	return checkedEntry
+}
+
+func (core *filterCore) Write(entry zapcore.Entry, fields []zapcore.Field) error {
+	if len(core.fields) > 0 {
+		fields = append(core.fields, fields...)
+	}
+
+	for _, filter := range core.filters {
+		if !filter(entry, fields) {
+			return nil
+		}
+	}
+
+	return core.Core.Write(entry, fields)
+}
diff --git a/zap/filter_core_test.go b/zap/filter_core_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..21c906f24f5b7458c7628985871f1480564a3164
--- /dev/null
+++ b/zap/filter_core_test.go
@@ -0,0 +1,48 @@
+package zap
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	"go.uber.org/zap"
+	"go.uber.org/zap/zapcore"
+	"go.uber.org/zap/zaptest/observer"
+)
+
+func TestFilterCore_Write(t *testing.T) {
+	core, logs := observer.New(zapcore.InfoLevel)
+	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)
+
+	err = core.Write(zapcore.Entry{Message: "msg"}, []zapcore.Field{zap.Bool("check", true)})
+	require.NoError(t, err)
+
+	err = core.Write(zapcore.Entry{Message: "msg"}, nil)
+	require.NoError(t, err)
+
+	require.Equal(t, 2, logs.Len())
+}
+
+func TestNotContainsField(t *testing.T) {
+	core, logs := observer.New(zapcore.InfoLevel)
+	core = WithFilters(core, Not(ContainsField(zap.Int("b", 2))))
+
+	err := core.Write(zapcore.Entry{Message: "msg"}, []zapcore.Field{
+		zap.Int("a", 1),
+		zap.Int("b", 2),
+	})
+	require.NoError(t, err)
+
+	err = core.Write(zapcore.Entry{Message: "msg"}, []zapcore.Field{
+		zap.Int("a", 1),
+		zap.Int("b", 3),
+	})
+	require.NoError(t, err)
+
+	err = core.Write(zapcore.Entry{Message: "msg"}, []zapcore.Field{})
+	require.NoError(t, err)
+
+	require.Equal(t, 2, logs.Len())
+}