github.heygears.com/openimsdk/tools@v0.0.49/log/file-rotatelogs/rotatelogs_test.go (about) 1 package rotatelogs_test 2 3 import ( 4 "fmt" 5 "io" 6 "io/ioutil" 7 "log" 8 "os" 9 "path/filepath" 10 "strings" 11 "testing" 12 "time" 13 14 "github.com/jonboulle/clockwork" 15 rotatelogs "github.com/lestrrat-go/file-rotatelogs" 16 "github.com/pkg/errors" 17 "github.com/stretchr/testify/assert" 18 ) 19 20 func TestSatisfiesIOWriter(t *testing.T) { 21 var w io.Writer 22 w, _ = rotatelogs.New("/foo/bar") 23 _ = w 24 } 25 26 func TestSatisfiesIOCloser(t *testing.T) { 27 var c io.Closer 28 c, _ = rotatelogs.New("/foo/bar") 29 _ = c 30 } 31 32 func TestLogRotate(t *testing.T) { 33 testCases := []struct { 34 Name string 35 FixArgs func([]rotatelogs.Option, string) []rotatelogs.Option 36 CheckExtras func(*testing.T, *rotatelogs.RotateLogs, string) bool 37 }{ 38 { 39 Name: "Basic Usage", 40 }, 41 { 42 Name: "With Symlink", 43 FixArgs: func(options []rotatelogs.Option, dir string) []rotatelogs.Option { 44 linkName := filepath.Join(dir, "log") 45 46 return append(options, rotatelogs.WithLinkName(linkName)) 47 }, 48 CheckExtras: func(t *testing.T, rl *rotatelogs.RotateLogs, dir string) bool { 49 linkName := filepath.Join(dir, "log") 50 linkDest, err := os.Readlink(linkName) 51 if !assert.NoError(t, err, `os.Readlink(%#v) should succeed`, linkName) { 52 return false 53 } 54 55 expectedLinkDest := filepath.Base(rl.CurrentFileName()) 56 t.Logf("expecting relative link: %s", expectedLinkDest) 57 58 return assert.Equal(t, linkDest, expectedLinkDest, `Symlink destination should match expected filename (%#v != %#v)`, expectedLinkDest, linkDest) 59 }, 60 }, 61 { 62 Name: "With Symlink (multiple levels)", 63 FixArgs: func(options []rotatelogs.Option, dir string) []rotatelogs.Option { 64 linkName := filepath.Join(dir, "nest1", "nest2", "log") 65 66 return append(options, rotatelogs.WithLinkName(linkName)) 67 }, 68 CheckExtras: func(t *testing.T, rl *rotatelogs.RotateLogs, dir string) bool { 69 linkName := filepath.Join(dir, "nest1", "nest2", "log") 70 linkDest, err := os.Readlink(linkName) 71 if !assert.NoError(t, err, `os.Readlink(%#v) should succeed`, linkName) { 72 return false 73 } 74 75 expectedLinkDest := filepath.Join("..", "..", filepath.Base(rl.CurrentFileName())) 76 t.Logf("expecting relative link: %s", expectedLinkDest) 77 78 return assert.Equal(t, linkDest, expectedLinkDest, `Symlink destination should match expected filename (%#v != %#v)`, expectedLinkDest, linkDest) 79 }, 80 }, 81 } 82 83 for i, tc := range testCases { 84 i := i // avoid lint errors 85 tc := tc // avoid lint errors 86 t.Run(tc.Name, func(t *testing.T) { 87 dir, err := ioutil.TempDir("", fmt.Sprintf("file-rotatelogs-test%d", i)) 88 if !assert.NoError(t, err, "creating temporary directory should succeed") { 89 return 90 } 91 defer os.RemoveAll(dir) 92 93 // Change current time, so we can safely purge old logs 94 dummyTime := time.Now().Add(-7 * 24 * time.Hour) 95 dummyTime = dummyTime.Add(time.Duration(-1 * dummyTime.Nanosecond())) 96 clock := clockwork.NewFakeClockAt(dummyTime) 97 98 options := []rotatelogs.Option{rotatelogs.WithClock(clock), rotatelogs.WithMaxAge(24 * time.Hour)} 99 if fn := tc.FixArgs; fn != nil { 100 options = fn(options, dir) 101 } 102 103 rl, err := rotatelogs.New(filepath.Join(dir, "log%Y%m%d%H%M%S"), options...) 104 if !assert.NoError(t, err, `rotatelogs.New should succeed`) { 105 return 106 } 107 defer rl.Close() 108 109 str := "Hello, World" 110 n, err := rl.Write([]byte(str)) 111 if !assert.NoError(t, err, "rl.Write should succeed") { 112 return 113 } 114 115 if !assert.Len(t, str, n, "rl.Write should succeed") { 116 return 117 } 118 119 fn := rl.CurrentFileName() 120 if fn == "" { 121 t.Errorf("Could not get filename %s", fn) 122 } 123 124 content, err := ioutil.ReadFile(fn) 125 if err != nil { 126 t.Errorf("Failed to read file %s: %s", fn, err) 127 } 128 129 if string(content) != str { 130 t.Errorf(`File content does not match (was "%s")`, content) 131 } 132 133 err = os.Chtimes(fn, dummyTime, dummyTime) 134 if err != nil { 135 t.Errorf("Failed to change access/modification times for %s: %s", fn, err) 136 } 137 138 fi, err := os.Stat(fn) 139 if err != nil { 140 t.Errorf("Failed to stat %s: %s", fn, err) 141 } 142 143 if !fi.ModTime().Equal(dummyTime) { 144 t.Errorf("Failed to chtime for %s (expected %s, got %s)", fn, fi.ModTime(), dummyTime) 145 } 146 147 clock.Advance(7 * 24 * time.Hour) 148 149 // This next Write() should trigger Rotate() 150 rl.Write([]byte(str)) 151 newfn := rl.CurrentFileName() 152 if newfn == fn { 153 t.Errorf(`New file name and old file name should not match ("%s" != "%s")`, fn, newfn) 154 } 155 156 content, err = ioutil.ReadFile(newfn) 157 if err != nil { 158 t.Errorf("Failed to read file %s: %s", newfn, err) 159 } 160 161 if string(content) != str { 162 t.Errorf(`File content does not match (was "%s")`, content) 163 } 164 165 time.Sleep(time.Second) 166 167 // fn was declared above, before mocking CurrentTime 168 // Old files should have been unlinked 169 _, err = os.Stat(fn) 170 if !assert.Error(t, err, "os.Stat should have failed") { 171 return 172 } 173 174 if fn := tc.CheckExtras; fn != nil { 175 if !fn(t, rl, dir) { 176 return 177 } 178 } 179 }) 180 } 181 } 182 183 func CreateRotationTestFile(dir string, base time.Time, d time.Duration, n int) { 184 timestamp := base 185 for i := 0; i < n; i++ { 186 // %Y%m%d%H%M%S 187 suffix := timestamp.Format("20060102150405") 188 path := filepath.Join(dir, "log"+suffix) 189 ioutil.WriteFile(path, []byte("rotation test file\n"), os.ModePerm) 190 os.Chtimes(path, timestamp, timestamp) 191 timestamp = timestamp.Add(d) 192 } 193 } 194 195 func TestLogRotationCount(t *testing.T) { 196 dir, err := ioutil.TempDir("", "file-rotatelogs-rotationcount-test") 197 if !assert.NoError(t, err, "creating temporary directory should succeed") { 198 return 199 } 200 defer os.RemoveAll(dir) 201 202 dummyTime := time.Now().Add(-7 * 24 * time.Hour) 203 dummyTime = dummyTime.Add(time.Duration(-1 * dummyTime.Nanosecond())) 204 clock := clockwork.NewFakeClockAt(dummyTime) 205 206 t.Run("Either maxAge or rotationCount should be set", func(t *testing.T) { 207 rl, err := rotatelogs.New( 208 filepath.Join(dir, "log%Y%m%d%H%M%S"), 209 rotatelogs.WithClock(clock), 210 rotatelogs.WithMaxAge(time.Duration(0)), 211 rotatelogs.WithRotationCount(0), 212 ) 213 if !assert.NoError(t, err, `Both of maxAge and rotationCount is disabled`) { 214 return 215 } 216 defer rl.Close() 217 }) 218 219 t.Run("Either maxAge or rotationCount should be set", func(t *testing.T) { 220 rl, err := rotatelogs.New( 221 filepath.Join(dir, "log%Y%m%d%H%M%S"), 222 rotatelogs.WithClock(clock), 223 rotatelogs.WithMaxAge(1), 224 rotatelogs.WithRotationCount(1), 225 ) 226 if !assert.Error(t, err, `Both of maxAge and rotationCount is enabled`) { 227 return 228 } 229 if rl != nil { 230 defer rl.Close() 231 } 232 }) 233 234 t.Run("Only latest log file is kept", func(t *testing.T) { 235 rl, err := rotatelogs.New( 236 filepath.Join(dir, "log%Y%m%d%H%M%S"), 237 rotatelogs.WithClock(clock), 238 rotatelogs.WithMaxAge(-1), 239 rotatelogs.WithRotationCount(1), 240 ) 241 if !assert.NoError(t, err, `rotatelogs.New should succeed`) { 242 return 243 } 244 defer rl.Close() 245 246 n, err := rl.Write([]byte("dummy")) 247 if !assert.NoError(t, err, "rl.Write should succeed") { 248 return 249 } 250 if !assert.Len(t, "dummy", n, "rl.Write should succeed") { 251 return 252 } 253 time.Sleep(time.Second) 254 files, _ := filepath.Glob(filepath.Join(dir, "log*")) 255 if !assert.Equal(t, 1, len(files), "Only latest log is kept") { 256 return 257 } 258 }) 259 260 t.Run("Old log files are purged except 2 log files", func(t *testing.T) { 261 CreateRotationTestFile(dir, dummyTime, time.Hour, 5) 262 rl, err := rotatelogs.New( 263 filepath.Join(dir, "log%Y%m%d%H%M%S"), 264 rotatelogs.WithClock(clock), 265 rotatelogs.WithMaxAge(-1), 266 rotatelogs.WithRotationCount(2), 267 ) 268 if !assert.NoError(t, err, `rotatelogs.New should succeed`) { 269 return 270 } 271 defer rl.Close() 272 273 n, err := rl.Write([]byte("dummy")) 274 if !assert.NoError(t, err, "rl.Write should succeed") { 275 return 276 } 277 if !assert.Len(t, "dummy", n, "rl.Write should succeed") { 278 return 279 } 280 time.Sleep(time.Second) 281 files, _ := filepath.Glob(filepath.Join(dir, "log*")) 282 if !assert.Equal(t, 2, len(files), "One file is kept") { 283 return 284 } 285 }) 286 } 287 288 func TestLogSetOutput(t *testing.T) { 289 dir, err := ioutil.TempDir("", "file-rotatelogs-test") 290 if err != nil { 291 t.Errorf("Failed to create temporary directory: %s", err) 292 } 293 defer os.RemoveAll(dir) 294 295 rl, err := rotatelogs.New(filepath.Join(dir, "log%Y%m%d%H%M%S")) 296 if !assert.NoError(t, err, `rotatelogs.New should succeed`) { 297 return 298 } 299 defer rl.Close() 300 301 log.SetOutput(rl) 302 defer log.SetOutput(os.Stderr) 303 304 str := "Hello, World" 305 log.Print(str) 306 307 fn := rl.CurrentFileName() 308 if fn == "" { 309 t.Errorf("Could not get filename %s", fn) 310 } 311 312 content, err := ioutil.ReadFile(fn) 313 if err != nil { 314 t.Errorf("Failed to read file %s: %s", fn, err) 315 } 316 317 if !strings.Contains(string(content), str) { 318 t.Errorf(`File content does not contain "%s" (was "%s")`, str, content) 319 } 320 } 321 322 func TestGHIssue16(t *testing.T) { 323 defer func() { 324 if v := recover(); v != nil { 325 assert.NoError(t, errors.Errorf("%s", v), "error should be nil") 326 } 327 }() 328 329 dir, err := ioutil.TempDir("", "file-rotatelogs-gh16") 330 if !assert.NoError(t, err, `creating temporary directory should succeed`) { 331 return 332 } 333 defer os.RemoveAll(dir) 334 335 rl, err := rotatelogs.New( 336 filepath.Join(dir, "log%Y%m%d%H%M%S"), 337 rotatelogs.WithLinkName("./test.log"), 338 rotatelogs.WithRotationTime(10*time.Second), 339 rotatelogs.WithRotationCount(3), 340 rotatelogs.WithMaxAge(-1), 341 ) 342 if !assert.NoError(t, err, `rotatelogs.New should succeed`) { 343 return 344 } 345 346 if !assert.NoError(t, rl.Rotate(), "rl.Rotate should succeed") { 347 return 348 } 349 defer rl.Close() 350 } 351 352 func TestRotationGenerationalNames(t *testing.T) { 353 dir, err := ioutil.TempDir("", "file-rotatelogs-generational") 354 if !assert.NoError(t, err, `creating temporary directory should succeed`) { 355 return 356 } 357 defer os.RemoveAll(dir) 358 359 t.Run("Rotate over unchanged pattern", func(t *testing.T) { 360 rl, err := rotatelogs.New( 361 filepath.Join(dir, "unchanged-pattern.log"), 362 ) 363 if !assert.NoError(t, err, `rotatelogs.New should succeed`) { 364 return 365 } 366 367 seen := map[string]struct{}{} 368 for i := 0; i < 10; i++ { 369 rl.Write([]byte("Hello, World!")) 370 if !assert.NoError(t, rl.Rotate(), "rl.Rotate should succeed") { 371 return 372 } 373 374 // Because every call to Rotate should yield a new log file, 375 // and the previous files already exist, the filenames should share 376 // the same prefix and have a unique suffix 377 fn := filepath.Base(rl.CurrentFileName()) 378 if !assert.True(t, strings.HasPrefix(fn, "unchanged-pattern.log"), "prefix for all filenames should match") { 379 return 380 } 381 rl.Write([]byte("Hello, World!")) 382 suffix := strings.TrimPrefix(fn, "unchanged-pattern.log") 383 expectedSuffix := fmt.Sprintf(".%d", i+1) 384 if !assert.True(t, suffix == expectedSuffix, "expected suffix %s found %s", expectedSuffix, suffix) { 385 return 386 } 387 assert.FileExists(t, rl.CurrentFileName(), "file does not exist %s", rl.CurrentFileName()) 388 stat, err := os.Stat(rl.CurrentFileName()) 389 if err == nil { 390 if !assert.True(t, stat.Size() == 13, "file %s size is %d, expected 13", rl.CurrentFileName(), stat.Size()) { 391 return 392 } 393 } else { 394 assert.Failf(t, "could not stat file %s", rl.CurrentFileName()) 395 396 return 397 } 398 399 if _, ok := seen[suffix]; !assert.False(t, ok, `filename suffix %s should be unique`, suffix) { 400 return 401 } 402 seen[suffix] = struct{}{} 403 } 404 defer rl.Close() 405 }) 406 t.Run("Rotate over pattern change over every second", func(t *testing.T) { 407 rl, err := rotatelogs.New( 408 filepath.Join(dir, "every-second-pattern-%Y%m%d%H%M%S.log"), 409 rotatelogs.WithRotationTime(time.Nanosecond), 410 ) 411 if !assert.NoError(t, err, `rotatelogs.New should succeed`) { 412 return 413 } 414 415 for i := 0; i < 10; i++ { 416 time.Sleep(time.Second) 417 rl.Write([]byte("Hello, World!")) 418 if !assert.NoError(t, rl.Rotate(), "rl.Rotate should succeed") { 419 return 420 } 421 422 // because every new Write should yield a new logfile, 423 // every rorate should be create a filename ending with a .1 424 if !assert.True(t, strings.HasSuffix(rl.CurrentFileName(), ".1"), "log name should end with .1") { 425 return 426 } 427 } 428 defer rl.Close() 429 }) 430 } 431 432 type ClockFunc func() time.Time 433 434 func (f ClockFunc) Now() time.Time { 435 return f() 436 } 437 438 func TestGHIssue23(t *testing.T) { 439 dir, err := ioutil.TempDir("", "file-rotatelogs-generational") 440 if !assert.NoError(t, err, `creating temporary directory should succeed`) { 441 return 442 } 443 defer os.RemoveAll(dir) 444 445 for _, locName := range []string{"Asia/Tokyo", "Pacific/Honolulu"} { 446 locName := locName 447 loc, _ := time.LoadLocation(locName) 448 tests := []struct { 449 Expected string 450 Clock rotatelogs.Clock 451 }{ 452 { 453 Expected: filepath.Join(dir, strings.ToLower(strings.Replace(locName, "/", "_", -1))+".201806010000.log"), 454 Clock: ClockFunc(func() time.Time { 455 return time.Date(2018, 6, 1, 3, 18, 0, 0, loc) 456 }), 457 }, 458 { 459 Expected: filepath.Join(dir, strings.ToLower(strings.Replace(locName, "/", "_", -1))+".201712310000.log"), 460 Clock: ClockFunc(func() time.Time { 461 return time.Date(2017, 12, 31, 23, 52, 0, 0, loc) 462 }), 463 }, 464 } 465 for _, test := range tests { 466 test := test 467 t.Run(fmt.Sprintf("location = %s, time = %s", locName, test.Clock.Now().Format(time.RFC3339)), func(t *testing.T) { 468 template := strings.ToLower(strings.Replace(locName, "/", "_", -1)) + ".%Y%m%d%H%M.log" 469 rl, err := rotatelogs.New( 470 filepath.Join(dir, template), 471 rotatelogs.WithClock(test.Clock), // we're not using WithLocation, but it's the same thing 472 ) 473 if !assert.NoError(t, err, "rotatelogs.New should succeed") { 474 return 475 } 476 477 t.Logf("expected %s", test.Expected) 478 rl.Rotate() 479 if !assert.Equal(t, test.Expected, rl.CurrentFileName(), "file names should match") { 480 return 481 } 482 }) 483 } 484 } 485 } 486 487 func TestForceNewFile(t *testing.T) { 488 dir, err := ioutil.TempDir("", "file-rotatelogs-force-new-file") 489 if !assert.NoError(t, err, `creating temporary directory should succeed`) { 490 return 491 } 492 defer os.RemoveAll(dir) 493 494 t.Run("Force a new file", func(t *testing.T) { 495 rl, err := rotatelogs.New( 496 filepath.Join(dir, "force-new-file.log"), 497 rotatelogs.ForceNewFile(), 498 ) 499 if !assert.NoError(t, err, "rotatelogs.New should succeed") { 500 return 501 } 502 rl.Write([]byte("Hello, World!")) 503 rl.Close() 504 505 for i := 0; i < 10; i++ { 506 baseFn := filepath.Join(dir, "force-new-file.log") 507 rl, err := rotatelogs.New( 508 baseFn, 509 rotatelogs.ForceNewFile(), 510 ) 511 if !assert.NoError(t, err, "rotatelogs.New should succeed") { 512 return 513 } 514 rl.Write([]byte("Hello, World")) 515 rl.Write([]byte(fmt.Sprintf("%d", i))) 516 rl.Close() 517 518 fn := filepath.Base(rl.CurrentFileName()) 519 suffix := strings.TrimPrefix(fn, "force-new-file.log") 520 expectedSuffix := fmt.Sprintf(".%d", i+1) 521 if !assert.True(t, suffix == expectedSuffix, "expected suffix %s found %s", expectedSuffix, suffix) { 522 return 523 } 524 assert.FileExists(t, rl.CurrentFileName(), "file does not exist %s", rl.CurrentFileName()) 525 content, err := ioutil.ReadFile(rl.CurrentFileName()) 526 if !assert.NoError(t, err, "ioutil.ReadFile %s should succeed", rl.CurrentFileName()) { 527 return 528 } 529 str := fmt.Sprintf("Hello, World%d", i) 530 if !assert.Equal(t, str, string(content), "read %s from file %s, not expected %s", string(content), rl.CurrentFileName(), str) { 531 return 532 } 533 534 assert.FileExists(t, baseFn, "file does not exist %s", baseFn) 535 content, err = ioutil.ReadFile(baseFn) 536 if !assert.NoError(t, err, "ioutil.ReadFile should succeed") { 537 return 538 } 539 if !assert.Equal(t, "Hello, World!", string(content), "read %s from file %s, not expected Hello, World!", string(content), baseFn) { 540 return 541 } 542 } 543 }) 544 545 t.Run("Force a new file with Rotate", func(t *testing.T) { 546 baseFn := filepath.Join(dir, "force-new-file-rotate.log") 547 rl, err := rotatelogs.New( 548 baseFn, 549 rotatelogs.ForceNewFile(), 550 ) 551 if !assert.NoError(t, err, "rotatelogs.New should succeed") { 552 return 553 } 554 rl.Write([]byte("Hello, World!")) 555 556 for i := 0; i < 10; i++ { 557 if !assert.NoError(t, rl.Rotate(), "rl.Rotate should succeed") { 558 return 559 } 560 rl.Write([]byte("Hello, World")) 561 rl.Write([]byte(fmt.Sprintf("%d", i))) 562 assert.FileExists(t, rl.CurrentFileName(), "file does not exist %s", rl.CurrentFileName()) 563 content, err := ioutil.ReadFile(rl.CurrentFileName()) 564 if !assert.NoError(t, err, "ioutil.ReadFile %s should succeed", rl.CurrentFileName()) { 565 return 566 } 567 str := fmt.Sprintf("Hello, World%d", i) 568 if !assert.Equal(t, str, string(content), "read %s from file %s, not expected %s", string(content), rl.CurrentFileName(), str) { 569 return 570 } 571 572 assert.FileExists(t, baseFn, "file does not exist %s", baseFn) 573 content, err = ioutil.ReadFile(baseFn) 574 if !assert.NoError(t, err, "ioutil.ReadFile should succeed") { 575 return 576 } 577 if !assert.Equal(t, "Hello, World!", string(content), "read %s from file %s, not expected Hello, World!", string(content), baseFn) { 578 return 579 } 580 } 581 }) 582 }