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