logger_test.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. package logger
  2. import (
  3. "encoding/json"
  4. "io"
  5. "os"
  6. "path/filepath"
  7. "strings"
  8. "testing"
  9. )
  10. func TestInit_DualOutput(t *testing.T) {
  11. // Use os.MkdirTemp instead of t.TempDir to avoid cleanup failures
  12. // when lumberjack holds file handles on Windows.
  13. tmpDir, err := os.MkdirTemp("", "logger-test-*")
  14. if err != nil {
  15. t.Fatalf("create temp dir: %v", err)
  16. }
  17. t.Cleanup(func() { _ = os.RemoveAll(tmpDir) })
  18. logPath := filepath.Join(tmpDir, "logs", "sub2api.log")
  19. origStdout := os.Stdout
  20. origStderr := os.Stderr
  21. stdoutR, stdoutW, err := os.Pipe()
  22. if err != nil {
  23. t.Fatalf("create stdout pipe: %v", err)
  24. }
  25. stderrR, stderrW, err := os.Pipe()
  26. if err != nil {
  27. t.Fatalf("create stderr pipe: %v", err)
  28. }
  29. os.Stdout = stdoutW
  30. os.Stderr = stderrW
  31. t.Cleanup(func() {
  32. os.Stdout = origStdout
  33. os.Stderr = origStderr
  34. _ = stdoutR.Close()
  35. _ = stderrR.Close()
  36. _ = stdoutW.Close()
  37. _ = stderrW.Close()
  38. })
  39. err = Init(InitOptions{
  40. Level: "debug",
  41. Format: "json",
  42. ServiceName: "sub2api",
  43. Environment: "test",
  44. Output: OutputOptions{
  45. ToStdout: true,
  46. ToFile: true,
  47. FilePath: logPath,
  48. },
  49. Rotation: RotationOptions{
  50. MaxSizeMB: 10,
  51. MaxBackups: 2,
  52. MaxAgeDays: 1,
  53. },
  54. Sampling: SamplingOptions{Enabled: false},
  55. })
  56. if err != nil {
  57. t.Fatalf("Init() error: %v", err)
  58. }
  59. L().Info("dual-output-info")
  60. L().Warn("dual-output-warn")
  61. // Skip Sync() — on Windows, fsync on pipes deadlocks (FlushFileBuffers).
  62. // The log data is already in the pipe buffer; closing writers is sufficient.
  63. _ = stdoutW.Close()
  64. _ = stderrW.Close()
  65. stdoutBytes, _ := io.ReadAll(stdoutR)
  66. stderrBytes, _ := io.ReadAll(stderrR)
  67. stdoutText := string(stdoutBytes)
  68. stderrText := string(stderrBytes)
  69. if !strings.Contains(stdoutText, "dual-output-info") {
  70. t.Fatalf("stdout missing info log: %s", stdoutText)
  71. }
  72. if !strings.Contains(stderrText, "dual-output-warn") {
  73. t.Fatalf("stderr missing warn log: %s", stderrText)
  74. }
  75. fileBytes, err := os.ReadFile(logPath)
  76. if err != nil {
  77. t.Fatalf("read log file: %v", err)
  78. }
  79. fileText := string(fileBytes)
  80. if !strings.Contains(fileText, "dual-output-info") || !strings.Contains(fileText, "dual-output-warn") {
  81. t.Fatalf("file missing logs: %s", fileText)
  82. }
  83. }
  84. func TestInit_FileOutputFailureDowngrade(t *testing.T) {
  85. origStdout := os.Stdout
  86. origStderr := os.Stderr
  87. _, stdoutW, err := os.Pipe()
  88. if err != nil {
  89. t.Fatalf("create stdout pipe: %v", err)
  90. }
  91. stderrR, stderrW, err := os.Pipe()
  92. if err != nil {
  93. t.Fatalf("create stderr pipe: %v", err)
  94. }
  95. os.Stdout = stdoutW
  96. os.Stderr = stderrW
  97. t.Cleanup(func() {
  98. os.Stdout = origStdout
  99. os.Stderr = origStderr
  100. _ = stdoutW.Close()
  101. _ = stderrR.Close()
  102. _ = stderrW.Close()
  103. })
  104. err = Init(InitOptions{
  105. Level: "info",
  106. Format: "json",
  107. Output: OutputOptions{
  108. ToStdout: true,
  109. ToFile: true,
  110. FilePath: filepath.Join(os.DevNull, "logs", "sub2api.log"),
  111. },
  112. Rotation: RotationOptions{
  113. MaxSizeMB: 10,
  114. MaxBackups: 1,
  115. MaxAgeDays: 1,
  116. },
  117. })
  118. if err != nil {
  119. t.Fatalf("Init() should downgrade instead of failing, got: %v", err)
  120. }
  121. _ = stderrW.Close()
  122. stderrBytes, _ := io.ReadAll(stderrR)
  123. if !strings.Contains(string(stderrBytes), "日志文件输出初始化失败") {
  124. t.Fatalf("stderr should contain fallback warning, got: %s", string(stderrBytes))
  125. }
  126. }
  127. func TestInit_CallerShouldPointToCallsite(t *testing.T) {
  128. origStdout := os.Stdout
  129. origStderr := os.Stderr
  130. stdoutR, stdoutW, err := os.Pipe()
  131. if err != nil {
  132. t.Fatalf("create stdout pipe: %v", err)
  133. }
  134. _, stderrW, err := os.Pipe()
  135. if err != nil {
  136. t.Fatalf("create stderr pipe: %v", err)
  137. }
  138. os.Stdout = stdoutW
  139. os.Stderr = stderrW
  140. t.Cleanup(func() {
  141. os.Stdout = origStdout
  142. os.Stderr = origStderr
  143. _ = stdoutR.Close()
  144. _ = stdoutW.Close()
  145. _ = stderrW.Close()
  146. })
  147. if err := Init(InitOptions{
  148. Level: "info",
  149. Format: "json",
  150. ServiceName: "sub2api",
  151. Environment: "test",
  152. Caller: true,
  153. Output: OutputOptions{
  154. ToStdout: true,
  155. ToFile: false,
  156. },
  157. Sampling: SamplingOptions{Enabled: false},
  158. }); err != nil {
  159. t.Fatalf("Init() error: %v", err)
  160. }
  161. L().Info("caller-check")
  162. // Skip Sync() — on Windows, fsync on pipes deadlocks (FlushFileBuffers).
  163. os.Stdout = origStdout
  164. os.Stderr = origStderr
  165. _ = stdoutW.Close()
  166. logBytes, _ := io.ReadAll(stdoutR)
  167. var line string
  168. for _, item := range strings.Split(string(logBytes), "\n") {
  169. if strings.Contains(item, "caller-check") {
  170. line = item
  171. break
  172. }
  173. }
  174. if line == "" {
  175. t.Fatalf("log output missing caller-check: %s", string(logBytes))
  176. }
  177. var payload map[string]any
  178. if err := json.Unmarshal([]byte(line), &payload); err != nil {
  179. t.Fatalf("parse log json failed: %v, line=%s", err, line)
  180. }
  181. caller, _ := payload["caller"].(string)
  182. if !strings.Contains(caller, "logger_test.go:") {
  183. t.Fatalf("caller should point to this test file, got: %s", caller)
  184. }
  185. }