package loggerx_test import ( "context" "encoding/json" "os" "path/filepath" "strings" "testing" "github.com/yuninks/loggerx" ) func readOne(t *testing.T, dir, file string) string { t.Helper() files, _ := filepath.Glob(filepath.Join(dir, "*_"+file)) if len(files) == 0 { files, _ = filepath.Glob(filepath.Join(dir, file)) } if len(files) == 0 { t.Fatalf("没找到 %s", file) } b, err := os.ReadFile(files[0]) if err != nil { t.Fatal(err) } return string(b) } // 默认(不调用 SetFormat)必须是合法 JSON —— 与 readme 的承诺一致 func TestDefaultFormatIsJSON(t *testing.T) { dir := t.TempDir() l := loggerx.NewLogger(context.Background(), loggerx.SetDir(dir)) l.Info(context.Background(), "hello") if err := l.Close(); err != nil { t.Fatal(err) } line := strings.TrimSpace(readOne(t, dir, "info.log")) var v map[string]any if err := json.Unmarshal([]byte(line), &v); err != nil { t.Fatalf("默认格式不是合法 JSON: %v\n%s", err, line) } if v["level"] != "info" { t.Errorf("level=%v", v["level"]) } } // SetFormat(FormatText) 之后输出单行文本,且仍是一行一条 func TestTextFormat(t *testing.T) { dir := t.TempDir() l := loggerx.NewLogger(context.Background(), loggerx.SetDir(dir), loggerx.SetFormat(loggerx.FormatText), ) ctx := context.Background() l.Info(ctx, "hello text") l.Errorf(ctx, "boom %d", 42) if err := l.Close(); err != nil { t.Fatal(err) } info := strings.TrimSpace(readOne(t, dir, "info.log")) if info == "" { t.Fatal("text 格式没有输出") } for _, want := range []string{"level=info", "time=", "file=", "content="} { if !strings.Contains(info, want) { t.Errorf("text 输出缺少 %q: %s", want, info) } } if strings.HasPrefix(info, "{") { t.Errorf("SetFormat(text) 之后不应还是 JSON: %s", info) } if strings.Count(info, "\n") != 0 { t.Errorf("一条日志必须占一行: %q", info) } errLine := strings.TrimSpace(readOne(t, dir, "error.log")) if !strings.Contains(errLine, "level=error") || !strings.Contains(errLine, "42") { t.Errorf("error 的 text 输出不正确: %s", errLine) } } // 内容里有空格/引号时,text 格式用引号包起来,避免歧义 func TestTextFormatQuotesAmbiguousValues(t *testing.T) { dir := t.TempDir() l := loggerx.NewLogger(context.Background(), loggerx.SetDir(dir), loggerx.SetFormat(loggerx.FormatText), ) l.Info(context.Background(), "has space and \"quote\"") if err := l.Close(); err != nil { t.Fatal(err) } line := strings.TrimSpace(readOne(t, dir, "info.log")) if !strings.Contains(line, `content="`) { t.Errorf("含空格/引号的值应当被引号包裹: %s", line) } } // SetFormat 传非法值时应保持默认,而不是切到一个没实现的格式 func TestInvalidFormatKeepsDefault(t *testing.T) { dir := t.TempDir() l := loggerx.NewLogger(context.Background(), loggerx.SetDir(dir), loggerx.SetFormat(loggerx.Format("yaml")), ) l.Info(context.Background(), "still-json") if err := l.Close(); err != nil { t.Fatal(err) } line := strings.TrimSpace(readOne(t, dir, "info.log")) var v map[string]any if err := json.Unmarshal([]byte(line), &v); err != nil { t.Errorf("非法格式值不该改变默认行为: %v\n%s", err, line) } } // SetPrefix 必须真的加上前缀(两种格式都生效) func TestPrefixApplied(t *testing.T) { dir := t.TempDir() l := loggerx.NewLogger(context.Background(), loggerx.SetDir(dir), loggerx.SetPrefix("[order-svc] "), ) l.Info(context.Background(), "with-prefix") if err := l.Close(); err != nil { t.Fatal(err) } line := readOne(t, dir, "info.log") if !strings.HasPrefix(line, "[order-svc] ") { t.Errorf("前缀未生效: %q", line) } // 前缀之后仍然要是合法 JSON body := strings.TrimSpace(strings.TrimPrefix(line, "[order-svc] ")) var v map[string]any if err := json.Unmarshal([]byte(body), &v); err != nil { t.Errorf("前缀后的内容不是合法 JSON: %v\n%s", err, body) } } // text 格式 + 前缀 + expandData 组合 func TestTextFormatWithPrefixAndExpand(t *testing.T) { dir := t.TempDir() l := loggerx.NewLogger(context.Background(), loggerx.SetDir(dir), loggerx.SetFormat(loggerx.FormatText), loggerx.SetPrefix("P|"), loggerx.SetExpandData("app", "order"), ) l.Info(context.Background(), "combo") if err := l.Close(); err != nil { t.Fatal(err) } line := strings.TrimSpace(readOne(t, dir, "info.log")) if !strings.HasPrefix(line, "P|level=info") { t.Errorf("前缀/格式组合输出不正确: %s", line) } if !strings.Contains(line, "app=order") { t.Errorf("expandData 未出现在 text 输出里: %s", line) } } // text 格式下不可序列化的内容也不能丢 func TestTextFormatUnmarshalableContent(t *testing.T) { dir := t.TempDir() l := loggerx.NewLogger(context.Background(), loggerx.SetDir(dir), loggerx.SetFormat(loggerx.FormatText), ) l.Info(context.Background(), make(chan int), func() {}) l.Info(context.Background(), "after") if err := l.Close(); err != nil { t.Fatal(err) } content := readOne(t, dir, "info.log") lines := strings.Split(strings.TrimSpace(content), "\n") if len(lines) != 2 { t.Fatalf("期望 2 行,实际 %d: %q", len(lines), content) } for i, line := range lines { if !strings.Contains(line, "content=") { t.Errorf("第 %d 行缺少 content: %s", i+1, line) } } }