logger_test.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. package logger
  2. import (
  3. "os"
  4. "path/filepath"
  5. "strconv"
  6. "strings"
  7. "testing"
  8. "time"
  9. )
  10. // TestNewLoggerNoFile creates a logger without file logging.
  11. func TestNewLoggerNoFile(t *testing.T) {
  12. l, err := NewLogger("test", "", false)
  13. if err != nil {
  14. t.Fatalf("NewLogger returned error: %v", err)
  15. }
  16. if l == nil {
  17. t.Fatal("NewLogger returned nil")
  18. }
  19. if l.LogToFile {
  20. t.Error("LogToFile should be false")
  21. }
  22. }
  23. // TestNewLoggerWithFile creates a logger that writes to a temp directory.
  24. func TestNewLoggerWithFile(t *testing.T) {
  25. dir := t.TempDir()
  26. l, err := NewLogger("myapp", dir, true)
  27. if err != nil {
  28. t.Fatalf("NewLogger returned error: %v", err)
  29. }
  30. if l == nil {
  31. t.Fatal("NewLogger returned nil")
  32. }
  33. defer l.Close()
  34. if !l.LogToFile {
  35. t.Error("LogToFile should be true")
  36. }
  37. if l.CurrentLogFile == "" {
  38. t.Error("CurrentLogFile should be set")
  39. }
  40. if !strings.HasPrefix(filepath.Base(l.CurrentLogFile), "myapp_") {
  41. t.Errorf("log filename prefix unexpected: %s", l.CurrentLogFile)
  42. }
  43. // Verify the file was created on disk.
  44. if _, err := os.Stat(l.CurrentLogFile); os.IsNotExist(err) {
  45. t.Errorf("log file not created at %s", l.CurrentLogFile)
  46. }
  47. }
  48. // TestNewLoggerCreatesDirectory verifies that a missing log directory is created.
  49. func TestNewLoggerCreatesDirectory(t *testing.T) {
  50. base := t.TempDir()
  51. dir := filepath.Join(base, "subdir", "logs")
  52. l, err := NewLogger("prefix", dir, true)
  53. if err != nil {
  54. t.Fatalf("NewLogger returned error: %v", err)
  55. }
  56. defer l.Close()
  57. if _, err := os.Stat(dir); os.IsNotExist(err) {
  58. t.Errorf("log directory was not created: %s", dir)
  59. }
  60. }
  61. // TestNewTmpLogger creates a non-persistent logger.
  62. func TestNewTmpLogger(t *testing.T) {
  63. l, err := NewTmpLogger()
  64. if err != nil {
  65. t.Fatalf("NewTmpLogger returned error: %v", err)
  66. }
  67. if l == nil {
  68. t.Fatal("NewTmpLogger returned nil")
  69. }
  70. if l.LogToFile {
  71. t.Error("LogToFile should be false for tmp logger")
  72. }
  73. }
  74. // TestPrintAndLog calls PrintAndLog and confirms it does not panic.
  75. func TestPrintAndLog(t *testing.T) {
  76. l, err := NewTmpLogger()
  77. if err != nil {
  78. t.Fatalf("NewTmpLogger error: %v", err)
  79. }
  80. // PrintAndLog must not panic even without a file.
  81. l.PrintAndLog("TestTitle", "test message", nil)
  82. // Give the goroutine a moment to finish.
  83. time.Sleep(20 * time.Millisecond)
  84. }
  85. // TestLogNoFile verifies that Log does nothing when LogToFile is false.
  86. func TestLogNoFile(t *testing.T) {
  87. l, err := NewTmpLogger()
  88. if err != nil {
  89. t.Fatalf("NewTmpLogger error: %v", err)
  90. }
  91. // Should not panic.
  92. l.Log("title", "message", nil)
  93. }
  94. // TestLogInfoWritesToFile verifies that an info log entry is written to the file.
  95. func TestLogInfoWritesToFile(t *testing.T) {
  96. dir := t.TempDir()
  97. l, err := NewLogger("test", dir, true)
  98. if err != nil {
  99. t.Fatalf("NewLogger error: %v", err)
  100. }
  101. defer l.Close()
  102. l.Log("MyTitle", "hello world", nil)
  103. data, err := os.ReadFile(l.CurrentLogFile)
  104. if err != nil {
  105. t.Fatalf("ReadFile error: %v", err)
  106. }
  107. content := string(data)
  108. if !strings.Contains(content, "[INFO]") {
  109. t.Errorf("expected [INFO] in log, got: %s", content)
  110. }
  111. if !strings.Contains(content, "hello world") {
  112. t.Errorf("expected message in log, got: %s", content)
  113. }
  114. }
  115. // TestLogErrorWritesToFile verifies that an error log entry includes [ERROR] and the error text.
  116. func TestLogErrorWritesToFile(t *testing.T) {
  117. dir := t.TempDir()
  118. l, err := NewLogger("test", dir, true)
  119. if err != nil {
  120. t.Fatalf("NewLogger error: %v", err)
  121. }
  122. defer l.Close()
  123. l.Log("ErrTitle", "something broke", os.ErrNotExist)
  124. data, err := os.ReadFile(l.CurrentLogFile)
  125. if err != nil {
  126. t.Fatalf("ReadFile error: %v", err)
  127. }
  128. content := string(data)
  129. if !strings.Contains(content, "[ERROR]") {
  130. t.Errorf("expected [ERROR] in log, got: %s", content)
  131. }
  132. if !strings.Contains(content, os.ErrNotExist.Error()) {
  133. t.Errorf("expected error text in log, got: %s", content)
  134. }
  135. }
  136. // TestValidateAndUpdateLogFilepath_NoChange checks that no rotation occurs when
  137. // the expected filepath matches the current one.
  138. func TestValidateAndUpdateLogFilepath_NoChange(t *testing.T) {
  139. dir := t.TempDir()
  140. l, err := NewLogger("rotate", dir, true)
  141. if err != nil {
  142. t.Fatalf("NewLogger error: %v", err)
  143. }
  144. defer l.Close()
  145. original := l.CurrentLogFile
  146. l.ValidateAndUpdateLogFilepath()
  147. if l.CurrentLogFile != original {
  148. t.Errorf("file path changed unexpectedly: %s -> %s", original, l.CurrentLogFile)
  149. }
  150. }
  151. // TestValidateAndUpdateLogFilepath_MonthChange simulates a month change by
  152. // manually setting CurrentLogFile to an old path and calling ValidateAndUpdate.
  153. func TestValidateAndUpdateLogFilepath_MonthChange(t *testing.T) {
  154. dir := t.TempDir()
  155. l, err := NewLogger("rotate", dir, true)
  156. if err != nil {
  157. t.Fatalf("NewLogger error: %v", err)
  158. }
  159. defer l.Close()
  160. // Force a "month change" by pointing CurrentLogFile at a stale path.
  161. l.CurrentLogFile = filepath.Join(dir, "rotate_1999-1.log")
  162. l.ValidateAndUpdateLogFilepath()
  163. // After the call, CurrentLogFile should now be the expected current path.
  164. expected := l.getLogFilepath()
  165. if l.CurrentLogFile != expected {
  166. t.Errorf("expected CurrentLogFile %s, got %s", expected, l.CurrentLogFile)
  167. }
  168. // Verify the new file exists on disk.
  169. if _, err := os.Stat(l.CurrentLogFile); os.IsNotExist(err) {
  170. t.Errorf("rotated log file not created: %s", l.CurrentLogFile)
  171. }
  172. }
  173. // TestClose verifies that Close can be called on a file-backed logger without panicking.
  174. func TestClose(t *testing.T) {
  175. dir := t.TempDir()
  176. l, err := NewLogger("close", dir, true)
  177. if err != nil {
  178. t.Fatalf("NewLogger error: %v", err)
  179. }
  180. l.Close() // must not panic
  181. }
  182. // TestGetLogFilepath checks that the generated log filename contains the year and month.
  183. func TestGetLogFilepath(t *testing.T) {
  184. l := &Logger{
  185. Prefix: "app",
  186. LogFolder: "/tmp",
  187. }
  188. year, month, _ := time.Now().Date()
  189. path := l.getLogFilepath()
  190. if !strings.Contains(path, "app_") {
  191. t.Errorf("expected prefix in path: %s", path)
  192. }
  193. if !strings.Contains(path, strconv.Itoa(year)) {
  194. t.Errorf("expected year in path: %s", path)
  195. }
  196. if !strings.Contains(path, strconv.Itoa(int(month))) {
  197. t.Errorf("expected month in path: %s", path)
  198. }
  199. }