| package logger |
|
|
| import ( |
| "encoding/json" |
| "io" |
| "os" |
| "path/filepath" |
| "strings" |
| "testing" |
| ) |
|
|
| func TestInit_DualOutput(t *testing.T) { |
| tmpDir := t.TempDir() |
| logPath := filepath.Join(tmpDir, "logs", "sub2api.log") |
|
|
| origStdout := os.Stdout |
| origStderr := os.Stderr |
| stdoutR, stdoutW, err := os.Pipe() |
| if err != nil { |
| t.Fatalf("create stdout pipe: %v", err) |
| } |
| stderrR, stderrW, err := os.Pipe() |
| if err != nil { |
| t.Fatalf("create stderr pipe: %v", err) |
| } |
| os.Stdout = stdoutW |
| os.Stderr = stderrW |
| t.Cleanup(func() { |
| os.Stdout = origStdout |
| os.Stderr = origStderr |
| _ = stdoutR.Close() |
| _ = stderrR.Close() |
| _ = stdoutW.Close() |
| _ = stderrW.Close() |
| }) |
|
|
| err = Init(InitOptions{ |
| Level: "debug", |
| Format: "json", |
| ServiceName: "sub2api", |
| Environment: "test", |
| Output: OutputOptions{ |
| ToStdout: true, |
| ToFile: true, |
| FilePath: logPath, |
| }, |
| Rotation: RotationOptions{ |
| MaxSizeMB: 10, |
| MaxBackups: 2, |
| MaxAgeDays: 1, |
| }, |
| Sampling: SamplingOptions{Enabled: false}, |
| }) |
| if err != nil { |
| t.Fatalf("Init() error: %v", err) |
| } |
|
|
| L().Info("dual-output-info") |
| L().Warn("dual-output-warn") |
| Sync() |
|
|
| _ = stdoutW.Close() |
| _ = stderrW.Close() |
| stdoutBytes, _ := io.ReadAll(stdoutR) |
| stderrBytes, _ := io.ReadAll(stderrR) |
| stdoutText := string(stdoutBytes) |
| stderrText := string(stderrBytes) |
|
|
| if !strings.Contains(stdoutText, "dual-output-info") { |
| t.Fatalf("stdout missing info log: %s", stdoutText) |
| } |
| if !strings.Contains(stderrText, "dual-output-warn") { |
| t.Fatalf("stderr missing warn log: %s", stderrText) |
| } |
|
|
| fileBytes, err := os.ReadFile(logPath) |
| if err != nil { |
| t.Fatalf("read log file: %v", err) |
| } |
| fileText := string(fileBytes) |
| if !strings.Contains(fileText, "dual-output-info") || !strings.Contains(fileText, "dual-output-warn") { |
| t.Fatalf("file missing logs: %s", fileText) |
| } |
| } |
|
|
| func TestInit_FileOutputFailureDowngrade(t *testing.T) { |
| origStdout := os.Stdout |
| origStderr := os.Stderr |
| _, stdoutW, err := os.Pipe() |
| if err != nil { |
| t.Fatalf("create stdout pipe: %v", err) |
| } |
| stderrR, stderrW, err := os.Pipe() |
| if err != nil { |
| t.Fatalf("create stderr pipe: %v", err) |
| } |
| os.Stdout = stdoutW |
| os.Stderr = stderrW |
| t.Cleanup(func() { |
| os.Stdout = origStdout |
| os.Stderr = origStderr |
| _ = stdoutW.Close() |
| _ = stderrR.Close() |
| _ = stderrW.Close() |
| }) |
|
|
| err = Init(InitOptions{ |
| Level: "info", |
| Format: "json", |
| Output: OutputOptions{ |
| ToStdout: true, |
| ToFile: true, |
| FilePath: filepath.Join(os.DevNull, "logs", "sub2api.log"), |
| }, |
| Rotation: RotationOptions{ |
| MaxSizeMB: 10, |
| MaxBackups: 1, |
| MaxAgeDays: 1, |
| }, |
| }) |
| if err != nil { |
| t.Fatalf("Init() should downgrade instead of failing, got: %v", err) |
| } |
|
|
| _ = stderrW.Close() |
| stderrBytes, _ := io.ReadAll(stderrR) |
| if !strings.Contains(string(stderrBytes), "日志文件输出初始化失败") { |
| t.Fatalf("stderr should contain fallback warning, got: %s", string(stderrBytes)) |
| } |
| } |
|
|
| func TestInit_CallerShouldPointToCallsite(t *testing.T) { |
| origStdout := os.Stdout |
| origStderr := os.Stderr |
| stdoutR, stdoutW, err := os.Pipe() |
| if err != nil { |
| t.Fatalf("create stdout pipe: %v", err) |
| } |
| _, stderrW, err := os.Pipe() |
| if err != nil { |
| t.Fatalf("create stderr pipe: %v", err) |
| } |
| os.Stdout = stdoutW |
| os.Stderr = stderrW |
| t.Cleanup(func() { |
| os.Stdout = origStdout |
| os.Stderr = origStderr |
| _ = stdoutR.Close() |
| _ = stdoutW.Close() |
| _ = stderrW.Close() |
| }) |
|
|
| if err := Init(InitOptions{ |
| Level: "info", |
| Format: "json", |
| ServiceName: "sub2api", |
| Environment: "test", |
| Caller: true, |
| Output: OutputOptions{ |
| ToStdout: true, |
| ToFile: false, |
| }, |
| Sampling: SamplingOptions{Enabled: false}, |
| }); err != nil { |
| t.Fatalf("Init() error: %v", err) |
| } |
|
|
| L().Info("caller-check") |
| Sync() |
| _ = stdoutW.Close() |
| logBytes, _ := io.ReadAll(stdoutR) |
|
|
| var line string |
| for _, item := range strings.Split(string(logBytes), "\n") { |
| if strings.Contains(item, "caller-check") { |
| line = item |
| break |
| } |
| } |
| if line == "" { |
| t.Fatalf("log output missing caller-check: %s", string(logBytes)) |
| } |
|
|
| var payload map[string]any |
| if err := json.Unmarshal([]byte(line), &payload); err != nil { |
| t.Fatalf("parse log json failed: %v, line=%s", err, line) |
| } |
| caller, _ := payload["caller"].(string) |
| if !strings.Contains(caller, "logger_test.go:") { |
| t.Fatalf("caller should point to this test file, got: %s", caller) |
| } |
| } |
|
|