旧链路: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 过滤
151 lines
4.7 KiB
Go
151 lines
4.7 KiB
Go
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") // 恢复,避免影响后续测试
|
||
}
|