github.com/adityamillind98/nomad@v0.11.8/command/agent/log_file.go (about) 1 package agent 2 3 import ( 4 "fmt" 5 "os" 6 "path/filepath" 7 "sort" 8 "strconv" 9 "strings" 10 "sync" 11 "time" 12 13 "github.com/hashicorp/logutils" 14 ) 15 16 var ( 17 now = time.Now 18 ) 19 20 // logFile is used to setup a file based logger that also performs log rotation 21 type logFile struct { 22 // Log level Filter to filter out logs that do not matcch LogLevel criteria 23 logFilter *logutils.LevelFilter 24 25 //Name of the log file 26 fileName string 27 28 //Path to the log file 29 logPath string 30 31 //Duration between each file rotation operation 32 duration time.Duration 33 34 //LastCreated represents the creation time of the latest log 35 LastCreated time.Time 36 37 //FileInfo is the pointer to the current file being written to 38 FileInfo *os.File 39 40 //MaxBytes is the maximum number of desired bytes for a log file 41 MaxBytes int 42 43 //BytesWritten is the number of bytes written in the current log file 44 BytesWritten int64 45 46 // Max rotated files to keep before removing them. 47 MaxFiles int 48 49 //acquire is the mutex utilized to ensure we have no concurrency issues 50 acquire sync.Mutex 51 } 52 53 func (l *logFile) fileNamePattern() string { 54 // Extract the file extension 55 fileExt := filepath.Ext(l.fileName) 56 // If we have no file extension we append .log 57 if fileExt == "" { 58 fileExt = ".log" 59 } 60 // Remove the file extension from the filename 61 return strings.TrimSuffix(l.fileName, fileExt) + "-%s" + fileExt 62 } 63 64 func (l *logFile) openNew() error { 65 fileNamePattern := l.fileNamePattern() 66 // New file name has the format : filename-timestamp.extension 67 createTime := now() 68 newfileName := fmt.Sprintf(fileNamePattern, strconv.FormatInt(createTime.UnixNano(), 10)) 69 newfilePath := filepath.Join(l.logPath, newfileName) 70 // Try creating a file. We truncate the file because we are the only authority to write the logs 71 filePointer, err := os.OpenFile(newfilePath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0640) 72 if err != nil { 73 return err 74 } 75 76 l.FileInfo = filePointer 77 // New file, new bytes tracker, new creation time :) 78 l.LastCreated = createTime 79 l.BytesWritten = 0 80 return nil 81 } 82 83 func (l *logFile) rotate() error { 84 // Get the time from the last point of contact 85 timeElapsed := time.Since(l.LastCreated) 86 // Rotate if we hit the byte file limit or the time limit 87 if (l.BytesWritten >= int64(l.MaxBytes) && (l.MaxBytes > 0)) || timeElapsed >= l.duration { 88 l.FileInfo.Close() 89 if err := l.pruneFiles(); err != nil { 90 return err 91 } 92 return l.openNew() 93 } 94 return nil 95 } 96 97 func (l *logFile) pruneFiles() error { 98 if l.MaxFiles == 0 { 99 return nil 100 } 101 pattern := l.fileNamePattern() 102 //get all the files that match the log file pattern 103 globExpression := filepath.Join(l.logPath, fmt.Sprintf(pattern, "*")) 104 matches, err := filepath.Glob(globExpression) 105 if err != nil { 106 return err 107 } 108 109 // Stort the strings as filepath.Glob does not publicly guarantee that files 110 // are sorted, so here we add an extra defensive sort. 111 sort.Strings(matches) 112 113 // Prune if there are more files stored than the configured max 114 stale := len(matches) - l.MaxFiles 115 for i := 0; i < stale; i++ { 116 if err := os.Remove(matches[i]); err != nil { 117 return err 118 } 119 } 120 return nil 121 } 122 123 // Write is used to implement io.Writer 124 func (l *logFile) Write(b []byte) (int, error) { 125 // Filter out log entries that do not match log level criteria 126 if !l.logFilter.Check(b) { 127 return 0, nil 128 } 129 130 l.acquire.Lock() 131 defer l.acquire.Unlock() 132 //Create a new file if we have no file to write to 133 if l.FileInfo == nil { 134 if err := l.openNew(); err != nil { 135 return 0, err 136 } 137 } 138 // Check for the last contact and rotate if necessary 139 if err := l.rotate(); err != nil { 140 return 0, err 141 } 142 143 n, err := l.FileInfo.Write(b) 144 l.BytesWritten += int64(n) 145 return n, err 146 }