1// Copyright (c) 2013 - Cloud Instruments Co., Ltd. 2// 3// All rights reserved. 4// 5// Redistribution and use in source and binary forms, with or without 6// modification, are permitted provided that the following conditions are met: 7// 8// 1. Redistributions of source code must retain the above copyright notice, this 9// list of conditions and the following disclaimer. 10// 2. Redistributions in binary form must reproduce the above copyright notice, 11// this list of conditions and the following disclaimer in the documentation 12// and/or other materials provided with the distribution. 13// 14// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 25package seelog 26 27import ( 28 "fmt" 29 "io" 30 "io/ioutil" 31 "os" 32 "path/filepath" 33 "sort" 34 "strconv" 35 "strings" 36 "sync" 37 "time" 38 39 "github.com/cihub/seelog/archive" 40 "github.com/cihub/seelog/archive/gzip" 41 "github.com/cihub/seelog/archive/tar" 42 "github.com/cihub/seelog/archive/zip" 43) 44 45// Common constants 46const ( 47 rollingLogHistoryDelimiter = "." 48) 49 50// Types of the rolling writer: roll by date, by time, etc. 51type rollingType uint8 52 53const ( 54 rollingTypeSize = iota 55 rollingTypeTime 56) 57 58// Types of the rolled file naming mode: prefix, postfix, etc. 59type rollingNameMode uint8 60 61const ( 62 rollingNameModePostfix = iota 63 rollingNameModePrefix 64) 65 66var rollingNameModesStringRepresentation = map[rollingNameMode]string{ 67 rollingNameModePostfix: "postfix", 68 rollingNameModePrefix: "prefix", 69} 70 71func rollingNameModeFromString(rollingNameStr string) (rollingNameMode, bool) { 72 for tp, tpStr := range rollingNameModesStringRepresentation { 73 if tpStr == rollingNameStr { 74 return tp, true 75 } 76 } 77 78 return 0, false 79} 80 81var rollingTypesStringRepresentation = map[rollingType]string{ 82 rollingTypeSize: "size", 83 rollingTypeTime: "date", 84} 85 86func rollingTypeFromString(rollingTypeStr string) (rollingType, bool) { 87 for tp, tpStr := range rollingTypesStringRepresentation { 88 if tpStr == rollingTypeStr { 89 return tp, true 90 } 91 } 92 93 return 0, false 94} 95 96// Old logs archivation type. 97type rollingArchiveType uint8 98 99const ( 100 rollingArchiveNone = iota 101 rollingArchiveZip 102 rollingArchiveGzip 103) 104 105var rollingArchiveTypesStringRepresentation = map[rollingArchiveType]string{ 106 rollingArchiveNone: "none", 107 rollingArchiveZip: "zip", 108 rollingArchiveGzip: "gzip", 109} 110 111type archiver func(f *os.File, exploded bool) archive.WriteCloser 112 113type unarchiver func(f *os.File) (archive.ReadCloser, error) 114 115type compressionType struct { 116 extension string 117 handleMultipleEntries bool 118 archiver archiver 119 unarchiver unarchiver 120} 121 122var compressionTypes = map[rollingArchiveType]compressionType{ 123 rollingArchiveZip: { 124 extension: ".zip", 125 handleMultipleEntries: true, 126 archiver: func(f *os.File, _ bool) archive.WriteCloser { 127 return zip.NewWriter(f) 128 }, 129 unarchiver: func(f *os.File) (archive.ReadCloser, error) { 130 fi, err := f.Stat() 131 if err != nil { 132 return nil, err 133 } 134 r, err := zip.NewReader(f, fi.Size()) 135 if err != nil { 136 return nil, err 137 } 138 return archive.NopCloser(r), nil 139 }, 140 }, 141 rollingArchiveGzip: { 142 extension: ".gz", 143 handleMultipleEntries: false, 144 archiver: func(f *os.File, exploded bool) archive.WriteCloser { 145 gw := gzip.NewWriter(f) 146 if exploded { 147 return gw 148 } 149 return tar.NewWriteMultiCloser(gw, gw) 150 }, 151 unarchiver: func(f *os.File) (archive.ReadCloser, error) { 152 gr, err := gzip.NewReader(f, f.Name()) 153 if err != nil { 154 return nil, err 155 } 156 157 // Determine if the gzip is a tar 158 tr := tar.NewReader(gr) 159 _, err = tr.Next() 160 isTar := err == nil 161 162 // Reset to beginning of file 163 if _, err := f.Seek(0, os.SEEK_SET); err != nil { 164 return nil, err 165 } 166 gr.Reset(f) 167 168 if isTar { 169 return archive.NopCloser(tar.NewReader(gr)), nil 170 } 171 return gr, nil 172 }, 173 }, 174} 175 176func (compressionType *compressionType) rollingArchiveTypeName(name string, exploded bool) string { 177 if !compressionType.handleMultipleEntries && !exploded { 178 return name + ".tar" + compressionType.extension 179 } else { 180 return name + compressionType.extension 181 } 182 183} 184 185func rollingArchiveTypeFromString(rollingArchiveTypeStr string) (rollingArchiveType, bool) { 186 for tp, tpStr := range rollingArchiveTypesStringRepresentation { 187 if tpStr == rollingArchiveTypeStr { 188 return tp, true 189 } 190 } 191 192 return 0, false 193} 194 195// Default names for different archive types 196var rollingArchiveDefaultExplodedName = "old" 197 198func rollingArchiveTypeDefaultName(archiveType rollingArchiveType, exploded bool) (string, error) { 199 compressionType, ok := compressionTypes[archiveType] 200 if !ok { 201 return "", fmt.Errorf("cannot get default filename for archive type = %v", archiveType) 202 } 203 return compressionType.rollingArchiveTypeName("log", exploded), nil 204} 205 206// rollerVirtual is an interface that represents all virtual funcs that are 207// called in different rolling writer subtypes. 208type rollerVirtual interface { 209 needsToRoll() bool // Returns true if needs to switch to another file. 210 isFileRollNameValid(rname string) bool // Returns true if logger roll file name (postfix/prefix/etc.) is ok. 211 sortFileRollNamesAsc(fs []string) ([]string, error) // Sorts logger roll file names in ascending order of their creation by logger. 212 213 // getNewHistoryRollFileName is called whenever we are about to roll the 214 // current log file. It returns the name the current log file should be 215 // rolled to. 216 getNewHistoryRollFileName(otherHistoryFiles []string) string 217 218 getCurrentFileName() string 219} 220 221// rollingFileWriter writes received messages to a file, until time interval passes 222// or file exceeds a specified limit. After that the current log file is renamed 223// and writer starts to log into a new file. You can set a limit for such renamed 224// files count, if you want, and then the rolling writer would delete older ones when 225// the files count exceed the specified limit. 226type rollingFileWriter struct { 227 fileName string // log file name 228 currentDirPath string 229 currentFile *os.File 230 currentName string 231 currentFileSize int64 232 rollingType rollingType // Rolling mode (Files roll by size/date/...) 233 archiveType rollingArchiveType 234 archivePath string 235 archiveExploded bool 236 fullName bool 237 maxRolls int 238 nameMode rollingNameMode 239 self rollerVirtual // Used for virtual calls 240 rollLock sync.Mutex 241} 242 243func newRollingFileWriter(fpath string, rtype rollingType, atype rollingArchiveType, apath string, maxr int, namemode rollingNameMode, 244 archiveExploded bool, fullName bool) (*rollingFileWriter, error) { 245 rw := new(rollingFileWriter) 246 rw.currentDirPath, rw.fileName = filepath.Split(fpath) 247 if len(rw.currentDirPath) == 0 { 248 rw.currentDirPath = "." 249 } 250 251 rw.rollingType = rtype 252 rw.archiveType = atype 253 rw.archivePath = apath 254 rw.nameMode = namemode 255 rw.maxRolls = maxr 256 rw.archiveExploded = archiveExploded 257 rw.fullName = fullName 258 return rw, nil 259} 260 261func (rw *rollingFileWriter) hasRollName(file string) bool { 262 switch rw.nameMode { 263 case rollingNameModePostfix: 264 rname := rw.fileName + rollingLogHistoryDelimiter 265 return strings.HasPrefix(file, rname) 266 case rollingNameModePrefix: 267 rname := rollingLogHistoryDelimiter + rw.fileName 268 return strings.HasSuffix(file, rname) 269 } 270 return false 271} 272 273func (rw *rollingFileWriter) createFullFileName(originalName, rollname string) string { 274 switch rw.nameMode { 275 case rollingNameModePostfix: 276 return originalName + rollingLogHistoryDelimiter + rollname 277 case rollingNameModePrefix: 278 return rollname + rollingLogHistoryDelimiter + originalName 279 } 280 return "" 281} 282 283func (rw *rollingFileWriter) getSortedLogHistory() ([]string, error) { 284 files, err := getDirFilePaths(rw.currentDirPath, nil, true) 285 if err != nil { 286 return nil, err 287 } 288 var validRollNames []string 289 for _, file := range files { 290 if rw.hasRollName(file) { 291 rname := rw.getFileRollName(file) 292 if rw.self.isFileRollNameValid(rname) { 293 validRollNames = append(validRollNames, rname) 294 } 295 } 296 } 297 sortedTails, err := rw.self.sortFileRollNamesAsc(validRollNames) 298 if err != nil { 299 return nil, err 300 } 301 validSortedFiles := make([]string, len(sortedTails)) 302 for i, v := range sortedTails { 303 validSortedFiles[i] = rw.createFullFileName(rw.fileName, v) 304 } 305 return validSortedFiles, nil 306} 307 308func (rw *rollingFileWriter) createFileAndFolderIfNeeded(first bool) error { 309 var err error 310 311 if len(rw.currentDirPath) != 0 { 312 err = os.MkdirAll(rw.currentDirPath, defaultDirectoryPermissions) 313 314 if err != nil { 315 return err 316 } 317 } 318 rw.currentName = rw.self.getCurrentFileName() 319 filePath := filepath.Join(rw.currentDirPath, rw.currentName) 320 321 // This will either open the existing file (without truncating it) or 322 // create if necessary. Append mode avoids any race conditions. 323 rw.currentFile, err = os.OpenFile(filePath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, defaultFilePermissions) 324 if err != nil { 325 return err 326 } 327 328 stat, err := rw.currentFile.Stat() 329 if err != nil { 330 rw.currentFile.Close() 331 rw.currentFile = nil 332 return err 333 } 334 335 rw.currentFileSize = stat.Size() 336 return nil 337} 338 339func (rw *rollingFileWriter) archiveExplodedLogs(logFilename string, compressionType compressionType) (err error) { 340 closeWithError := func(c io.Closer) { 341 if cerr := c.Close(); cerr != nil && err == nil { 342 err = cerr 343 } 344 } 345 346 rollPath := filepath.Join(rw.currentDirPath, logFilename) 347 src, err := os.Open(rollPath) 348 if err != nil { 349 return err 350 } 351 defer src.Close() // Read-only 352 353 // Buffer to a temporary file on the same partition 354 // Note: archivePath is a path to a directory when handling exploded logs 355 dst, err := rw.tempArchiveFile(rw.archivePath) 356 if err != nil { 357 return err 358 } 359 defer func() { 360 closeWithError(dst) 361 if err != nil { 362 os.Remove(dst.Name()) // Can't do anything when we fail to remove temp file 363 return 364 } 365 366 // Finalize archive by swapping the buffered archive into place 367 err = os.Rename(dst.Name(), filepath.Join(rw.archivePath, 368 compressionType.rollingArchiveTypeName(logFilename, true))) 369 }() 370 371 // archive entry 372 w := compressionType.archiver(dst, true) 373 defer closeWithError(w) 374 fi, err := src.Stat() 375 if err != nil { 376 return err 377 } 378 if err := w.NextFile(logFilename, fi); err != nil { 379 return err 380 } 381 _, err = io.Copy(w, src) 382 return err 383} 384 385func (rw *rollingFileWriter) archiveUnexplodedLogs(compressionType compressionType, rollsToDelete int, history []string) (err error) { 386 closeWithError := func(c io.Closer) { 387 if cerr := c.Close(); cerr != nil && err == nil { 388 err = cerr 389 } 390 } 391 392 // Buffer to a temporary file on the same partition 393 // Note: archivePath is a path to a file when handling unexploded logs 394 dst, err := rw.tempArchiveFile(filepath.Dir(rw.archivePath)) 395 if err != nil { 396 return err 397 } 398 defer func() { 399 closeWithError(dst) 400 if err != nil { 401 os.Remove(dst.Name()) // Can't do anything when we fail to remove temp file 402 return 403 } 404 405 // Finalize archive by moving the buffered archive into place 406 err = os.Rename(dst.Name(), rw.archivePath) 407 }() 408 409 w := compressionType.archiver(dst, false) 410 defer closeWithError(w) 411 412 src, err := os.Open(rw.archivePath) 413 switch { 414 // Archive exists 415 case err == nil: 416 defer src.Close() // Read-only 417 418 r, err := compressionType.unarchiver(src) 419 if err != nil { 420 return err 421 } 422 defer r.Close() // Read-only 423 424 if err := archive.Copy(w, r); err != nil { 425 return err 426 } 427 428 // Failed to stat 429 case !os.IsNotExist(err): 430 return err 431 } 432 433 // Add new files to the archive 434 for i := 0; i < rollsToDelete; i++ { 435 rollPath := filepath.Join(rw.currentDirPath, history[i]) 436 src, err := os.Open(rollPath) 437 if err != nil { 438 return err 439 } 440 defer src.Close() // Read-only 441 fi, err := src.Stat() 442 if err != nil { 443 return err 444 } 445 if err := w.NextFile(src.Name(), fi); err != nil { 446 return err 447 } 448 if _, err := io.Copy(w, src); err != nil { 449 return err 450 } 451 } 452 return nil 453} 454 455func (rw *rollingFileWriter) deleteOldRolls(history []string) error { 456 if rw.maxRolls <= 0 { 457 return nil 458 } 459 460 rollsToDelete := len(history) - rw.maxRolls 461 if rollsToDelete <= 0 { 462 return nil 463 } 464 465 if rw.archiveType != rollingArchiveNone { 466 if rw.archiveExploded { 467 os.MkdirAll(rw.archivePath, defaultDirectoryPermissions) 468 469 // Archive logs 470 for i := 0; i < rollsToDelete; i++ { 471 rw.archiveExplodedLogs(history[i], compressionTypes[rw.archiveType]) 472 } 473 } else { 474 os.MkdirAll(filepath.Dir(rw.archivePath), defaultDirectoryPermissions) 475 476 rw.archiveUnexplodedLogs(compressionTypes[rw.archiveType], rollsToDelete, history) 477 } 478 } 479 480 var err error 481 // In all cases (archive files or not) the files should be deleted. 482 for i := 0; i < rollsToDelete; i++ { 483 // Try best to delete files without breaking the loop. 484 if err = tryRemoveFile(filepath.Join(rw.currentDirPath, history[i])); err != nil { 485 reportInternalError(err) 486 } 487 } 488 489 return nil 490} 491 492func (rw *rollingFileWriter) getFileRollName(fileName string) string { 493 switch rw.nameMode { 494 case rollingNameModePostfix: 495 return fileName[len(rw.fileName+rollingLogHistoryDelimiter):] 496 case rollingNameModePrefix: 497 return fileName[:len(fileName)-len(rw.fileName+rollingLogHistoryDelimiter)] 498 } 499 return "" 500} 501 502func (rw *rollingFileWriter) roll() error { 503 // First, close current file. 504 err := rw.currentFile.Close() 505 if err != nil { 506 return err 507 } 508 rw.currentFile = nil 509 510 // Current history of all previous log files. 511 // For file roller it may be like this: 512 // * ... 513 // * file.log.4 514 // * file.log.5 515 // * file.log.6 516 // 517 // For date roller it may look like this: 518 // * ... 519 // * file.log.11.Aug.13 520 // * file.log.15.Aug.13 521 // * file.log.16.Aug.13 522 // Sorted log history does NOT include current file. 523 history, err := rw.getSortedLogHistory() 524 if err != nil { 525 return err 526 } 527 // Renames current file to create a new roll history entry 528 // For file roller it may be like this: 529 // * ... 530 // * file.log.4 531 // * file.log.5 532 // * file.log.6 533 // n file.log.7 <---- RENAMED (from file.log) 534 newHistoryName := rw.createFullFileName(rw.fileName, 535 rw.self.getNewHistoryRollFileName(history)) 536 537 err = os.Rename(filepath.Join(rw.currentDirPath, rw.currentName), filepath.Join(rw.currentDirPath, newHistoryName)) 538 if err != nil { 539 return err 540 } 541 542 // Finally, add the newly added history file to the history archive 543 // and, if after that the archive exceeds the allowed max limit, older rolls 544 // must the removed/archived. 545 history = append(history, newHistoryName) 546 if len(history) > rw.maxRolls { 547 err = rw.deleteOldRolls(history) 548 if err != nil { 549 return err 550 } 551 } 552 553 return nil 554} 555 556func (rw *rollingFileWriter) Write(bytes []byte) (n int, err error) { 557 rw.rollLock.Lock() 558 defer rw.rollLock.Unlock() 559 560 if rw.self.needsToRoll() { 561 if err := rw.roll(); err != nil { 562 return 0, err 563 } 564 } 565 566 if rw.currentFile == nil { 567 err := rw.createFileAndFolderIfNeeded(true) 568 if err != nil { 569 return 0, err 570 } 571 } 572 573 n, err = rw.currentFile.Write(bytes) 574 rw.currentFileSize += int64(n) 575 return n, err 576} 577 578func (rw *rollingFileWriter) Close() error { 579 if rw.currentFile != nil { 580 e := rw.currentFile.Close() 581 if e != nil { 582 return e 583 } 584 rw.currentFile = nil 585 } 586 return nil 587} 588 589func (rw *rollingFileWriter) tempArchiveFile(archiveDir string) (*os.File, error) { 590 tmp := filepath.Join(archiveDir, ".seelog_tmp") 591 if err := os.MkdirAll(tmp, defaultDirectoryPermissions); err != nil { 592 return nil, err 593 } 594 return ioutil.TempFile(tmp, "archived_logs") 595} 596 597// ============================================================================================= 598// Different types of rolling writers 599// ============================================================================================= 600 601// -------------------------------------------------- 602// Rolling writer by SIZE 603// -------------------------------------------------- 604 605// rollingFileWriterSize performs roll when file exceeds a specified limit. 606type rollingFileWriterSize struct { 607 *rollingFileWriter 608 maxFileSize int64 609} 610 611func NewRollingFileWriterSize(fpath string, atype rollingArchiveType, apath string, maxSize int64, maxRolls int, namemode rollingNameMode, archiveExploded bool) (*rollingFileWriterSize, error) { 612 rw, err := newRollingFileWriter(fpath, rollingTypeSize, atype, apath, maxRolls, namemode, archiveExploded, false) 613 if err != nil { 614 return nil, err 615 } 616 rws := &rollingFileWriterSize{rw, maxSize} 617 rws.self = rws 618 return rws, nil 619} 620 621func (rws *rollingFileWriterSize) needsToRoll() bool { 622 return rws.currentFileSize >= rws.maxFileSize 623} 624 625func (rws *rollingFileWriterSize) isFileRollNameValid(rname string) bool { 626 if len(rname) == 0 { 627 return false 628 } 629 _, err := strconv.Atoi(rname) 630 return err == nil 631} 632 633type rollSizeFileTailsSlice []string 634 635func (p rollSizeFileTailsSlice) Len() int { 636 return len(p) 637} 638func (p rollSizeFileTailsSlice) Less(i, j int) bool { 639 v1, _ := strconv.Atoi(p[i]) 640 v2, _ := strconv.Atoi(p[j]) 641 return v1 < v2 642} 643func (p rollSizeFileTailsSlice) Swap(i, j int) { 644 p[i], p[j] = p[j], p[i] 645} 646 647func (rws *rollingFileWriterSize) sortFileRollNamesAsc(fs []string) ([]string, error) { 648 ss := rollSizeFileTailsSlice(fs) 649 sort.Sort(ss) 650 return ss, nil 651} 652 653func (rws *rollingFileWriterSize) getNewHistoryRollFileName(otherLogFiles []string) string { 654 v := 0 655 if len(otherLogFiles) != 0 { 656 latest := otherLogFiles[len(otherLogFiles)-1] 657 v, _ = strconv.Atoi(rws.getFileRollName(latest)) 658 } 659 return fmt.Sprintf("%d", v+1) 660} 661 662func (rws *rollingFileWriterSize) getCurrentFileName() string { 663 return rws.fileName 664} 665 666func (rws *rollingFileWriterSize) String() string { 667 return fmt.Sprintf("Rolling file writer (By SIZE): filename: %s, archive: %s, archivefile: %s, maxFileSize: %v, maxRolls: %v", 668 rws.fileName, 669 rollingArchiveTypesStringRepresentation[rws.archiveType], 670 rws.archivePath, 671 rws.maxFileSize, 672 rws.maxRolls) 673} 674 675// -------------------------------------------------- 676// Rolling writer by TIME 677// -------------------------------------------------- 678 679// rollingFileWriterTime performs roll when a specified time interval has passed. 680type rollingFileWriterTime struct { 681 *rollingFileWriter 682 timePattern string 683 currentTimeFileName string 684} 685 686func NewRollingFileWriterTime(fpath string, atype rollingArchiveType, apath string, maxr int, 687 timePattern string, namemode rollingNameMode, archiveExploded bool, fullName bool) (*rollingFileWriterTime, error) { 688 689 rw, err := newRollingFileWriter(fpath, rollingTypeTime, atype, apath, maxr, namemode, archiveExploded, fullName) 690 if err != nil { 691 return nil, err 692 } 693 rws := &rollingFileWriterTime{rw, timePattern, ""} 694 rws.self = rws 695 return rws, nil 696} 697 698func (rwt *rollingFileWriterTime) needsToRoll() bool { 699 newName := time.Now().Format(rwt.timePattern) 700 701 if rwt.currentTimeFileName == "" { 702 // first run; capture the current name 703 rwt.currentTimeFileName = newName 704 return false 705 } 706 707 return newName != rwt.currentTimeFileName 708} 709 710func (rwt *rollingFileWriterTime) isFileRollNameValid(rname string) bool { 711 if len(rname) == 0 { 712 return false 713 } 714 _, err := time.ParseInLocation(rwt.timePattern, rname, time.Local) 715 return err == nil 716} 717 718type rollTimeFileTailsSlice struct { 719 data []string 720 pattern string 721} 722 723func (p rollTimeFileTailsSlice) Len() int { 724 return len(p.data) 725} 726 727func (p rollTimeFileTailsSlice) Less(i, j int) bool { 728 t1, _ := time.ParseInLocation(p.pattern, p.data[i], time.Local) 729 t2, _ := time.ParseInLocation(p.pattern, p.data[j], time.Local) 730 return t1.Before(t2) 731} 732 733func (p rollTimeFileTailsSlice) Swap(i, j int) { 734 p.data[i], p.data[j] = p.data[j], p.data[i] 735} 736 737func (rwt *rollingFileWriterTime) sortFileRollNamesAsc(fs []string) ([]string, error) { 738 ss := rollTimeFileTailsSlice{data: fs, pattern: rwt.timePattern} 739 sort.Sort(ss) 740 return ss.data, nil 741} 742 743func (rwt *rollingFileWriterTime) getNewHistoryRollFileName(_ []string) string { 744 newFileName := rwt.currentTimeFileName 745 rwt.currentTimeFileName = time.Now().Format(rwt.timePattern) 746 return newFileName 747} 748 749func (rwt *rollingFileWriterTime) getCurrentFileName() string { 750 if rwt.fullName { 751 return rwt.createFullFileName(rwt.fileName, time.Now().Format(rwt.timePattern)) 752 } 753 return rwt.fileName 754} 755 756func (rwt *rollingFileWriterTime) String() string { 757 return fmt.Sprintf("Rolling file writer (By TIME): filename: %s, archive: %s, archivefile: %s, pattern: %s, maxRolls: %v", 758 rwt.fileName, 759 rollingArchiveTypesStringRepresentation[rwt.archiveType], 760 rwt.archivePath, 761 rwt.timePattern, 762 rwt.maxRolls) 763} 764