7.5 KiB
Миграция klog/zap → slog
Контекст
tools/klog — тонкая обёртка над go.uber.org/zap, которая является точкой входа для логирования во всех сервисах.
Модуль: gitlab.adsw.io/platform/tools/v3, потребляется 15+ репозиториями через vendor.
Текущий формат логов (сохраняется)
{"level":"info","caller":"main.go:42","msg":"starting service","source":"accounting","date":"2026-05-08T12:34:56.789+03:00"}
{"level":"error","caller":"auth.go:28","msg":"fetching user profile","source":"accounting","date":"2026-05-08T12:34:56.789+03:00","error":"context deadline exceeded"}
Поля: level (строчными), caller, msg, source (при init), date (ISO 8601 в каждом вызове через cookFields()).
Формат воспроизводится через log/slog с кастомным JSON handler'ом + ReplaceAttr для приведения уровней к нижнему регистру.
Стратегия
Публичный API klog не меняется — сервисы обновляют vendor и ничего не ломается.
Единственное breaking изменение: SetLogger(*zap.Logger) → SetLogger(*slog.Logger).
Используется только в тестах через zap.NewNop().
Решение: добавить SetNopLogger() — тесты меняют одну строку без импорта zap.
Шаг 0 — tools (делается первым, разблокирует всё остальное)
| # | Файл | Что менять | Сложность |
|---|---|---|---|
| 1 | tools/klog/log.go |
Переписать на log/slog с кастомным JSON handler'ом, сохранить формат, добавить SetNopLogger() |
Средняя |
| 2 | tools/go.mod |
Удалить go.uber.org/zap из require |
Низкая |
| 3 | tools/vendor/ |
go mod tidy && go mod vendor |
Низкая |
После выпустить тег v3.9.0.
Шаг 1 — тесты с klog.SetLogger(zap.NewNop()) (критично)
Это единственные места, где внешний код завязан на тип *zap.Logger.
После обновления vendor без этих правок сервисы не скомпилируются.
Замена везде одинаковая: klog.SetLogger(zap.NewNop()) → klog.SetNopLogger()
| # | Репозиторий | Файл | Сложность |
|---|---|---|---|
| 4 | pilot |
internal/service/agreement_link_test.go |
Низкая |
| 5 | pilot |
internal/service/application_test.go |
Низкая |
| 6 | pilot |
internal/service/distro_download_link_test.go |
Низкая |
| 7 | pilot |
internal/service/distro_test.go |
Низкая |
| 8 | pilot |
internal/service/download_history_test.go |
Низкая |
| 9 | pilot |
internal/service/download_link_test.go |
Низкая |
| 10 | pilot |
internal/service/mail_test.go |
Низкая |
| 11 | hr |
internal/service/department_test.go |
Низкая |
| 12 | rubytech |
internal/service/client_test.go |
Низкая |
| 13 | rubytech |
internal/service/customer_test.go |
Низкая |
| 14 | rubytech |
internal/service/project_test.go |
Низкая |
| 15 | user |
internal/private/graph/resolver_test.go |
Низкая |
| 16 | user |
internal/service/role_test.go |
Низкая |
| 17 | user |
internal/service/session_test.go |
Низкая |
| 18 | user |
internal/service/user_test.go |
Низкая |
Шаг 2 — vendor-бамп во всех сервисах (рутина, не требует изменений кода)
Для сервисов без SetLogger(zap.NewNop()) достаточно только обновить vendor.
Для pilot, hr, rubytech, user — сначала шаг 1, потом vendor-бамп.
go get gitlab.adsw.io/platform/tools/v3@v3.9.0
go mod vendor
| # | Репозиторий | Зависимость | Сложность |
|---|---|---|---|
| 19 | accounting |
— | Низкая |
| 20 | async |
— | Низкая |
| 21 | document-generator |
— | Низкая |
| 22 | education |
— | Низкая |
| 23 | file-manager |
— | Низкая |
| 24 | hr |
после п.11 | Низкая |
| 25 | mailer |
— | Низкая |
| 26 | partner |
— | Низкая |
| 27 | pilot |
после пп.4–10 | Низкая |
| 28 | private-api-gateway |
— | Низкая |
| 29 | public-api-gateway |
— | Низкая |
| 30 | rubytech |
после пп.12–14 | Низкая |
| 31 | store |
— | Низкая |
| 32 | unisender |
— | Низкая |
| 33 | user |
после пп.15–18 | Низкая |
| 34 | yandex |
— | Низкая |
Шаг 3 — api-gateway (отдельно, не блокирует остальное)
api-gateway держит *zap.Logger как поле структуры Application и передаёт его
в log.NewZapLogger() из библиотеки jensneuse/abstractlogger. Это независимо от klog
и не блокирует миграцию остальных сервисов.
| # | Файл | Проблема | Сложность |
|---|---|---|---|
| 35 | api-gateway/pkg/application/application.go |
Поле ZapLogger *zap.Logger — abstractlogger требует zap. Нужно либо оставить zap только здесь, либо заменить abstractlogger на slog-совместимый аналог |
Высокая |
| 36 | api-gateway/cmd/http/main.go |
klog.InitLogger() возвращает *zap.Logger сейчас, после миграции вернёт *slog.Logger — нужно отвязать инициализацию zap от klog |
Средняя |
Порядок выполнения
Шаг 0: tools/klog/log.go → slog + SetNopLogger() → тег v3.9.0
│
├─► Шаг 1: исправить 15 тестовых файлов (pilot, hr, rubytech, user)
│
└─► Шаг 2: vendor-бамп в каждом сервисе по мере готовности (в течение месяца)
│
└─► Шаг 3: api-gateway — решить вопрос с abstractlogger (отдельно)
Шаг 0 и Шаг 1 должны быть выполнены до того, как любой сервис обновит vendor.
| Сервис | Готовность | Пролит |
|---|---|---|
| accounting | ||
| async | ||
| document | ||
| education | ||
| file-manager | + | |
| hr | + | |
| mailer | ||
| partner | ||
| pilot | + | + |
| private | ||
| public | ||
| rubytech | + | |
| store | ||
| unisender | ||
| user | ||
| yandex | + | |
| api-gateway | ||
| mq-tools | ||
| tools | + | + |