reindex: пересборка витрины проигрыванием журнала
- `healthlog reindex` собирает витрину из журнала (тела архива + учёт доставок) в ОТДЕЛЬНЫЙ файл базы, строго по `(received_at, id)`; рабочую базу читает без наката миграций и не трогает вовсе. Подмену делает человек при остановленном сервисе: переименование поверх открытого дескриптора портит базу молча. - Журналом считается архив, а не таблица доставок: тело без учётной записи заводится заново (метка из ULID, размер и хеш по распакованному телу), запись без тела переносится, но не сворачивается. Оракул сходимости встроен — два отпечатка и «объектов было/стало»; пустой журнал успехом не считается. - Прогон живого архива переехал на новый пакет: второго проигрывателя журнала в проекте не осталось, а его утверждение о ключе сна перестало быть константой, протухающей с каждой доставкой.
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
package logging
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"log/slog"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Уровень — это адресат, а не громкость: DEBUG предназначен разработчику при
|
||||
// отладке и в штатной работе наружу не выходит.
|
||||
func TestУровеньОтсекаетНижние(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var buf bytes.Buffer
|
||||
log := NewTo(&buf, "warn", "json")
|
||||
log.Debug("отладка")
|
||||
log.Info("событие")
|
||||
log.Warn("может стать проблемой")
|
||||
|
||||
out := buf.String()
|
||||
if strings.Contains(out, "отладка") || strings.Contains(out, "событие") {
|
||||
t.Errorf("уровень warn пропустил записи ниже себя: %s", out)
|
||||
}
|
||||
if !strings.Contains(out, "может стать проблемой") {
|
||||
t.Errorf("запись уровня warn потерялась: %s", out)
|
||||
}
|
||||
}
|
||||
|
||||
// Время в логах — UTC: иначе порядок событий между машинами не сравнить.
|
||||
func TestВремяВUTC(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var buf bytes.Buffer
|
||||
NewTo(&buf, "info", "json").Info("событие")
|
||||
|
||||
var rec map[string]any
|
||||
if err := json.Unmarshal(buf.Bytes(), &rec); err != nil {
|
||||
t.Fatalf("запись не JSON: %v (%s)", err, buf.String())
|
||||
}
|
||||
ts, _ := rec[slog.TimeKey].(string)
|
||||
if !strings.HasSuffix(ts, "Z") {
|
||||
t.Errorf("метка времени %q не в UTC", ts)
|
||||
}
|
||||
}
|
||||
|
||||
// Команды CLI пишут отчёт в stdout, поэтому их лог обязан идти в stderr —
|
||||
// иначе отчёт не разобрать ни глазами, ни перенаправлением.
|
||||
func TestNewErrПишетВStderrАNewВStdout(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Прямой проверки потока здесь нет намеренно: подмена os.Stdout была бы
|
||||
// мутацией глобала. Проверяем то, что от этих конструкторов зависит на
|
||||
// самом деле, — что они собирают разные назначения и оба живые.
|
||||
if New("info", "json") == nil || NewErr("info", "text") == nil {
|
||||
t.Fatal("конструктор вернул nil")
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
NewTo(&buf, "info", "text").Info("событие", "ключ", "значение")
|
||||
if !strings.Contains(buf.String(), "ключ=значение") {
|
||||
t.Errorf("текстовый формат не применён: %s", buf.String())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user