diff --git a/pkg/items/item.go b/pkg/items/item.go
index 7c34d2bb3563b338d6c3cb0baf97c4e49f58b535..1cadde35cf30614a898e60b19559057e47f4787e 100644
--- a/pkg/items/item.go
+++ b/pkg/items/item.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"fmt"
 	"reflect"
+	"strings"
 	"time"
 
 	"git.perx.ru/perxis/perxis-go/pkg/data"
@@ -135,6 +136,16 @@ func (i *Item) Clone() *Item {
 	return &itm
 }
 
+func (i Item) ObjectID(kv ...string) string {
+	path := []string{
+		"spaces", i.SpaceID,
+		"envs", i.EnvID,
+		"items", i.ID,
+	}
+	path = append(path, kv...)
+	return strings.Join(path, "/")
+}
+
 func (i *Item) ToMap() map[string]interface{} {
 	return map[string]interface{}{
 		"id":                   i.ID,
diff --git a/pkg/items/middleware/log_service_middleware.go b/pkg/items/middleware/log_service_middleware.go
new file mode 100644
index 0000000000000000000000000000000000000000..ec3028fc476c0e29aa36dc4f38ce776f04f64cf4
--- /dev/null
+++ b/pkg/items/middleware/log_service_middleware.go
@@ -0,0 +1,207 @@
+package middleware
+
+import (
+	"context"
+
+	"git.perx.ru/perxis/perxis-go/pkg/items"
+	"git.perx.ru/perxis/perxis-go/pkg/schema"
+	"go.uber.org/zap"
+)
+
+type logService struct {
+	logger *zap.Logger
+	next   items.Items
+}
+
+func LogServiceMiddleware(logger *zap.Logger) Middleware {
+	logger = logger.With(
+		zap.String("component", "Items.Service"),
+	)
+
+	return func(next items.Items) items.Items {
+		return &logService{
+			logger: logger,
+			next:   next,
+		}
+	}
+}
+
+func (m *logService) Create(ctx context.Context, item *items.Item, opts ...*items.CreateOptions) (created *items.Item, err error) {
+	level := zap.InfoLevel
+
+	created, err = m.next.Create(ctx, item, opts...)
+	if err != nil {
+		level = zap.WarnLevel
+	}
+
+	if created != nil {
+		item = created
+	}
+
+	m.logger.Log(level, "create item",
+		zap.String("event", "Items.Create"),
+		zap.String("object", item.ObjectID()),
+		zap.String("caller", ctx.Value("caller").(string)), // TODO Решить, как заполнять это поле
+	)
+
+	return
+}
+
+func (m *logService) Introspect(ctx context.Context, item *items.Item, opts ...*items.IntrospectOptions) (itm *items.Item, sch *schema.Schema, err error) {
+	return m.next.Introspect(ctx, item, opts...)
+}
+
+func (m *logService) Get(ctx context.Context, spaceId, envId, collectionId, itemId string, options ...*items.GetOptions) (item *items.Item, err error) {
+	return m.next.Get(ctx, spaceId, envId, collectionId, itemId, options...)
+}
+
+func (m *logService) Find(ctx context.Context, spaceId, envId, collectionId string, filter *items.Filter, options ...*items.FindOptions) (items []*items.Item, total int, err error) {
+	return m.next.Find(ctx, spaceId, envId, collectionId, filter, options...)
+}
+
+func (m *logService) Update(ctx context.Context, item *items.Item, options ...*items.UpdateOptions) (err error) {
+	level := zap.InfoLevel
+
+	err = m.next.Update(ctx, item, options...)
+	if err != nil {
+		level = zap.WarnLevel
+	}
+
+	m.logger.Log(level, "update item",
+		zap.String("event", "Items.Update"),
+		zap.String("object", item.ObjectID("revs", item.RevisionID)),
+		zap.String("caller", ctx.Value("caller").(string)),
+	)
+
+	return
+}
+
+func (m *logService) Delete(ctx context.Context, item *items.Item, options ...*items.DeleteOptions) (err error) {
+	level := zap.InfoLevel
+
+	err = m.next.Delete(ctx, item, options...)
+	if err != nil {
+		level = zap.WarnLevel
+	}
+
+	m.logger.Log(level, "delete item",
+		zap.String("event", "Items.Delete"),
+		zap.String("object", item.ObjectID()),
+		zap.String("caller", ctx.Value("caller").(string)),
+	)
+
+	return
+}
+
+func (m *logService) Undelete(ctx context.Context, item *items.Item, options ...*items.UndeleteOptions) (err error) {
+	level := zap.InfoLevel
+
+	err = m.next.Undelete(ctx, item, options...)
+	if err != nil {
+		level = zap.WarnLevel
+	}
+
+	m.logger.Log(level, "undelete item",
+		zap.String("event", "Items.Undelete"),
+		zap.String("object", item.ObjectID()),
+		zap.String("caller", ctx.Value("caller").(string)),
+	)
+
+	return
+}
+
+func (m *logService) Publish(ctx context.Context, item *items.Item, options ...*items.PublishOptions) (err error) {
+	level := zap.InfoLevel
+
+	err = m.next.Publish(ctx, item, options...)
+	if err != nil {
+		level = zap.WarnLevel
+	}
+
+	m.logger.Log(level, "publish item",
+		zap.String("event", "Items.Publish"),
+		zap.String("object", item.ObjectID()),
+		zap.String("caller", ctx.Value("caller").(string)),
+	)
+
+	return
+}
+
+func (m *logService) Unpublish(ctx context.Context, item *items.Item, options ...*items.UnpublishOptions) (err error) {
+	level := zap.InfoLevel
+
+	err = m.next.Unpublish(ctx, item, options...)
+	if err != nil {
+		level = zap.WarnLevel
+	}
+
+	m.logger.Log(level, "unpublish item",
+		zap.String("event", "Items.Unpublish"),
+		zap.String("object", item.ObjectID()),
+		zap.String("caller", ctx.Value("caller").(string)),
+	)
+
+	return
+}
+
+func (m *logService) GetPublished(ctx context.Context, spaceId, envId, collectionId, itemId string, options ...*items.GetPublishedOptions) (item *items.Item, err error) {
+	return m.next.GetPublished(ctx, spaceId, envId, collectionId, itemId, options...)
+}
+
+func (m *logService) FindPublished(ctx context.Context, spaceId, envId, collectionId string, filter *items.Filter, options ...*items.FindPublishedOptions) (items []*items.Item, total int, err error) {
+	return m.next.FindPublished(ctx, spaceId, envId, collectionId, filter, options...)
+}
+
+func (m *logService) GetRevision(ctx context.Context, spaceId, envId, collectionId, itemId, revisionId string, options ...*items.GetRevisionOptions) (item *items.Item, err error) {
+	return m.next.GetRevision(ctx, spaceId, envId, collectionId, itemId, revisionId, options...)
+}
+
+func (m *logService) ListRevisions(ctx context.Context, spaceId, envId, collectionId, itemId string, options ...*items.ListRevisionsOptions) (items []*items.Item, err error) {
+	return m.next.ListRevisions(ctx, spaceId, envId, collectionId, itemId, options...)
+}
+
+func (m *logService) Archive(ctx context.Context, item *items.Item, options ...*items.ArchiveOptions) (err error) {
+	level := zap.InfoLevel
+
+	err = m.next.Archive(ctx, item, options...)
+	if err != nil {
+		level = zap.WarnLevel
+	}
+
+	m.logger.Log(level, "archive item",
+		zap.String("event", "Items.Archive"),
+		zap.String("object", item.ObjectID()),
+		zap.String("caller", ctx.Value("caller").(string)),
+	)
+
+	return
+}
+
+func (m *logService) FindArchived(ctx context.Context, spaceId, envId, collectionId string, filter *items.Filter, options ...*items.FindArchivedOptions) (items []*items.Item, total int, err error) {
+	return m.next.FindArchived(ctx, spaceId, envId, collectionId, filter, options...)
+}
+
+func (m *logService) Unarchive(ctx context.Context, item *items.Item, options ...*items.UnarchiveOptions) (err error) {
+	level := zap.InfoLevel
+
+	err = m.next.Unarchive(ctx, item, options...)
+	if err != nil {
+		level = zap.WarnLevel
+	}
+
+	m.logger.Log(level, "unarchive item",
+		zap.String("event", "Items.Unarchive"),
+		zap.String("object", item.ObjectID()),
+		zap.String("caller", ctx.Value("caller").(string)),
+	)
+
+	return
+}
+
+func (m *logService) Aggregate(ctx context.Context, spaceId, envId, collectionId string, filter *items.Filter, options ...*items.AggregateOptions) (result map[string]interface{}, err error) {
+	return m.next.Aggregate(ctx, spaceId, envId, collectionId, filter, options...)
+}
+
+func (m *logService) AggregatePublished(ctx context.Context, spaceId, envId, collectionId string, filter *items.Filter, options ...*items.AggregatePublishedOptions) (result map[string]interface{}, err error) {
+	return m.next.AggregatePublished(ctx, spaceId, envId, collectionId, filter, options...)
+}