rotatelogger_test.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599
  1. package logx
  2. import (
  3. "errors"
  4. "io"
  5. "os"
  6. "path"
  7. "path/filepath"
  8. "sync/atomic"
  9. "syscall"
  10. "testing"
  11. "time"
  12. "github.com/stretchr/testify/assert"
  13. "github.com/zeromicro/go-zero/core/fs"
  14. "github.com/zeromicro/go-zero/core/stringx"
  15. )
  16. func TestDailyRotateRuleMarkRotated(t *testing.T) {
  17. t.Run("daily rule", func(t *testing.T) {
  18. var rule DailyRotateRule
  19. rule.MarkRotated()
  20. assert.Equal(t, getNowDate(), rule.rotatedTime)
  21. })
  22. t.Run("daily rule", func(t *testing.T) {
  23. rule := DefaultRotateRule("test", "-", 1, false)
  24. _, ok := rule.(*DailyRotateRule)
  25. assert.True(t, ok)
  26. })
  27. }
  28. func TestDailyRotateRuleOutdatedFiles(t *testing.T) {
  29. t.Run("no files", func(t *testing.T) {
  30. var rule DailyRotateRule
  31. assert.Empty(t, rule.OutdatedFiles())
  32. rule.days = 1
  33. assert.Empty(t, rule.OutdatedFiles())
  34. rule.gzip = true
  35. assert.Empty(t, rule.OutdatedFiles())
  36. })
  37. t.Run("bad files", func(t *testing.T) {
  38. rule := DailyRotateRule{
  39. filename: "[a-z",
  40. }
  41. assert.Empty(t, rule.OutdatedFiles())
  42. rule.days = 1
  43. assert.Empty(t, rule.OutdatedFiles())
  44. rule.gzip = true
  45. assert.Empty(t, rule.OutdatedFiles())
  46. })
  47. t.Run("temp files", func(t *testing.T) {
  48. boundary := time.Now().Add(-time.Hour * time.Duration(hoursPerDay) * 2).Format(dateFormat)
  49. f1, err := os.CreateTemp(os.TempDir(), "go-zero-test-"+boundary)
  50. assert.NoError(t, err)
  51. _ = f1.Close()
  52. f2, err := os.CreateTemp(os.TempDir(), "go-zero-test-"+boundary)
  53. assert.NoError(t, err)
  54. _ = f2.Close()
  55. t.Cleanup(func() {
  56. _ = os.Remove(f1.Name())
  57. _ = os.Remove(f2.Name())
  58. })
  59. rule := DailyRotateRule{
  60. filename: path.Join(os.TempDir(), "go-zero-test-"),
  61. days: 1,
  62. }
  63. assert.NotEmpty(t, rule.OutdatedFiles())
  64. })
  65. }
  66. func TestDailyRotateRuleShallRotate(t *testing.T) {
  67. var rule DailyRotateRule
  68. rule.rotatedTime = time.Now().Add(time.Hour * 24).Format(dateFormat)
  69. assert.True(t, rule.ShallRotate(0))
  70. }
  71. func TestSizeLimitRotateRuleMarkRotated(t *testing.T) {
  72. t.Run("size limit rule", func(t *testing.T) {
  73. var rule SizeLimitRotateRule
  74. rule.MarkRotated()
  75. assert.Equal(t, getNowDateInRFC3339Format(), rule.rotatedTime)
  76. })
  77. t.Run("size limit rule", func(t *testing.T) {
  78. rule := NewSizeLimitRotateRule("foo", "-", 1, 1, 1, false)
  79. rule.MarkRotated()
  80. assert.Equal(t, getNowDateInRFC3339Format(), rule.(*SizeLimitRotateRule).rotatedTime)
  81. })
  82. }
  83. func TestSizeLimitRotateRuleOutdatedFiles(t *testing.T) {
  84. t.Run("no files", func(t *testing.T) {
  85. var rule SizeLimitRotateRule
  86. assert.Empty(t, rule.OutdatedFiles())
  87. rule.days = 1
  88. assert.Empty(t, rule.OutdatedFiles())
  89. rule.gzip = true
  90. assert.Empty(t, rule.OutdatedFiles())
  91. rule.maxBackups = 0
  92. assert.Empty(t, rule.OutdatedFiles())
  93. })
  94. t.Run("bad files", func(t *testing.T) {
  95. rule := SizeLimitRotateRule{
  96. DailyRotateRule: DailyRotateRule{
  97. filename: "[a-z",
  98. },
  99. }
  100. assert.Empty(t, rule.OutdatedFiles())
  101. rule.days = 1
  102. assert.Empty(t, rule.OutdatedFiles())
  103. rule.gzip = true
  104. assert.Empty(t, rule.OutdatedFiles())
  105. })
  106. t.Run("temp files", func(t *testing.T) {
  107. boundary := time.Now().Add(-time.Hour * time.Duration(hoursPerDay) * 2).Format(dateFormat)
  108. f1, err := os.CreateTemp(os.TempDir(), "go-zero-test-"+boundary)
  109. assert.NoError(t, err)
  110. f2, err := os.CreateTemp(os.TempDir(), "go-zero-test-"+boundary)
  111. assert.NoError(t, err)
  112. boundary1 := time.Now().Add(time.Hour * time.Duration(hoursPerDay) * 2).Format(dateFormat)
  113. f3, err := os.CreateTemp(os.TempDir(), "go-zero-test-"+boundary1)
  114. assert.NoError(t, err)
  115. t.Cleanup(func() {
  116. _ = f1.Close()
  117. _ = os.Remove(f1.Name())
  118. _ = f2.Close()
  119. _ = os.Remove(f2.Name())
  120. _ = f3.Close()
  121. _ = os.Remove(f3.Name())
  122. })
  123. rule := SizeLimitRotateRule{
  124. DailyRotateRule: DailyRotateRule{
  125. filename: path.Join(os.TempDir(), "go-zero-test-"),
  126. days: 1,
  127. },
  128. maxBackups: 3,
  129. }
  130. assert.NotEmpty(t, rule.OutdatedFiles())
  131. })
  132. t.Run("no backups", func(t *testing.T) {
  133. boundary := time.Now().Add(-time.Hour * time.Duration(hoursPerDay) * 2).Format(dateFormat)
  134. f1, err := os.CreateTemp(os.TempDir(), "go-zero-test-"+boundary)
  135. assert.NoError(t, err)
  136. f2, err := os.CreateTemp(os.TempDir(), "go-zero-test-"+boundary)
  137. assert.NoError(t, err)
  138. boundary1 := time.Now().Add(time.Hour * time.Duration(hoursPerDay) * 2).Format(dateFormat)
  139. f3, err := os.CreateTemp(os.TempDir(), "go-zero-test-"+boundary1)
  140. assert.NoError(t, err)
  141. t.Cleanup(func() {
  142. _ = f1.Close()
  143. _ = os.Remove(f1.Name())
  144. _ = f2.Close()
  145. _ = os.Remove(f2.Name())
  146. _ = f3.Close()
  147. _ = os.Remove(f3.Name())
  148. })
  149. rule := SizeLimitRotateRule{
  150. DailyRotateRule: DailyRotateRule{
  151. filename: path.Join(os.TempDir(), "go-zero-test-"),
  152. days: 1,
  153. },
  154. }
  155. assert.NotEmpty(t, rule.OutdatedFiles())
  156. logger := new(RotateLogger)
  157. logger.rule = &rule
  158. logger.maybeDeleteOutdatedFiles()
  159. assert.Empty(t, rule.OutdatedFiles())
  160. })
  161. }
  162. func TestSizeLimitRotateRuleShallRotate(t *testing.T) {
  163. var rule SizeLimitRotateRule
  164. rule.rotatedTime = time.Now().Add(time.Hour * 24).Format(fileTimeFormat)
  165. rule.maxSize = 0
  166. assert.False(t, rule.ShallRotate(0))
  167. rule.maxSize = 100
  168. assert.False(t, rule.ShallRotate(0))
  169. assert.True(t, rule.ShallRotate(101*megaBytes))
  170. }
  171. func TestRotateLoggerClose(t *testing.T) {
  172. t.Run("close", func(t *testing.T) {
  173. filename, err := fs.TempFilenameWithText("foo")
  174. assert.Nil(t, err)
  175. if len(filename) > 0 {
  176. defer os.Remove(filename)
  177. }
  178. logger, err := NewLogger(filename, new(DailyRotateRule), false)
  179. assert.Nil(t, err)
  180. _, err = logger.Write([]byte("foo"))
  181. assert.Nil(t, err)
  182. assert.Nil(t, logger.Close())
  183. })
  184. t.Run("close and write", func(t *testing.T) {
  185. logger := new(RotateLogger)
  186. logger.done = make(chan struct{})
  187. close(logger.done)
  188. _, err := logger.Write([]byte("foo"))
  189. assert.ErrorIs(t, err, ErrLogFileClosed)
  190. })
  191. }
  192. func TestRotateLoggerGetBackupFilename(t *testing.T) {
  193. filename, err := fs.TempFilenameWithText("foo")
  194. assert.Nil(t, err)
  195. if len(filename) > 0 {
  196. defer os.Remove(filename)
  197. }
  198. logger, err := NewLogger(filename, new(DailyRotateRule), false)
  199. assert.Nil(t, err)
  200. assert.True(t, len(logger.getBackupFilename()) > 0)
  201. logger.backup = ""
  202. assert.True(t, len(logger.getBackupFilename()) > 0)
  203. }
  204. func TestRotateLoggerMayCompressFile(t *testing.T) {
  205. old := os.Stdout
  206. os.Stdout = os.NewFile(0, os.DevNull)
  207. defer func() {
  208. os.Stdout = old
  209. }()
  210. filename, err := fs.TempFilenameWithText("foo")
  211. assert.Nil(t, err)
  212. if len(filename) > 0 {
  213. defer os.Remove(filename)
  214. }
  215. logger, err := NewLogger(filename, new(DailyRotateRule), false)
  216. assert.Nil(t, err)
  217. logger.maybeCompressFile(filename)
  218. _, err = os.Stat(filename)
  219. assert.Nil(t, err)
  220. }
  221. func TestRotateLoggerMayCompressFileTrue(t *testing.T) {
  222. old := os.Stdout
  223. os.Stdout = os.NewFile(0, os.DevNull)
  224. defer func() {
  225. os.Stdout = old
  226. }()
  227. filename, err := fs.TempFilenameWithText("foo")
  228. assert.Nil(t, err)
  229. logger, err := NewLogger(filename, new(DailyRotateRule), true)
  230. assert.Nil(t, err)
  231. if len(filename) > 0 {
  232. defer os.Remove(filepath.Base(logger.getBackupFilename()) + ".gz")
  233. }
  234. logger.maybeCompressFile(filename)
  235. _, err = os.Stat(filename)
  236. assert.NotNil(t, err)
  237. }
  238. func TestRotateLoggerRotate(t *testing.T) {
  239. filename, err := fs.TempFilenameWithText("foo")
  240. assert.Nil(t, err)
  241. logger, err := NewLogger(filename, new(DailyRotateRule), true)
  242. assert.Nil(t, err)
  243. if len(filename) > 0 {
  244. defer func() {
  245. os.Remove(logger.getBackupFilename())
  246. os.Remove(filepath.Base(logger.getBackupFilename()) + ".gz")
  247. }()
  248. }
  249. err = logger.rotate()
  250. switch v := err.(type) {
  251. case *os.LinkError:
  252. // avoid rename error on docker container
  253. assert.Equal(t, syscall.EXDEV, v.Err)
  254. case *os.PathError:
  255. // ignore remove error for tests,
  256. // files are cleaned in GitHub actions.
  257. assert.Equal(t, "remove", v.Op)
  258. default:
  259. assert.Nil(t, err)
  260. }
  261. }
  262. func TestRotateLoggerWrite(t *testing.T) {
  263. filename, err := fs.TempFilenameWithText("foo")
  264. assert.Nil(t, err)
  265. rule := new(DailyRotateRule)
  266. logger, err := NewLogger(filename, rule, true)
  267. assert.Nil(t, err)
  268. if len(filename) > 0 {
  269. defer func() {
  270. os.Remove(logger.getBackupFilename())
  271. os.Remove(filepath.Base(logger.getBackupFilename()) + ".gz")
  272. }()
  273. }
  274. // the following write calls cannot be changed to Write, because of DATA RACE.
  275. logger.write([]byte(`foo`))
  276. rule.rotatedTime = time.Now().Add(-time.Hour * 24).Format(dateFormat)
  277. logger.write([]byte(`bar`))
  278. logger.Close()
  279. logger.write([]byte(`baz`))
  280. }
  281. func TestLogWriterClose(t *testing.T) {
  282. assert.Nil(t, newLogWriter(nil).Close())
  283. }
  284. func TestRotateLoggerWithSizeLimitRotateRuleClose(t *testing.T) {
  285. filename, err := fs.TempFilenameWithText("foo")
  286. assert.Nil(t, err)
  287. if len(filename) > 0 {
  288. defer os.Remove(filename)
  289. }
  290. logger, err := NewLogger(filename, new(SizeLimitRotateRule), false)
  291. assert.Nil(t, err)
  292. _ = logger.Close()
  293. }
  294. func TestRotateLoggerGetBackupWithSizeLimitRotateRuleFilename(t *testing.T) {
  295. filename, err := fs.TempFilenameWithText("foo")
  296. assert.Nil(t, err)
  297. if len(filename) > 0 {
  298. defer os.Remove(filename)
  299. }
  300. logger, err := NewLogger(filename, new(SizeLimitRotateRule), false)
  301. assert.Nil(t, err)
  302. assert.True(t, len(logger.getBackupFilename()) > 0)
  303. logger.backup = ""
  304. assert.True(t, len(logger.getBackupFilename()) > 0)
  305. }
  306. func TestRotateLoggerWithSizeLimitRotateRuleMayCompressFile(t *testing.T) {
  307. old := os.Stdout
  308. os.Stdout = os.NewFile(0, os.DevNull)
  309. defer func() {
  310. os.Stdout = old
  311. }()
  312. filename, err := fs.TempFilenameWithText("foo")
  313. assert.Nil(t, err)
  314. if len(filename) > 0 {
  315. defer os.Remove(filename)
  316. }
  317. logger, err := NewLogger(filename, new(SizeLimitRotateRule), false)
  318. assert.Nil(t, err)
  319. logger.maybeCompressFile(filename)
  320. _, err = os.Stat(filename)
  321. assert.Nil(t, err)
  322. }
  323. func TestRotateLoggerWithSizeLimitRotateRuleMayCompressFileTrue(t *testing.T) {
  324. old := os.Stdout
  325. os.Stdout = os.NewFile(0, os.DevNull)
  326. defer func() {
  327. os.Stdout = old
  328. }()
  329. filename, err := fs.TempFilenameWithText("foo")
  330. assert.Nil(t, err)
  331. logger, err := NewLogger(filename, new(SizeLimitRotateRule), true)
  332. assert.Nil(t, err)
  333. if len(filename) > 0 {
  334. defer os.Remove(filepath.Base(logger.getBackupFilename()) + ".gz")
  335. }
  336. logger.maybeCompressFile(filename)
  337. _, err = os.Stat(filename)
  338. assert.NotNil(t, err)
  339. }
  340. func TestRotateLoggerWithSizeLimitRotateRuleMayCompressFileFailed(t *testing.T) {
  341. old := os.Stdout
  342. os.Stdout = os.NewFile(0, os.DevNull)
  343. defer func() {
  344. os.Stdout = old
  345. }()
  346. filename := stringx.RandId()
  347. logger, err := NewLogger(filename, new(SizeLimitRotateRule), true)
  348. defer os.Remove(filename)
  349. if assert.NoError(t, err) {
  350. assert.NotPanics(t, func() {
  351. logger.maybeCompressFile(stringx.RandId())
  352. })
  353. }
  354. }
  355. func TestRotateLoggerWithSizeLimitRotateRuleRotate(t *testing.T) {
  356. filename, err := fs.TempFilenameWithText("foo")
  357. assert.Nil(t, err)
  358. logger, err := NewLogger(filename, new(SizeLimitRotateRule), true)
  359. assert.Nil(t, err)
  360. if len(filename) > 0 {
  361. defer func() {
  362. os.Remove(logger.getBackupFilename())
  363. os.Remove(filepath.Base(logger.getBackupFilename()) + ".gz")
  364. }()
  365. }
  366. err = logger.rotate()
  367. switch v := err.(type) {
  368. case *os.LinkError:
  369. // avoid rename error on docker container
  370. assert.Equal(t, syscall.EXDEV, v.Err)
  371. case *os.PathError:
  372. // ignore remove error for tests,
  373. // files are cleaned in GitHub actions.
  374. assert.Equal(t, "remove", v.Op)
  375. default:
  376. assert.Nil(t, err)
  377. }
  378. }
  379. func TestRotateLoggerWithSizeLimitRotateRuleWrite(t *testing.T) {
  380. filename, err := fs.TempFilenameWithText("foo")
  381. assert.Nil(t, err)
  382. rule := new(SizeLimitRotateRule)
  383. logger, err := NewLogger(filename, rule, true)
  384. assert.Nil(t, err)
  385. if len(filename) > 0 {
  386. defer func() {
  387. os.Remove(logger.getBackupFilename())
  388. os.Remove(filepath.Base(logger.getBackupFilename()) + ".gz")
  389. }()
  390. }
  391. // the following write calls cannot be changed to Write, because of DATA RACE.
  392. logger.write([]byte(`foo`))
  393. rule.rotatedTime = time.Now().Add(-time.Hour * 24).Format(dateFormat)
  394. logger.write([]byte(`bar`))
  395. logger.Close()
  396. logger.write([]byte(`baz`))
  397. }
  398. func TestGzipFile(t *testing.T) {
  399. err := errors.New("any error")
  400. t.Run("gzip file open failed", func(t *testing.T) {
  401. fsys := &fakeFileSystem{
  402. openFn: func(name string) (*os.File, error) {
  403. return nil, err
  404. },
  405. }
  406. assert.ErrorIs(t, err, gzipFile("any", fsys))
  407. assert.False(t, fsys.Removed())
  408. })
  409. t.Run("gzip file create failed", func(t *testing.T) {
  410. fsys := &fakeFileSystem{
  411. createFn: func(name string) (*os.File, error) {
  412. return nil, err
  413. },
  414. }
  415. assert.ErrorIs(t, err, gzipFile("any", fsys))
  416. assert.False(t, fsys.Removed())
  417. })
  418. t.Run("gzip file copy failed", func(t *testing.T) {
  419. fsys := &fakeFileSystem{
  420. copyFn: func(writer io.Writer, reader io.Reader) (int64, error) {
  421. return 0, err
  422. },
  423. }
  424. assert.ErrorIs(t, err, gzipFile("any", fsys))
  425. assert.False(t, fsys.Removed())
  426. })
  427. t.Run("gzip file last close failed", func(t *testing.T) {
  428. var called int32
  429. fsys := &fakeFileSystem{
  430. closeFn: func(closer io.Closer) error {
  431. if atomic.AddInt32(&called, 1) > 2 {
  432. return err
  433. }
  434. return nil
  435. },
  436. }
  437. assert.NoError(t, gzipFile("any", fsys))
  438. assert.True(t, fsys.Removed())
  439. })
  440. t.Run("gzip file remove failed", func(t *testing.T) {
  441. fsys := &fakeFileSystem{
  442. removeFn: func(name string) error {
  443. return err
  444. },
  445. }
  446. assert.Error(t, err, gzipFile("any", fsys))
  447. assert.True(t, fsys.Removed())
  448. })
  449. t.Run("gzip file everything ok", func(t *testing.T) {
  450. fsys := &fakeFileSystem{}
  451. assert.NoError(t, gzipFile("any", fsys))
  452. assert.True(t, fsys.Removed())
  453. })
  454. }
  455. func BenchmarkRotateLogger(b *testing.B) {
  456. filename := "./test.log"
  457. filename2 := "./test2.log"
  458. dailyRotateRuleLogger, err1 := NewLogger(
  459. filename,
  460. DefaultRotateRule(
  461. filename,
  462. backupFileDelimiter,
  463. 1,
  464. true,
  465. ),
  466. true,
  467. )
  468. if err1 != nil {
  469. b.Logf("Failed to new daily rotate rule logger: %v", err1)
  470. b.FailNow()
  471. }
  472. sizeLimitRotateRuleLogger, err2 := NewLogger(
  473. filename2,
  474. NewSizeLimitRotateRule(
  475. filename,
  476. backupFileDelimiter,
  477. 1,
  478. 100,
  479. 10,
  480. true,
  481. ),
  482. true,
  483. )
  484. if err2 != nil {
  485. b.Logf("Failed to new size limit rotate rule logger: %v", err1)
  486. b.FailNow()
  487. }
  488. defer func() {
  489. dailyRotateRuleLogger.Close()
  490. sizeLimitRotateRuleLogger.Close()
  491. os.Remove(filename)
  492. os.Remove(filename2)
  493. }()
  494. b.Run("daily rotate rule", func(b *testing.B) {
  495. for i := 0; i < b.N; i++ {
  496. dailyRotateRuleLogger.write([]byte("testing\ntesting\n"))
  497. }
  498. })
  499. b.Run("size limit rotate rule", func(b *testing.B) {
  500. for i := 0; i < b.N; i++ {
  501. sizeLimitRotateRuleLogger.write([]byte("testing\ntesting\n"))
  502. }
  503. })
  504. }
  505. type fakeFileSystem struct {
  506. removed int32
  507. closeFn func(closer io.Closer) error
  508. copyFn func(writer io.Writer, reader io.Reader) (int64, error)
  509. createFn func(name string) (*os.File, error)
  510. openFn func(name string) (*os.File, error)
  511. removeFn func(name string) error
  512. }
  513. func (f *fakeFileSystem) Close(closer io.Closer) error {
  514. if f.closeFn != nil {
  515. return f.closeFn(closer)
  516. }
  517. return nil
  518. }
  519. func (f *fakeFileSystem) Copy(writer io.Writer, reader io.Reader) (int64, error) {
  520. if f.copyFn != nil {
  521. return f.copyFn(writer, reader)
  522. }
  523. return 0, nil
  524. }
  525. func (f *fakeFileSystem) Create(name string) (*os.File, error) {
  526. if f.createFn != nil {
  527. return f.createFn(name)
  528. }
  529. return nil, nil
  530. }
  531. func (f *fakeFileSystem) Open(name string) (*os.File, error) {
  532. if f.openFn != nil {
  533. return f.openFn(name)
  534. }
  535. return nil, nil
  536. }
  537. func (f *fakeFileSystem) Remove(name string) error {
  538. atomic.AddInt32(&f.removed, 1)
  539. if f.removeFn != nil {
  540. return f.removeFn(name)
  541. }
  542. return nil
  543. }
  544. func (f *fakeFileSystem) Removed() bool {
  545. return atomic.LoadInt32(&f.removed) > 0
  546. }