- `healthlog reindex` собирает витрину из журнала (тела архива + учёт доставок) в ОТДЕЛЬНЫЙ файл базы, строго по `(received_at, id)`; рабочую базу читает без наката миграций и не трогает вовсе. Подмену делает человек при остановленном сервисе: переименование поверх открытого дескриптора портит базу молча. - Журналом считается архив, а не таблица доставок: тело без учётной записи заводится заново (метка из ULID, размер и хеш по распакованному телу), запись без тела переносится, но не сворачивается. Оракул сходимости встроен — два отпечатка и «объектов было/стало»; пустой журнал успехом не считается. - Прогон живого архива переехал на новый пакет: второго проигрывателя журнала в проекте не осталось, а его утверждение о ключе сна перестало быть константой, протухающей с каждой доставкой.
66 lines
2.5 KiB
Go
66 lines
2.5 KiB
Go
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())
|
||
}
|
||
}
|