Files
dygou_go/internal/logger/logger_test.go
T
yml2213 b72a58fd57 重构: 日志系统结构化,消除字符串反解析
旧链路:LogEvent→log.Print→logFileWriter→MemoryLogStore.Write
再用 ParseLogLine 字符串切割把分类/级别/账号反解析回来,格式一调就崩。

新链路:LogEvent 直构 LogEntry(Time/Level/Category/Account 直接填值),
经 sink 分发到内存(同步)/文件(异步)/终端。Message 仍用 formatLogLine
渲染保持文件文本格式与前端清理逻辑兼容。

- MemoryLogStore 新增 Append(entry) 替代 Write 的字符串解析路径
- 删除 logFileWriter,新增 sink 统一管理三路输出
- 删除 log.SetOutput/log.Print 依赖
- 保留 ParseLogLine/DetectLogLevel/formatLogLine(AsyncLogWriter 丢弃
  统计与测试仍用),但 log_store 不再调用
- 新增 TestStructuredLogging 覆盖 LogEvent→sink→Append 链路与 minLevel 过滤
2026-07-29 12:52:26 +08:00

151 lines
4.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package logger
import (
"strings"
"testing"
)
func TestIsLogLevel(t *testing.T) {
valid := []string{"success", "error", "warn", "info", "debug", "SUCCESS", "Warn"}
for _, l := range valid {
if !IsLogLevel(l) {
t.Errorf("IsLogLevel(%q) = false, want true", l)
}
}
invalid := []string{"fatal", "trace", "", "warning"}
for _, l := range invalid {
if IsLogLevel(l) {
t.Errorf("IsLogLevel(%q) = true, want false", l)
}
}
}
func TestDetectLogLevel(t *testing.T) {
cases := []struct {
line string
want string
}{
{"[2026/01/01 00:00:00.000][网络][debug] 调试", "debug"},
{"[2026/01/01 00:00:00.000][抢购][success] 兑换成功", "success"},
{"[2026/01/01 00:00:00.000][系统][error] 失败", "error"},
{"[2026/01/01 00:00:00.000][系统][warn] 跳过", "warn"},
{"[2026/01/01 00:00:00.000][系统][info] 信息", "info"},
{"兑换成功完成", "success"},
{"网络请求失败 error 发生", "error"},
{"配置降级处理 warn", "warn"},
{"普通信息日志", "info"},
}
for _, c := range cases {
if got := DetectLogLevel(c.line); got != c.want {
t.Errorf("DetectLogLevel(%q) = %q, want %q", c.line, got, c.want)
}
}
}
func TestParseLogLine(t *testing.T) {
line := "2026/01/01 12:00:00.123 [抢购][success] 账号:testuser 兑换成功"
meta := ParseLogLine(line)
if meta.Time != "2026-01-01 12:00:00.123" {
t.Errorf("ParseLogLine Time = %q, want '2026-01-01 12:00:00.123'", meta.Time)
}
if meta.Category != "抢购" {
t.Errorf("ParseLogLine Category = %q, want '抢购'", meta.Category)
}
if meta.Level != "success" {
t.Errorf("ParseLogLine Level = %q, want 'success'", meta.Level)
}
if meta.Account != "testuser" {
t.Errorf("ParseLogLine Account = %q, want 'testuser'", meta.Account)
}
}
func TestParseLogLineShort(t *testing.T) {
// 不含时间的行,ParseLogLine 不应 panic,Time 应为空
line := "[系统][info] 配置已加载"
meta := ParseLogLine(line)
if meta.Category != "系统" {
t.Errorf("ParseLogLine Category = %q, want '系统'", meta.Category)
}
if meta.Level != "info" {
t.Errorf("ParseLogLine Level = %q, want 'info'", meta.Level)
}
if meta.Time != "" {
t.Errorf("ParseLogLine Time = %q, want empty", meta.Time)
}
}
func TestParseLogMeta(t *testing.T) {
cat, lvl := ParseLogMeta("[网络][warn] 降级")
if cat != "网络" || lvl != "warn" {
t.Errorf("ParseLogMeta = (%q, %q), want ('网络', 'warn')", cat, lvl)
}
}
// TestStructuredLogging 验证 LogEvent→sink→MemoryLogStore 结构化链路:
// LogEntry 的 category/level/account 应直接由调用方填入,而非字符串反解析。
func TestStructuredLogging(t *testing.T) {
store := NewMemoryLogStore(100)
cleanup := InitLogging(store, t.TempDir())
defer func() {
cleanup()
globalSink = nil
}()
LogEvent("抢购", "success", "兑换成功: %s", "商品A")
LogAccountEvent("抢购", "warn", "testuser", "冷却: %ds", 5)
entries := store.Since(0)
// InitLogging 自身产生 1 条"日志文件已启用",加上上述 2 条,共 3 条
if len(entries) != 3 {
t.Fatalf("Since(0) = %d entries, want 3", len(entries))
}
if entries[0].Category != "日志" {
t.Errorf("entry0 Category = %q, want '日志' (InitLogging 启动日志)", entries[0].Category)
}
e1 := entries[1]
if e1.ID != 2 {
t.Errorf("entry1 ID = %d, want 2", e1.ID)
}
if e1.Category != "抢购" {
t.Errorf("entry1 Category = %q, want '抢购'", e1.Category)
}
if e1.Level != "success" {
t.Errorf("entry1 Level = %q, want 'success'", e1.Level)
}
if e1.Account != "" {
t.Errorf("entry1 Account = %q, want empty", e1.Account)
}
if e1.Time == "" {
t.Error("entry1 Time is empty")
}
if !strings.Contains(e1.Message, "[抢购][success]") {
t.Errorf("entry1 Message = %q, want contains '[抢购][success]'", e1.Message)
}
if !strings.Contains(e1.Message, "兑换成功: 商品A") {
t.Errorf("entry1 Message = %q, want contains '兑换成功: 商品A'", e1.Message)
}
e2 := entries[2]
if e2.ID != 3 {
t.Errorf("entry2 ID = %d, want 3", e2.ID)
}
if e2.Account != "testuser" {
t.Errorf("entry2 Account = %q, want 'testuser'", e2.Account)
}
if !strings.Contains(e2.Message, "[账号:testuser]") {
t.Errorf("entry2 Message = %q, want contains '[账号:testuser]'", e2.Message)
}
if !strings.Contains(e2.Message, "冷却: 5s") {
t.Errorf("entry2 Message = %q, want contains '冷却: 5s'", e2.Message)
}
// 验证 minLevel 过滤:设为 error 后 info 日志不应进入内存
SetMinLevel("error")
LogEvent("测试", "info", "这条应被过滤")
if got := store.Since(e2.ID); len(got) != 0 {
t.Errorf("after SetMinLevel(error), Since(%d) = %d entries, want 0", e2.ID, len(got))
}
SetMinLevel("info") // 恢复,避免影响后续测试
}