mirror of
https://github.com/kemko/nomad.git
synced 2026-01-01 16:05:42 +03:00
The Nomad agent used a log filter to ensure logs were written at the expected level. Since the use of hclog this is not required, as hclog acts as the gate keeper and filter for logging. All log writers accept messages from hclog which has already done the filtering.
157 lines
3.9 KiB
Go
157 lines
3.9 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package agent
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
var (
|
|
now = time.Now
|
|
)
|
|
|
|
// logFile is used to setup a file based logger that also performs log rotation
|
|
type logFile struct {
|
|
|
|
//Name of the log file
|
|
fileName string
|
|
|
|
//Path to the log file
|
|
logPath string
|
|
|
|
//Duration between each file rotation operation
|
|
duration time.Duration
|
|
|
|
//LastCreated represents the creation time of the latest log
|
|
LastCreated time.Time
|
|
|
|
//FileInfo is the pointer to the current file being written to
|
|
FileInfo *os.File
|
|
|
|
//MaxBytes is the maximum number of desired bytes for a log file
|
|
MaxBytes int
|
|
|
|
//BytesWritten is the number of bytes written in the current log file
|
|
BytesWritten int64
|
|
|
|
// Max rotated files to keep before removing them.
|
|
MaxFiles int
|
|
|
|
//acquire is the mutex utilized to ensure we have no concurrency issues
|
|
acquire sync.Mutex
|
|
}
|
|
|
|
func (l *logFile) fileNamePattern() string {
|
|
// Extract the file extension
|
|
fileExt := filepath.Ext(l.fileName)
|
|
// If we have no file extension we append .log
|
|
if fileExt == "" {
|
|
fileExt = ".log"
|
|
}
|
|
// Remove the file extension from the filename
|
|
return strings.TrimSuffix(l.fileName, fileExt) + "-%s" + fileExt
|
|
}
|
|
|
|
func (l *logFile) openNew() error {
|
|
newfilePath := filepath.Join(l.logPath, l.fileName)
|
|
|
|
// Try creating or opening the active log file. Since the active log file
|
|
// always has the same name, append log entries to prevent overwriting
|
|
// previous log data.
|
|
filePointer, err := os.OpenFile(newfilePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0640)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
stat, err := filePointer.Stat()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
l.FileInfo = filePointer
|
|
l.BytesWritten = stat.Size()
|
|
l.LastCreated = l.createTime(stat)
|
|
return nil
|
|
}
|
|
|
|
func (l *logFile) rotate() error {
|
|
// Get the time from the last point of contact
|
|
timeElapsed := time.Since(l.LastCreated)
|
|
// Rotate if we hit the byte file limit or the time limit
|
|
if (l.BytesWritten >= int64(l.MaxBytes) && (l.MaxBytes > 0)) || timeElapsed >= l.duration {
|
|
l.FileInfo.Close()
|
|
|
|
// Move current log file to a timestamped file.
|
|
rotateTime := now()
|
|
rotatefileName := fmt.Sprintf(l.fileNamePattern(), strconv.FormatInt(rotateTime.UnixNano(), 10))
|
|
oldPath := l.FileInfo.Name()
|
|
newPath := filepath.Join(l.logPath, rotatefileName)
|
|
if err := os.Rename(oldPath, newPath); err != nil {
|
|
return fmt.Errorf("failed to rotate log files: %v", err)
|
|
}
|
|
|
|
if err := l.pruneFiles(); err != nil {
|
|
return fmt.Errorf("failed to prune log files: %v", err)
|
|
}
|
|
return l.openNew()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (l *logFile) pruneFiles() error {
|
|
if l.MaxFiles == 0 {
|
|
return nil
|
|
}
|
|
pattern := l.fileNamePattern()
|
|
//get all the files that match the log file pattern
|
|
globExpression := filepath.Join(l.logPath, fmt.Sprintf(pattern, "*"))
|
|
matches, err := filepath.Glob(globExpression)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Sort the strings as filepath.Glob does not publicly guarantee that files
|
|
// are sorted, so here we add an extra defensive sort.
|
|
sort.Strings(matches)
|
|
|
|
// Prune if there are more files stored than the configured max
|
|
stale := len(matches) - l.MaxFiles
|
|
for i := 0; i < stale; i++ {
|
|
if err := os.Remove(matches[i]); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Write is used to implement io.Writer.
|
|
//
|
|
// Nomad's log file capability is fed by go-hclog which is responsible for
|
|
// performing the log level filtering. It is not needed here.
|
|
func (l *logFile) Write(b []byte) (int, error) {
|
|
l.acquire.Lock()
|
|
defer l.acquire.Unlock()
|
|
|
|
//Create a new file if we have no file to write to
|
|
if l.FileInfo == nil {
|
|
if err := l.openNew(); err != nil {
|
|
return 0, err
|
|
}
|
|
}
|
|
// Check for the last contact and rotate if necessary
|
|
if err := l.rotate(); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
n, err := l.FileInfo.Write(b)
|
|
l.BytesWritten += int64(n)
|
|
return n, err
|
|
}
|