diff --git a/pkg/spaces/middleware/caching_middleware.go b/pkg/spaces/middleware/caching_middleware.go
new file mode 100644
index 0000000000000000000000000000000000000000..62396fc8f1e9101f92885330a50797e26dbdebe2
--- /dev/null
+++ b/pkg/spaces/middleware/caching_middleware.go
@@ -0,0 +1,106 @@
+package service
+
+import (
+	"context"
+
+	"git.perx.ru/perxis/perxis-go/pkg/cache"
+	service "git.perx.ru/perxis/perxis-go/pkg/spaces"
+)
+
+func orgKey(orgID string) string { return "org-" + orgID }
+
+func CachingMiddleware(cache *cache.Cache) Middleware {
+	return func(next service.Spaces) service.Spaces {
+		m := &cachingMiddleware{
+			cache: cache,
+			next:  next,
+		}
+
+		return m
+	}
+}
+
+type cachingMiddleware struct {
+	cache *cache.Cache
+	next  service.Spaces
+}
+
+func (m cachingMiddleware) Create(ctx context.Context, space *service.Space) (sp *service.Space, err error) {
+
+	sp, err = m.next.Create(ctx, space)
+	if err == nil {
+		m.cache.Remove(orgKey(sp.OrgID))
+	}
+	return sp, err
+}
+
+func (m cachingMiddleware) Get(ctx context.Context, spaceId string) (sp *service.Space, err error) {
+
+	value, e := m.cache.Get(spaceId)
+	if e == nil {
+		return value.(*service.Space), err
+	}
+	sp, err = m.next.Get(ctx, spaceId)
+	if err == nil {
+		m.cache.Set(spaceId, sp)
+	}
+	return sp, err
+}
+
+func (m cachingMiddleware) List(ctx context.Context, orgId string) (spaces []*service.Space, err error) {
+
+	value, e := m.cache.Get(orgKey(orgId))
+	if e == nil {
+		return value.([]*service.Space), err
+	}
+	spaces, err = m.next.List(ctx, orgId)
+	if err == nil {
+		m.cache.Set(orgKey(orgId), spaces)
+		for _, s := range spaces {
+			m.cache.Set(s.ID, s)
+		}
+	}
+	return spaces, err
+}
+
+func (m cachingMiddleware) Update(ctx context.Context, space *service.Space) (err error) {
+
+	err = m.next.Update(ctx, space)
+	if err == nil {
+		value, e := m.cache.Get(space.ID)
+		if e == nil {
+			space := value.(*service.Space)
+			m.cache.Remove(orgKey(space.OrgID))
+		}
+		m.cache.Remove(space.ID)
+	}
+	return err
+}
+
+func (m cachingMiddleware) UpdateConfig(ctx context.Context, spaceId string, config *service.Config) (err error) {
+
+	err = m.next.UpdateConfig(ctx, spaceId, config)
+	if err == nil {
+		value, e := m.cache.Get(spaceId)
+		if e == nil {
+			space := value.(*service.Space)
+			m.cache.Remove(orgKey(space.OrgID))
+		}
+		m.cache.Remove(spaceId)
+	}
+	return err
+}
+
+func (m cachingMiddleware) Delete(ctx context.Context, spaceId string) (err error) {
+
+	err = m.next.Delete(ctx, spaceId)
+	if err == nil {
+		value, e := m.cache.Get(spaceId)
+		if e == nil {
+			space := value.(*service.Space)
+			m.cache.Remove(orgKey(space.OrgID))
+		}
+		m.cache.Remove(spaceId)
+	}
+	return err
+}
diff --git a/pkg/spaces/middleware/caching_middleware_test.go b/pkg/spaces/middleware/caching_middleware_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..2cfaf98db8713081225b3f01f39782f487292161
--- /dev/null
+++ b/pkg/spaces/middleware/caching_middleware_test.go
@@ -0,0 +1,241 @@
+package service
+
+import (
+	"context"
+	"testing"
+	"time"
+
+	"git.perx.ru/perxis/perxis-go/pkg/cache"
+	"git.perx.ru/perxis/perxis-go/pkg/errors"
+
+	"git.perx.ru/perxis/perxis-go/pkg/spaces"
+	spmocks "git.perx.ru/perxis/perxis-go/pkg/spaces/mocks"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/mock"
+	"github.com/stretchr/testify/require"
+)
+
+func TestRolesCache(t *testing.T) {
+
+	const (
+		spaceID = "spaceID"
+		orgID   = "orgID"
+		size    = 5
+		ttl     = 20 * time.Millisecond
+	)
+
+	errNotFound := errors.NotFound(errors.New("not found"))
+
+	ctx := context.Background()
+
+	t.Run("Get from cache", func(t *testing.T) {
+		sp := &spmocks.Spaces{}
+
+		svc := CachingMiddleware(cache.NewCache(size, ttl))(sp)
+
+		sp.On("Get", mock.Anything, spaceID).Return(&spaces.Space{ID: spaceID, OrgID: orgID, Name: "Space"}, nil).Once()
+
+		v1, err := svc.Get(ctx, spaceID)
+		require.NoError(t, err)
+
+		v2, err := svc.Get(ctx, spaceID)
+		require.NoError(t, err)
+		assert.Same(t, v1, v2, "Ожидается при повторном запросе получение объекта из кэша.")
+
+		sp.AssertExpectations(t)
+	})
+
+	t.Run("List from cache", func(t *testing.T) {
+		sp := &spmocks.Spaces{}
+
+		svc := CachingMiddleware(cache.NewCache(size, ttl))(sp)
+
+		sp.On("List", mock.Anything, orgID).Return([]*spaces.Space{{ID: spaceID, OrgID: orgID, Name: "Space"}}, nil).Once()
+
+		vl1, err := svc.List(ctx, orgID)
+		require.NoError(t, err)
+
+		vl2, err := svc.List(ctx, orgID)
+		require.NoError(t, err)
+		assert.Same(t, vl1[0], vl2[0], "Ожидается при повторном запросе получение объектов из кэша.")
+
+		sp.AssertExpectations(t)
+	})
+
+	t.Run("Invalidate cache", func(t *testing.T) {
+		t.Run("After Update", func(t *testing.T) {
+			sp := &spmocks.Spaces{}
+
+			svc := CachingMiddleware(cache.NewCache(size, ttl))(sp)
+
+			sp.On("Get", mock.Anything, spaceID).Return(&spaces.Space{ID: spaceID, OrgID: orgID, Name: "Space"}, nil).Once()
+			sp.On("List", mock.Anything, orgID).Return([]*spaces.Space{{ID: spaceID, OrgID: orgID, Name: "Space"}}, nil).Once()
+
+			v1, err := svc.Get(ctx, spaceID)
+			require.NoError(t, err)
+
+			v2, err := svc.Get(ctx, spaceID)
+			require.NoError(t, err)
+			assert.Same(t, v1, v2, "Ожидается при повторном запросе получение объекта из кэша.")
+
+			vl1, err := svc.List(ctx, orgID)
+			require.NoError(t, err)
+
+			vl2, err := svc.List(ctx, orgID)
+			require.NoError(t, err)
+			assert.Same(t, vl1[0], vl2[0], "Ожидается при повторном запросе получение объектов из кэша.")
+
+			sp.On("Update", mock.Anything, mock.Anything).Return(nil).Once()
+
+			err = svc.Update(ctx, &spaces.Space{ID: spaceID, OrgID: orgID, Name: "SpaceUPD"})
+			require.NoError(t, err)
+
+			sp.On("Get", mock.Anything, spaceID).Return(&spaces.Space{ID: spaceID, OrgID: orgID, Name: "SpaceUPD"}, nil).Once()
+			sp.On("List", mock.Anything, orgID).Return([]*spaces.Space{{ID: spaceID, OrgID: orgID, Name: "SpaceUPD"}}, nil).Once()
+
+			v3, err := svc.Get(ctx, spaceID)
+			require.NoError(t, err)
+			assert.NotSame(t, v2, v3, "Ожидается что кеш объекта был удален после обновления объекта.")
+
+			vl3, err := svc.List(ctx, orgID)
+			require.NoError(t, err)
+			assert.NotSame(t, vl2[0], vl3[0], "Ожидается что кеш объектов был удален после обновления объекта.")
+
+			sp.AssertExpectations(t)
+		})
+
+		t.Run("After UpdateConfig", func(t *testing.T) {
+			sp := &spmocks.Spaces{}
+
+			svc := CachingMiddleware(cache.NewCache(size, ttl))(sp)
+
+			sp.On("Get", mock.Anything, spaceID).Return(&spaces.Space{ID: spaceID, OrgID: orgID, Name: "Space"}, nil).Once()
+			sp.On("List", mock.Anything, orgID).Return([]*spaces.Space{{ID: spaceID, OrgID: orgID, Name: "Space"}}, nil).Once()
+
+			v1, err := svc.Get(ctx, spaceID)
+			require.NoError(t, err)
+
+			v2, err := svc.Get(ctx, spaceID)
+			require.NoError(t, err)
+			assert.Same(t, v1, v2, "Ожидается при повторном запросе получение объекта из кэша.")
+
+			vl1, err := svc.List(ctx, orgID)
+			require.NoError(t, err)
+
+			vl2, err := svc.List(ctx, orgID)
+			require.NoError(t, err)
+			assert.Same(t, vl1[0], vl2[0], "Ожидается при повторном запросе получение объектов из кэша.")
+
+			sp.On("UpdateConfig", mock.Anything, spaceID, mock.Anything).Return(nil).Once()
+
+			err = svc.UpdateConfig(ctx, spaceID, &spaces.Config{Features: []string{"feature"}})
+			require.NoError(t, err)
+
+			sp.On("Get", mock.Anything, spaceID).Return(&spaces.Space{ID: spaceID, OrgID: orgID, Name: "SpaceUPD", Config: &spaces.Config{Features: []string{"feature"}}}, nil).Once()
+			sp.On("List", mock.Anything, orgID).Return([]*spaces.Space{{ID: spaceID, OrgID: orgID, Name: "SpaceUPD", Config: &spaces.Config{Features: []string{"feature"}}}}, nil).Once()
+
+			v3, err := svc.Get(ctx, spaceID)
+			require.NoError(t, err)
+			assert.NotSame(t, v2, v3, "Ожидается что кеш объекта был удален после обновления объекта.")
+
+			vl3, err := svc.List(ctx, orgID)
+			require.NoError(t, err)
+			assert.NotSame(t, vl2[0], vl3[0], "Ожидается что кеш объектов был удален после обновления объекта.")
+
+			sp.AssertExpectations(t)
+		})
+
+		t.Run("After Delete", func(t *testing.T) {
+			sp := &spmocks.Spaces{}
+
+			svc := CachingMiddleware(cache.NewCache(size, ttl))(sp)
+
+			sp.On("Get", mock.Anything, spaceID).Return(&spaces.Space{ID: spaceID, OrgID: orgID, Name: "Space"}, nil).Once()
+			sp.On("List", mock.Anything, orgID).Return([]*spaces.Space{{ID: spaceID, OrgID: orgID, Name: "Space"}}, nil).Once()
+
+			v1, err := svc.Get(ctx, spaceID)
+			require.NoError(t, err)
+
+			v2, err := svc.Get(ctx, spaceID)
+			require.NoError(t, err)
+			assert.Same(t, v1, v2, "Ожидается при повторном запросе получение объекта из кэша.")
+
+			vl1, err := svc.List(ctx, orgID)
+			require.NoError(t, err)
+
+			vl2, err := svc.List(ctx, orgID)
+			require.NoError(t, err)
+			assert.Same(t, vl1[0], vl2[0], "Ожидается при повторном запросе получение объектов из кэша.")
+
+			sp.On("Delete", mock.Anything, spaceID).Return(nil).Once()
+
+			err = svc.Delete(ctx, spaceID)
+			require.NoError(t, err)
+
+			sp.On("Get", mock.Anything, spaceID).Return(nil, errNotFound).Once()
+			sp.On("List", mock.Anything, orgID).Return([]*spaces.Space{}, nil).Once()
+
+			_, err = svc.Get(ctx, spaceID)
+			require.Error(t, err)
+			assert.EqualError(t, err, "not found", "Ожидается что после удаления объекта кеш  был удален и получена ошибка от сервиса.")
+
+			vl3, err := svc.List(ctx, orgID)
+			require.NoError(t, err)
+			assert.Len(t, vl3, 0, "Ожидается что после удаления кеш объектов был удален.")
+
+			sp.AssertExpectations(t)
+		})
+
+		t.Run("After Create", func(t *testing.T) {
+			sp := &spmocks.Spaces{}
+
+			svc := CachingMiddleware(cache.NewCache(size, ttl))(sp)
+
+			sp.On("List", mock.Anything, orgID).Return([]*spaces.Space{{ID: spaceID, OrgID: orgID, Name: "Space"}}, nil).Once()
+
+			vl1, err := svc.List(ctx, orgID)
+			require.NoError(t, err)
+
+			vl2, err := svc.List(ctx, orgID)
+			require.NoError(t, err)
+			assert.Same(t, vl1[0], vl2[0], "Ожидается при повторном запросе получение объектов из кэша.")
+
+			sp.On("Create", mock.Anything, mock.Anything).Return(&spaces.Space{ID: "spaceID2", OrgID: orgID, Name: "Space2"}, nil).Once()
+
+			_, err = svc.Create(ctx, &spaces.Space{ID: "spaceID2", OrgID: orgID, Name: "Space2"})
+			require.NoError(t, err)
+
+			sp.On("List", mock.Anything, orgID).Return([]*spaces.Space{{ID: spaceID, OrgID: orgID, Name: "Space"}, {ID: "spaceID2", OrgID: orgID, Name: "Space2"}}, nil).Once()
+
+			vl3, err := svc.List(ctx, orgID)
+			require.NoError(t, err)
+			assert.NotSame(t, vl2[0], vl3[0], "Ожидается что кеш объектов был удален после создания нового объекта.")
+
+			sp.AssertExpectations(t)
+		})
+
+		t.Run("After TTL expired", func(t *testing.T) {
+			sp := &spmocks.Spaces{}
+
+			svc := CachingMiddleware(cache.NewCache(size, ttl))(sp)
+
+			sp.On("Get", mock.Anything, spaceID).Return(&spaces.Space{ID: spaceID, OrgID: orgID, Name: "Space"}, nil).Once()
+
+			v1, err := svc.Get(ctx, spaceID)
+			require.NoError(t, err)
+
+			v2, err := svc.Get(ctx, spaceID)
+			require.NoError(t, err)
+			assert.Same(t, v1, v2, "Ожидается при повторном запросе получение объекта из кэша.")
+
+			time.Sleep(2 * ttl)
+			sp.On("Get", mock.Anything, spaceID).Return(&spaces.Space{ID: spaceID, OrgID: orgID, Name: "Space"}, nil).Once()
+
+			v3, err := svc.Get(ctx, spaceID)
+			require.NoError(t, err)
+			assert.NotSame(t, v2, v3, "Ожидается удаление объекта из кэша по истечению ttl.")
+
+			sp.AssertExpectations(t)
+		})
+	})
+}
diff --git a/pkg/spaces/middleware/error_logging_middleware.go b/pkg/spaces/middleware/error_logging_middleware.go
new file mode 100644
index 0000000000000000000000000000000000000000..d41b6678ecba526842c64aa519742eedab7e7779
--- /dev/null
+++ b/pkg/spaces/middleware/error_logging_middleware.go
@@ -0,0 +1,90 @@
+// Code generated by gowrap. DO NOT EDIT.
+// template: ../../../assets/templates/middleware/error_log
+// gowrap: http://github.com/hexdigest/gowrap
+
+package service
+
+//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 (
+	"context"
+
+	"git.perx.ru/perxis/perxis-go/pkg/spaces"
+	"go.uber.org/zap"
+)
+
+// errorLoggingMiddleware implements spaces.Spaces that is instrumented with logging
+type errorLoggingMiddleware struct {
+	logger *zap.Logger
+	next   spaces.Spaces
+}
+
+// ErrorLoggingMiddleware instruments an implementation of the spaces.Spaces with simple logging
+func ErrorLoggingMiddleware(logger *zap.Logger) Middleware {
+	return func(next spaces.Spaces) spaces.Spaces {
+		return &errorLoggingMiddleware{
+			next:   next,
+			logger: logger,
+		}
+	}
+}
+
+func (m *errorLoggingMiddleware) Create(ctx context.Context, space *spaces.Space) (created *spaces.Space, err error) {
+	logger := m.logger
+	defer func() {
+		if err != nil {
+			logger.Warn("response error", zap.Error(err))
+		}
+	}()
+	return m.next.Create(ctx, space)
+}
+
+func (m *errorLoggingMiddleware) Delete(ctx context.Context, spaceId string) (err error) {
+	logger := m.logger
+	defer func() {
+		if err != nil {
+			logger.Warn("response error", zap.Error(err))
+		}
+	}()
+	return m.next.Delete(ctx, spaceId)
+}
+
+func (m *errorLoggingMiddleware) Get(ctx context.Context, spaceId string) (space *spaces.Space, err error) {
+	logger := m.logger
+	defer func() {
+		if err != nil {
+			logger.Warn("response error", zap.Error(err))
+		}
+	}()
+	return m.next.Get(ctx, spaceId)
+}
+
+func (m *errorLoggingMiddleware) List(ctx context.Context, orgId string) (spaces []*spaces.Space, err error) {
+	logger := m.logger
+	defer func() {
+		if err != nil {
+			logger.Warn("response error", zap.Error(err))
+		}
+	}()
+	return m.next.List(ctx, orgId)
+}
+
+func (m *errorLoggingMiddleware) Update(ctx context.Context, space *spaces.Space) (err error) {
+	logger := m.logger
+	defer func() {
+		if err != nil {
+			logger.Warn("response error", zap.Error(err))
+		}
+	}()
+	return m.next.Update(ctx, space)
+}
+
+func (m *errorLoggingMiddleware) UpdateConfig(ctx context.Context, spaceId string, config *spaces.Config) (err error) {
+	logger := m.logger
+	defer func() {
+		if err != nil {
+			logger.Warn("response error", zap.Error(err))
+		}
+	}()
+	return m.next.UpdateConfig(ctx, spaceId, config)
+}
diff --git a/pkg/spaces/middleware/logging_middleware.go b/pkg/spaces/middleware/logging_middleware.go
new file mode 100644
index 0000000000000000000000000000000000000000..c2b62dbbcdd111a19538b00b5c22ceeb52896dca
--- /dev/null
+++ b/pkg/spaces/middleware/logging_middleware.go
@@ -0,0 +1,248 @@
+// Code generated by gowrap. DO NOT EDIT.
+// template: ../../../assets/templates/middleware/access_log
+// gowrap: http://github.com/hexdigest/gowrap
+
+package service
+
+//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) 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)),
+		zap.Error(err),
+	}
+
+	for k, v := range map[string]interface{}{
+		"created": created,
+		"err":     err} {
+		if k == "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)),
+		zap.Error(err),
+	}
+
+	for k, v := range map[string]interface{}{
+		"err": err} {
+		if k == "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)),
+		zap.Error(err),
+	}
+
+	for k, v := range map[string]interface{}{
+		"space": space,
+		"err":   err} {
+		if k == "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)),
+		zap.Error(err),
+	}
+
+	for k, v := range map[string]interface{}{
+		"spaces": spaces,
+		"err":    err} {
+		if k == "err" {
+			continue
+		}
+		fields = append(fields, zap.Reflect(k, v))
+	}
+
+	m.logger.Debug("List.Response", fields...)
+
+	return spaces, 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)),
+		zap.Error(err),
+	}
+
+	for k, v := range map[string]interface{}{
+		"err": err} {
+		if k == "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)),
+		zap.Error(err),
+	}
+
+	for k, v := range map[string]interface{}{
+		"err": err} {
+		if k == "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
new file mode 100644
index 0000000000000000000000000000000000000000..9d9d2243026f6c1152f7625e4c15bd26b5b7b5fc
--- /dev/null
+++ b/pkg/spaces/middleware/middleware.go
@@ -0,0 +1,28 @@
+// Code generated by gowrap. DO NOT EDIT.
+// template: ../../../assets/templates/middleware/middleware
+// gowrap: http://github.com/hexdigest/gowrap
+
+package service
+
+//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/spaces -i Spaces -t ../../../assets/templates/middleware/middleware -o middleware.go -l ""
+
+import (
+	"git.perx.ru/perxis/perxis-go/pkg/spaces"
+	"go.uber.org/zap"
+)
+
+type Middleware func(spaces.Spaces) spaces.Spaces
+
+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 = RecoveringMiddleware(logger)(s)
+	return s
+}
diff --git a/pkg/spaces/middleware/recovering_middleware.go b/pkg/spaces/middleware/recovering_middleware.go
new file mode 100644
index 0000000000000000000000000000000000000000..5ca795bf5c188603794603ec11fc0c8a79479524
--- /dev/null
+++ b/pkg/spaces/middleware/recovering_middleware.go
@@ -0,0 +1,103 @@
+// Code generated by gowrap. DO NOT EDIT.
+// template: ../../../assets/templates/middleware/recovery
+// gowrap: http://github.com/hexdigest/gowrap
+
+package service
+
+//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 (
+	"context"
+	"fmt"
+
+	"git.perx.ru/perxis/perxis-go/pkg/spaces"
+	"go.uber.org/zap"
+)
+
+// recoveringMiddleware implements spaces.Spaces that is instrumented with logging
+type recoveringMiddleware struct {
+	logger *zap.Logger
+	next   spaces.Spaces
+}
+
+// RecoveringMiddleware instruments an implementation of the spaces.Spaces with simple logging
+func RecoveringMiddleware(logger *zap.Logger) Middleware {
+	return func(next spaces.Spaces) spaces.Spaces {
+		return &recoveringMiddleware{
+			next:   next,
+			logger: logger,
+		}
+	}
+}
+
+func (m *recoveringMiddleware) Create(ctx context.Context, space *spaces.Space) (created *spaces.Space, 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.Create(ctx, space)
+}
+
+func (m *recoveringMiddleware) Delete(ctx context.Context, spaceId string) (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, spaceId)
+}
+
+func (m *recoveringMiddleware) Get(ctx context.Context, spaceId string) (space *spaces.Space, 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.Get(ctx, spaceId)
+}
+
+func (m *recoveringMiddleware) List(ctx context.Context, orgId string) (spaces []*spaces.Space, 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.List(ctx, orgId)
+}
+
+func (m *recoveringMiddleware) Update(ctx context.Context, space *spaces.Space) (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.Update(ctx, space)
+}
+
+func (m *recoveringMiddleware) UpdateConfig(ctx context.Context, spaceId string, config *spaces.Config) (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.UpdateConfig(ctx, spaceId, config)
+}