github.com/ethereumproject/go-ethereum@v5.5.2+incompatible/logger/glog/glog_test.go (about) 1 // Go support for leveled logs, analogous to https://code.google.com/p/google-glog/ 2 // 3 // Copyright 2013 Google Inc. All Rights Reserved. 4 // Modifications copyright 2017 ETC Dev Team. All Rights Reserved. 5 // 6 // Licensed under the Apache License, Version 2.0 (the "License"); 7 // you may not use this file except in compliance with the License. 8 // You may obtain a copy of the License at 9 // 10 // http://www.apache.org/licenses/LICENSE-2.0 11 // 12 // Unless required by applicable law or agreed to in writing, software 13 // distributed under the License is distributed on an "AS IS" BASIS, 14 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 // See the License for the specific language governing permissions and 16 // limitations under the License. 17 18 package glog 19 20 import ( 21 "bytes" 22 "compress/gzip" 23 "fmt" 24 "io/ioutil" 25 stdLog "log" 26 "math/rand" 27 "os" 28 "path/filepath" 29 "runtime" 30 "strconv" 31 "strings" 32 "sync" 33 "sync/atomic" 34 "testing" 35 "time" 36 ) 37 38 // Test that shortHostname works as advertised. 39 func TestShortHostname(t *testing.T) { 40 for hostname, expect := range map[string]string{ 41 "": "", 42 "host": "host", 43 "host.google.com": "host", 44 } { 45 if got := shortHostname(hostname); expect != got { 46 t.Errorf("shortHostname(%q): expected %q, got %q", hostname, expect, got) 47 } 48 } 49 } 50 51 // flushBuffer wraps a bytes.Buffer to satisfy flushSyncWriter. 52 type flushBuffer struct { 53 bytes.Buffer 54 } 55 56 func (f *flushBuffer) Flush() error { 57 return nil 58 } 59 60 func (f *flushBuffer) Sync() error { 61 return nil 62 } 63 64 // swapLogging sets the log writers and returns the old array. 65 func (l *loggingT) swapLogging(writers [numSeverity]flushSyncWriter) (old [numSeverity]flushSyncWriter) { 66 l.mu.Lock() 67 defer l.mu.Unlock() 68 old = l.file 69 for i, w := range writers { 70 logging.file[i] = w 71 } 72 return 73 } 74 75 // swapLogging sets the log writers and returns the old array. 76 func (l *loggingT) swapDisplay(writers [numSeverity]flushSyncWriter) (old [numSeverity]flushSyncWriter) { 77 l.mu.Lock() 78 defer l.mu.Unlock() 79 old = l.file 80 for i, w := range writers { 81 display.file[i] = w 82 } 83 return 84 } 85 86 // newLoggingBuffers sets the log writers to all new byte buffers and returns the old array. 87 func (l *loggingT) newLoggingBuffers() [numSeverity]flushSyncWriter { 88 return l.swapLogging([numSeverity]flushSyncWriter{new(flushBuffer), new(flushBuffer), new(flushBuffer), new(flushBuffer)}) 89 } 90 91 // newDisplayBuffers sets the log writers to all new byte buffers and returns the old array. 92 func (l *loggingT) newDisplayBuffers() [numSeverity]flushSyncWriter { 93 return l.swapDisplay([numSeverity]flushSyncWriter{new(flushBuffer), new(flushBuffer), new(flushBuffer), new(flushBuffer)}) 94 } 95 96 // loggingContents returns the specified log value as a string. 97 func loggingContents(s severity) string { 98 return logging.file[s].(*flushBuffer).String() 99 } 100 101 // displayContents returns the specified log value as a string. 102 func displayContents(s severity) string { 103 return display.file[s].(*flushBuffer).String() 104 } 105 106 // loggingContains reports whether the string is contained in the log. 107 func loggingContains(s severity, str string, t *testing.T) bool { 108 return strings.Contains(loggingContents(s), str) 109 } 110 111 // displayContains reports whether the string is contained in the log. 112 func displayContains(s severity, str string, t *testing.T) bool { 113 return strings.Contains(displayContents(s), str) 114 } 115 116 // setFlags configures the logging flags how the test expects them. 117 func setFlags() { 118 logging.toStderr = false 119 display.toStderr = false 120 } 121 122 // Test that Info works as advertised. 123 func TestInfo(t *testing.T) { 124 setFlags() 125 defer logging.swapLogging(logging.newLoggingBuffers()) 126 Info("test") 127 if !loggingContains(infoLog, "I", t) { 128 t.Errorf("Info has wrong character: %q", loggingContents(infoLog)) 129 } 130 if !loggingContains(infoLog, "test", t) { 131 t.Error("Info failed") 132 } 133 } 134 135 func TestInfoDepth(t *testing.T) { 136 setFlags() 137 defer logging.swapLogging(logging.newLoggingBuffers()) 138 139 f := func() { InfoDepth(1, "depth-test1") } 140 141 // The next three lines must stay together 142 _, _, wantLine, _ := runtime.Caller(0) 143 InfoDepth(0, "depth-test0") 144 f() 145 146 msgs := strings.Split(strings.TrimSuffix(loggingContents(infoLog), "\n"), "\n") 147 if len(msgs) != 2 { 148 t.Fatalf("Got %d lines, expected 2", len(msgs)) 149 } 150 151 for i, m := range msgs { 152 if !strings.HasPrefix(m, "I") { 153 t.Errorf("InfoDepth[%d] has wrong character: %q", i, m) 154 } 155 w := fmt.Sprintf("depth-test%d", i) 156 if !strings.Contains(m, w) { 157 t.Errorf("InfoDepth[%d] missing %q: %q", i, w, m) 158 } 159 160 // pull out the line number (between : and ]) 161 msg := m[strings.LastIndex(m, ":")+1:] 162 x := strings.Index(msg, "]") 163 if x < 0 { 164 t.Errorf("InfoDepth[%d]: missing ']': %q", i, m) 165 continue 166 } 167 line, err := strconv.Atoi(msg[:x]) 168 if err != nil { 169 t.Errorf("InfoDepth[%d]: bad line number: %q", i, m) 170 continue 171 } 172 wantLine++ 173 if wantLine != line { 174 t.Errorf("InfoDepth[%d]: got line %d, want %d", i, line, wantLine) 175 } 176 } 177 } 178 179 func init() { 180 CopyStandardLogTo("INFO") 181 } 182 183 // Test that CopyStandardLogTo panics on bad input. 184 func TestCopyStandardLogToPanic(t *testing.T) { 185 defer func() { 186 if s, ok := recover().(string); !ok || !strings.Contains(s, "LOG") { 187 t.Errorf(`CopyStandardLogTo("LOG") should have panicked: %v`, s) 188 } 189 }() 190 CopyStandardLogTo("LOG") 191 } 192 193 // Test that using the standard log package logs to INFO. 194 func TestStandardLog(t *testing.T) { 195 setFlags() 196 defer logging.swapLogging(logging.newLoggingBuffers()) 197 stdLog.Print("test") 198 if !loggingContains(infoLog, "I", t) { 199 t.Errorf("Info has wrong character: %q", loggingContents(infoLog)) 200 } 201 if !loggingContains(infoLog, "test", t) { 202 t.Error("Info failed") 203 } 204 } 205 206 func lineNumber() int { 207 _, _, line, _ := runtime.Caller(1) 208 return line 209 } 210 211 // Test that the header has the correct format. 212 func TestHeader1ErrorLog(t *testing.T) { 213 setFlags() 214 defer logging.swapLogging(logging.newLoggingBuffers()) 215 defer func(previous func() time.Time) { timeNow = previous }(timeNow) 216 timeNow = func() time.Time { 217 return time.Date(2006, 1, 2, 15, 4, 5, .067890e9, time.Local) 218 } 219 pid = 1234 220 Error("test") 221 line := lineNumber() - 1 // the line directly above 222 format := "E" + "0102 15:04:05.067890 logger/glog/glog_test.go:%d] test\n" 223 n, err := fmt.Sscanf(loggingContents(errorLog), format, &line) 224 if n != 1 || err != nil { 225 t.Errorf("log format error: %d elements, error %s:\n%s", n, err, loggingContents(errorLog)) 226 } 227 // Scanf treats multiple spaces as equivalent to a single space, 228 // so check for correct space-padding also. 229 want := fmt.Sprintf(format, line) 230 if loggingContents(errorLog) != want { 231 t.Errorf("log format error: got:\n\t%q\nwant:\t%q", loggingContents(errorLog), want) 232 } 233 } 234 235 // Test that the header has the correct format. 236 func TestHeader2InfoLog(t *testing.T) { 237 setFlags() 238 defer logging.swapLogging(logging.newLoggingBuffers()) 239 defer func(previous func() time.Time) { timeNow = previous }(timeNow) 240 timeNow = func() time.Time { 241 return time.Date(2006, 1, 2, 15, 4, 5, .067890e9, time.Local) 242 } 243 s := logging.verbosityTraceThreshold.get() 244 logging.verbosityTraceThreshold.set(5) // Use app flag defaults 245 defer logging.verbosityTraceThreshold.set(s) 246 pid = 1234 247 Info("test") 248 format := "I" + "0102 15:04:05.067890 logger/glog/glog_test.go:" + strconv.Itoa(lineNumber()-1) + "] test\n" 249 n, err := fmt.Sscanf(loggingContents(infoLog), format) 250 if err != nil { 251 t.Errorf("log format error: %d elements, error %s:\n%s", n, err, loggingContents(infoLog)) 252 } 253 // Scanf treats multiple spaces as equivalent to a single space, 254 // so check for correct space-padding also. 255 want := fmt.Sprintf(format) 256 if loggingContents(infoLog) != want { 257 t.Errorf("log format error: got:\n\t%q\nwant:\n\t%q", loggingContents(infoLog), want) 258 } 259 } 260 261 // Test that an Error log goes to Warning and Info. 262 // Even in the Info log, the source character will be E, so the data should 263 // all be identical. 264 func TestError(t *testing.T) { 265 setFlags() 266 defer logging.swapLogging(logging.newLoggingBuffers()) 267 defer display.swapDisplay(display.newDisplayBuffers()) 268 Error("test") 269 if !loggingContains(errorLog, "E", t) { 270 t.Errorf("Error has wrong character: %q", loggingContents(errorLog)) 271 } 272 if !loggingContains(errorLog, "test", t) { 273 t.Error("Error failed") 274 } 275 str := loggingContents(errorLog) 276 if !loggingContains(warningLog, str, t) { 277 t.Error("Warning failed") 278 } 279 if !loggingContains(infoLog, str, t) { 280 t.Error("Info failed") 281 } 282 } 283 284 // Test that a Warning log goes to Info. 285 // Even in the Info log, the source character will be W, so the data should 286 // all be identical. 287 func TestWarningLogging(t *testing.T) { 288 setFlags() 289 defer logging.swapLogging(logging.newLoggingBuffers()) 290 Warning("test") 291 if !loggingContains(warningLog, "W", t) { 292 t.Errorf("Warning has wrong character: %q", loggingContents(warningLog)) 293 } 294 if !loggingContains(warningLog, "test", t) { 295 t.Error("Warning failed") 296 } 297 str := loggingContents(warningLog) 298 if !loggingContains(infoLog, str, t) { 299 t.Error("Info failed") 300 } 301 } 302 303 func TestWarningDisplay(t *testing.T) { 304 setFlags() 305 defer display.swapDisplay(display.newDisplayBuffers()) 306 display.verbosity.Set("3") 307 defer display.verbosity.Set("0") 308 D(2).Warnln("test") 309 if !displayContains(warningLog, "test", t) { 310 t.Error("Warning failed") 311 } 312 str := displayContents(warningLog) 313 if !displayContains(infoLog, str, t) { 314 t.Error("Info failed") 315 } 316 } 317 318 func TestErrorDisplay(t *testing.T) { 319 setFlags() 320 defer display.swapDisplay(display.newDisplayBuffers()) 321 display.verbosity.Set("3") 322 defer display.verbosity.Set("0") 323 D(2).Errorln("test") 324 if !displayContains(errorLog, "ERR ", t) { 325 t.Errorf("Error has wrong character: %q", displayContents(errorLog)) 326 } 327 if !displayContains(warningLog, "test", t) { 328 t.Error("Warning failed") 329 } 330 str := displayContents(warningLog) 331 if !displayContains(infoLog, str, t) { 332 t.Error("Info failed") 333 } 334 } 335 336 // Test that a V log goes to Info. 337 func TestV(t *testing.T) { 338 setFlags() 339 defer logging.swapLogging(logging.newLoggingBuffers()) 340 logging.verbosity.Set("2") 341 defer logging.verbosity.Set("0") 342 V(2).Info("test") 343 if !loggingContains(infoLog, "I", t) { 344 t.Errorf("Info has wrong character: %q", loggingContents(infoLog)) 345 } 346 if !loggingContains(infoLog, "test", t) { 347 t.Error("Info failed") 348 } 349 } 350 351 // Test that a V log goes to Info. 352 func TestD(t *testing.T) { 353 setFlags() 354 defer display.swapDisplay(display.newDisplayBuffers()) 355 display.verbosity.Set("2") 356 defer display.verbosity.Set("0") 357 D(2).Infoln("test") 358 if !displayContains(infoLog, ":", t) { 359 t.Errorf("Info has wrong character: %q", displayContents(infoLog)) 360 } 361 if displayContains(infoLog, "I", t) { 362 t.Errorf("Info has wrong character: %q", displayContents(infoLog)) 363 } 364 if !displayContains(infoLog, "test", t) { 365 t.Error("Info failed") 366 } 367 } 368 369 // Test that a vmodule enables a log in this file. 370 func TestVmoduleOn(t *testing.T) { 371 setFlags() 372 defer logging.swapLogging(logging.newLoggingBuffers()) 373 logging.vmodule.Set("glog_test.go=2") 374 defer logging.vmodule.Set("") 375 if !V(1) { 376 t.Error("V not enabled for 1") 377 } 378 if !V(2) { 379 t.Error("V not enabled for 2") 380 } 381 if V(3) { 382 t.Error("V enabled for 3") 383 } 384 V(2).Info("test") 385 if !loggingContains(infoLog, "I", t) { 386 t.Errorf("Info has wrong character: %q", loggingContents(infoLog)) 387 } 388 if !loggingContains(infoLog, "test", t) { 389 t.Error("Info failed") 390 } 391 } 392 393 // Test that a vmodule of another file does not enable a log in this file. 394 func TestVmoduleOff(t *testing.T) { 395 setFlags() 396 defer logging.swapLogging(logging.newLoggingBuffers()) 397 logging.vmodule.Set("notthisfile=2") 398 defer logging.vmodule.Set("") 399 for i := 1; i <= 3; i++ { 400 if V(Level(i)) { 401 t.Errorf("V enabled for %d", i) 402 } 403 } 404 V(2).Info("test") 405 if loggingContents(infoLog) != "" { 406 t.Error("V logged incorrectly") 407 } 408 } 409 410 var patternTests = []struct{ input, want string }{ 411 {"foo/bar/x.go", ".*/foo/bar/x\\.go$"}, 412 {"foo/*/x.go", ".*/foo(/.*)?/x\\.go$"}, 413 {"foo/*", ".*/foo(/.*)?/[^/]+\\.go$"}, 414 } 415 416 func TestCompileModulePattern(t *testing.T) { 417 for _, test := range patternTests { 418 re, err := compileModulePattern(test.input) 419 if err != nil { 420 t.Fatalf("%s: %v", test.input, err) 421 } 422 if re.String() != test.want { 423 t.Errorf("mismatch for %q: got %q, want %q", test.input, re.String(), test.want) 424 } 425 } 426 } 427 428 // vGlobs are patterns that match/don't match this file at V=2. 429 var vGlobs = map[string]bool{ 430 // Easy to test the numeric match here. 431 "glog_test.go=1": false, // If -vmodule sets V to 1, V(2) will fail. 432 "glog_test.go=2": true, 433 "glog_test.go=3": true, // If -vmodule sets V to 1, V(3) will succeed. 434 435 // Import path prefix matching 436 "logger/glog=1": false, 437 "logger/glog=2": true, 438 "logger/glog=3": true, 439 440 // Import path glob matching 441 "logger/*=1": false, 442 "logger/*=2": true, 443 "logger/*=3": true, 444 445 // These all use 2 and check the patterns. 446 "*=2": true, 447 } 448 449 // Test that vmodule globbing works as advertised. 450 func testVmoduleGlob(pat string, match bool, t *testing.T) { 451 setFlags() 452 defer logging.swapLogging(logging.newLoggingBuffers()) 453 defer logging.vmodule.Set("") 454 logging.vmodule.Set(pat) 455 if V(2) != Verbose(match) { 456 t.Errorf("incorrect match for %q: got %t expected %t", pat, V(2), match) 457 } 458 } 459 460 // Test that a vmodule globbing works as advertised. 461 func TestVmoduleGlob(t *testing.T) { 462 for glob, match := range vGlobs { 463 testVmoduleGlob(glob, match, t) 464 } 465 } 466 467 func TestRollover(t *testing.T) { 468 setFlags() 469 var err error 470 defer func(previous func(error)) { logExitFunc = previous }(logExitFunc) 471 logExitFunc = func(e error) { 472 err = e 473 } 474 defer func(previous uint64) { MaxSize = previous }(MaxSize) 475 MaxSize = 512 476 477 Info("x") // Be sure we have a file. 478 info, ok := logging.file[infoLog].(*syncBuffer) 479 if !ok { 480 t.Fatal("info wasn't created") 481 } 482 if err != nil { 483 t.Fatalf("info has initial error: %v", err) 484 } 485 fname0 := info.file.Name() 486 Info(strings.Repeat("x", int(MaxSize))) // force a rollover 487 if err != nil { 488 t.Fatalf("info has error after big write: %v", err) 489 } 490 491 // Make sure the next log file gets a file name with a different 492 // time stamp. 493 // 494 // TODO: determine whether we need to support subsecond log 495 // rotation. C++ does not appear to handle this case (nor does it 496 // handle Daylight Savings Time properly). 497 time.Sleep(1 * time.Second) 498 499 Info("x") // create a new file 500 if err != nil { 501 t.Fatalf("error after rotation: %v", err) 502 } 503 fname1 := info.file.Name() 504 if fname0 == fname1 { 505 t.Errorf("info.f.Name did not change: %v", fname0) 506 } 507 if info.nbytes >= MaxSize { 508 t.Errorf("file size was not reset: %d", info.nbytes) 509 } 510 } 511 512 func TestLogBacktraceAt(t *testing.T) { 513 setFlags() 514 defer logging.swapLogging(logging.newLoggingBuffers()) 515 // The peculiar style of this code simplifies line counting and maintenance of the 516 // tracing block below. 517 var infoLine string 518 setTraceLocation := func(file string, line int, ok bool, delta int) { 519 if !ok { 520 t.Fatal("could not get file:line") 521 } 522 _, file = filepath.Split(file) 523 infoLine = fmt.Sprintf("%s:%d", file, line+delta) 524 err := logging.traceLocation.Set(infoLine) 525 if err != nil { 526 t.Fatal("error setting log_backtrace_at: ", err) 527 } 528 } 529 { 530 // Start of tracing block. These lines know about each other's relative position. 531 _, file, line, ok := runtime.Caller(0) 532 setTraceLocation(file, line, ok, +2) // Two lines between Caller and Info calls. 533 Info("we want a stack trace here") 534 } 535 numAppearances := strings.Count(loggingContents(infoLog), infoLine) 536 if numAppearances < 2 { 537 // Need 2 appearances, one in the log header and one in the trace: 538 // log_test.go:281: I0511 16:36:06.952398 02238 log_test.go:280] we want a stack trace here 539 // ... 540 // github.com/glog/glog_test.go:280 (0x41ba91) 541 // ... 542 // We could be more precise but that would require knowing the details 543 // of the traceback format, which may not be dependable. 544 t.Fatal("got no trace back; log is ", loggingContents(infoLog)) 545 } 546 } 547 548 func TestExtractTimestamp(t *testing.T) { 549 preffix := fmt.Sprintf("%s.%s.%s.log.", "geth_test", "sampleHost", "sampleUser") 550 cases := []struct { 551 name string 552 fileName string 553 expected string 554 }{ 555 {"valid INFO", preffix + "INFO.20171202-132113.2841", "20171202-132113"}, 556 {"valid WARNIG", preffix + "WARNING.20171202-210922.13848", "20171202-210922"}, 557 {"valid gzipped", preffix + "WARNING.20171202-210922.13848.gz", "20171202-210922"}, 558 {"extra long filename", preffix + "WARNING.20171202-210922.13848.gz.bak", "20171202-210922"}, 559 {"too short filename", preffix + "WARNING.20171202-21092", ""}, 560 {"no preffix", "WARNING.20171202-21092", ""}, 561 } 562 563 for _, test := range cases { 564 t.Run(test.name, func(t *testing.T) { 565 actual := extractTimestamp(test.fileName, preffix) 566 if test.expected != actual { 567 t.Errorf("Expected: '%s', actual: '%s'", test.expected, actual) 568 } 569 }) 570 } 571 } 572 573 func TestShouldRotate(t *testing.T) { 574 // fixed date, to make tests stable, 04.12.2017 is Monday 575 start := time.Date(2017, time.December, 4, 0, 0, 0, 0, time.UTC) 576 577 cases := []struct { 578 name string 579 nbytes uint64 580 len int 581 now time.Time 582 minSize uint64 583 maxSize uint64 584 interval Interval 585 expected bool 586 }{ 587 {"empty log no rotation", 0, 123, start, 0, 0, Never, false}, 588 {"empty log with hourly rotation", 0, 123, start, 0, 0, Hourly, false}, 589 {"empty log with size rotation", 0, 123, start, 0, 1024 * 1024, Never, false}, 590 {"log with hourly rotation after less than hour", 1234, 123, start.Add(45 * time.Minute), 0, 0, Hourly, false}, 591 {"log with hourly rotation after more than hour", 1234, 123, start.Add(65 * time.Minute), 0, 0, Hourly, true}, 592 {"log with size rotation below MinSize", 1024, 123, start, 512 * 1024, 1024 * 1024, Never, false}, 593 {"log with size rotation between MinSize and MaxSize", 765 * 1024, 123, start, 512 * 1024, 1024 * 1024, Never, false}, 594 {"log with size rotation above MaxSize", 1024*1024 - 100, 123, start, 512 * 1024, 1024 * 1024, Never, true}, 595 {"log with daily rotation after less than day", 1234, 123, start.Add(23 * time.Hour), 0, 0, Daily, false}, 596 {"log with daily rotation after more than day", 1234, 123, start.Add(25 * time.Hour), 0, 0, Daily, true}, 597 {"log with weekly rotation after less than week", 1234, 123, start.Add((6*24 + 23) * time.Hour), 0, 0, Weekly, false}, 598 {"log with weekly rotation after more than week", 1234, 123, start.Add((7*24 + 1) * time.Hour), 0, 0, Weekly, true}, 599 {"log with monthly rotation after less than month", 1234, 123, start.Add(14 * 24 * time.Hour), 0, 0, Monthly, false}, 600 {"log with monthly rotation after more than month", 1234, 123, start.Add(30 * 24 * time.Hour), 0, 0, Monthly, true}, 601 } 602 603 for _, test := range cases { 604 t.Run(test.name, func(t *testing.T) { 605 sb := &syncBuffer{nbytes: test.nbytes, time: start} 606 MinSize = test.minSize 607 MaxSize = test.maxSize 608 RotationInterval = test.interval 609 actual := sb.shouldRotate(test.len, test.now) 610 if test.expected != actual { 611 t.Errorf("Expected: '%v', actual: '%v'", test.expected, actual) 612 } 613 }) 614 } 615 } 616 617 func TestGzipFile(t *testing.T) { 618 dir, err := ioutil.TempDir("", "") 619 if err != nil { 620 t.Error(err) 621 } 622 defer os.RemoveAll(dir) 623 624 file := filepath.Join(dir, "sample.log") 625 data := strings.Repeat("lorem ipsum dolor sit amet", 4096) 626 ioutil.WriteFile(file, []byte(data), 0600) 627 628 err = gzipFile(file) 629 if err != nil { 630 t.Fatal(err) 631 } 632 633 gzipped := file + ".gz" 634 635 files, err := ioutil.ReadDir(dir) 636 if err != nil { 637 t.Fatal(err) 638 } 639 640 if len(files) != 1 { 641 t.Errorf("expected 1 file in directory, found %d", len(files)) 642 } 643 if files[0].Size() == 0 || files[0].Size() >= int64(len(data)) { 644 t.Errorf("expected: 0 < file size < %d [bytes], actual: %d [bytes]", len(data), files[0].Size()) 645 } 646 if filepath.Join(dir, files[0].Name()) != gzipped { 647 t.Errorf("expected filename: %s; actual filename: %s", gzipped, files[0].Name()) 648 } 649 650 input, err := os.Open(filepath.Join(dir, files[0].Name())) 651 if err != nil { 652 t.Error(err) 653 } 654 defer input.Close() 655 656 reader, err := gzip.NewReader(input) 657 if err != nil { 658 t.Fatal(err) 659 } 660 defer reader.Close() 661 662 result, err := ioutil.ReadAll(reader) 663 if err != nil { 664 t.Error(err) 665 } 666 if string(result) != data { 667 t.Errorf("contents of gzip file are invalid") 668 } 669 } 670 671 // there are always 10 interesting files, 10*1024b each 672 // some of them are gzipped 673 // there are WARN and INFO logs 674 // there are also 3 other files, that shoudn't be touched 675 // there are 2 "current files" (pointed by symlinks) 676 // global configuration options should be applied 677 // after running rotation, assertions about resulting data are checked 678 func testRotation(t *testing.T) { 679 dir, err := ioutil.TempDir("", "") 680 if err != nil { 681 t.Error(err) 682 } 683 defer os.RemoveAll(dir) 684 685 start := time.Date(2017, time.December, 06, 0, 0, 0, 0, time.UTC) 686 687 data := []byte(strings.Repeat(".", 10*1024)) 688 logDate := start 689 // generate files 690 for i := 0; i < 5; i++ { 691 infoF, infoL := logName("INFO", logDate.Add(1*time.Second)) 692 warnF, warnL := logName("WARNING", logDate.Add(10*time.Second).Add(1*time.Second)) 693 694 ioutil.WriteFile(filepath.Join(dir, infoF), data, 0600) 695 ioutil.WriteFile(filepath.Join(dir, warnF), data, 0600) 696 697 infoSL := filepath.Join(dir, infoL) 698 os.Remove(infoSL) // ignore err 699 os.Symlink(filepath.Join(dir, infoF), infoSL) // ignore err 700 701 warnSL := filepath.Join(dir, warnL) 702 os.Remove(warnSL) // ignore err 703 os.Symlink(filepath.Join(dir, warnF), warnSL) // ignore err 704 705 logDate = logDate.Add(24 * time.Hour) 706 } 707 dummy1 := "and_now_for_something_completely_different.log" 708 ioutil.WriteFile(filepath.Join(dir, dummy1), data, 0600) 709 dummy2, _ := logName("ERROR", start.Add(-10*24*time.Hour)) 710 dummy2 = "keep." + dummy2 711 ioutil.WriteFile(filepath.Join(dir, dummy2), data, 0600) 712 dummy3, _ := logName("INFO", start.Add(+10*24*time.Hour)) 713 dummy3 = strings.Replace(dummy3, userName, "differentUser", 1) 714 ioutil.WriteFile(filepath.Join(dir, dummy3), data, 0600) 715 716 // prepare environment 717 logDirs = nil 718 SetLogDir(dir) 719 createLogDirs() 720 721 // execute rotation 722 sb := &syncBuffer{} 723 atomic.StoreInt64(&rotationTime, 0) // make sure, that rotation is executed 724 sb.rotateOld(logDate) 725 726 // make assertions 727 files, err := ioutil.ReadDir(dir) 728 if err != nil { 729 t.Fatal(err) 730 } 731 732 // ensure that 3rd party files are intact 733 dummy1ok := false 734 dummy2ok := false 735 dummy3ok := false 736 totalSize := uint64(0) 737 nGzipped := 0 738 preffix := fmt.Sprintf("%s.%s.%s.log.", program, host, userName) 739 maxTimestamp := "" 740 if MaxAge > 0 { 741 t := logDate.Add(-1 * MaxAge) 742 maxTimestamp = fmt.Sprintf("%04d%02d%02d-%02d%02d%02d", 743 t.Year(), 744 t.Month(), 745 t.Day(), 746 t.Hour(), 747 t.Minute(), 748 t.Second(), 749 ) 750 } 751 for _, file := range files { 752 sameSize := file.Size() == int64(len(data)) 753 switch file.Name() { 754 case dummy1: 755 dummy1ok = sameSize 756 case dummy2: 757 dummy2ok = sameSize 758 case dummy3: 759 dummy3ok = sameSize 760 default: 761 totalSize += uint64(file.Size()) 762 if maxTimestamp != "" && file.Mode().IsRegular() { 763 timestamp := extractTimestamp(file.Name(), preffix) 764 if strings.Compare(timestamp, maxTimestamp) < 0 { 765 t.Errorf("Old file not removed properly: %s, %v\n", file.Name(), maxTimestamp) 766 } 767 } 768 } 769 if strings.HasSuffix(file.Name(), ".gz") { 770 nGzipped++ 771 } 772 773 } 774 775 if !dummy1ok || !dummy2ok || !dummy3ok { 776 t.Error("Some 3rd party files removed or modified!") 777 } 778 779 if MaxTotalSize > 0 && totalSize >= MaxTotalSize { 780 t.Error("MaxTotalSize constraint violated!") 781 } 782 783 // 3 x 3rd-party files, 2 x symlink, 2 x current log files 784 if Compress && nGzipped == len(files)-3-2-2 { 785 t.Error("Some files not compressed!") 786 } 787 } 788 789 func TestRotateOldFiles(t *testing.T) { 790 MinSize = 0 791 MaxSize = 1024 792 RotationInterval = Never 793 794 cases := []struct { 795 name string 796 maxAge time.Duration 797 maxTotalSize uint64 798 compress bool 799 }{ 800 {"no limits", 0, 0, false}, 801 {"no limits with compression", 0, 0, true}, 802 {"with age limit", 2 * 24 * time.Hour, 0, false}, 803 {"with size limit", 0, 5 * 1024, false}, 804 {"with both limits", 4 * 24 * time.Hour, 5 * 1024, false}, 805 {"with age limit and compression", 2 * 24 * time.Hour, 0, true}, 806 {"with size limit and compression", 0, 5 * 1024, true}, 807 {"with both limits and compression", 4 * 24 * time.Hour, 5 * 1024, true}, 808 } 809 810 for _, test := range cases { 811 t.Run(test.name, func(t *testing.T) { 812 MaxAge = test.maxAge 813 MaxTotalSize = test.maxTotalSize 814 Compress = test.compress 815 testRotation(t) 816 }) 817 } 818 } 819 820 func TestParseInterval(t *testing.T) { 821 cases := []struct { 822 value string 823 expected Interval 824 err bool 825 }{ 826 {"never", Never, false}, 827 {"NeVeR", Never, false}, 828 {"daily", Daily, false}, 829 {"Daily", Daily, false}, 830 {"weekly", Weekly, false}, 831 {"weekLY", Weekly, false}, 832 {"monthly", Monthly, false}, 833 {"mONThLy", Monthly, false}, 834 {"invalid", Never, true}, 835 {"daily weekly", Never, true}, 836 {"none", Never, true}, 837 } 838 839 for _, test := range cases { 840 t.Run(test.value, func(t *testing.T) { 841 interval, err := ParseInterval(test.value) 842 if test.expected != interval { 843 t.Error("Invalid interval value") 844 } 845 if test.err != (err != nil) { 846 t.Error("Invalid error value") 847 } 848 }) 849 } 850 } 851 852 const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 853 const ( 854 letterIdxBits = 6 // 6 bits to represent a letter index 855 letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits 856 letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits 857 ) 858 859 // See: https://stackoverflow.com/a/31832326/3474438 860 func randStringBytesMaskImprSrc(n int, src rand.Source) []byte { 861 b := make([]byte, n) 862 // A src.Int63() generates 63 random bits, enough for letterIdxMax characters! 863 for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; { 864 if remain == 0 { 865 cache, remain = src.Int63(), letterIdxMax 866 } 867 if idx := int(cache & letterIdxMask); idx < len(letterBytes) { 868 b[i] = letterBytes[idx] 869 i-- 870 } 871 cache >>= letterIdxBits 872 remain-- 873 } 874 return b 875 } 876 877 func TestLongRunningRotateOld(t *testing.T) { 878 dir, err := ioutil.TempDir("", "") 879 if err != nil { 880 t.Error(err) 881 } 882 defer os.RemoveAll(dir) 883 884 start := time.Date(2017, time.December, 06, 0, 0, 0, 0, time.UTC) 885 886 fileSize := 512 * 1024 887 888 logDate := start 889 // generate files 890 var src = rand.NewSource(time.Now().UnixNano()) 891 for i := 0; i < 64; i++ { 892 infoF, infoL := logName("INFO", logDate.Add(1*time.Second)) 893 894 ioutil.WriteFile(filepath.Join(dir, infoF), randStringBytesMaskImprSrc(fileSize, src), 0600) 895 896 infoSL := filepath.Join(dir, infoL) 897 os.Remove(infoSL) // ignore err 898 os.Symlink(filepath.Join(dir, infoF), infoSL) // ignore err 899 900 logDate = logDate.Add(24 * time.Hour) 901 } 902 903 // prepare environment 904 logDirs = nil 905 SetLogDir(dir) 906 createLogDirs() 907 908 // execute rotation 909 sb := &syncBuffer{} 910 911 wg := sync.WaitGroup{} 912 913 run := func() { 914 wg.Add(1) 915 defer wg.Done() 916 sb.rotateOld(time.Now()) 917 } 918 919 go run() 920 for i := 0; i < 64; i++ { 921 time.Sleep(32 * time.Millisecond) 922 go run() 923 } 924 925 wg.Wait() 926 } 927 928 func BenchmarkHeaderLogging(b *testing.B) { 929 for i := 0; i < b.N; i++ { 930 buf, _, _ := logging.header(infoLog, 0) 931 logging.putBuffer(buf) 932 } 933 } 934 935 func BenchmarkHeaderDisplay(b *testing.B) { 936 for i := 0; i < b.N; i++ { 937 buf, _, _ := display.header(infoLog, 0) 938 display.putBuffer(buf) 939 } 940 }