safing-portmaster/base/log/writer.go

119 lines
2.7 KiB
Go

package log
import (
"fmt"
"os"
"path/filepath"
"sync"
"time"
)
// GlobalWriter is the global log writer.
var GlobalWriter *LogWriter = nil
type LogWriter struct {
writeLock sync.Mutex
isStdout bool
file *os.File
}
// NewStdoutWriter creates a new log writer thet will write to the stdout.
func NewStdoutWriter() *LogWriter {
return &LogWriter{
file: os.Stdout,
isStdout: true,
}
}
// NewFileWriter creates a new log writer that will write to a file. The file path will be <dir>/2006-01-02_15-04-05.log (with current date and time)
func NewFileWriter(dir string) (*LogWriter, error) {
// Make sure log dir exists, if not, create with strict permission, as logs can contain sensitive data.
_ = os.MkdirAll(dir, 0o700)
// Open new log file.
logFile := time.Now().UTC().Format("2006-01-02_15-04-05") + ".log"
file, err := os.Create(filepath.Join(dir, logFile))
if err != nil {
return nil, err
}
return &LogWriter{
file: file,
isStdout: false,
}, nil
}
// Write writes the buffer to the writer.
func (l *LogWriter) Write(buf []byte) (int, error) {
if l == nil {
return 0, fmt.Errorf("log writer not initialized")
}
// No need to lock in stdout context.
if !l.isStdout {
l.writeLock.Lock()
defer l.writeLock.Unlock()
}
return l.file.Write(buf)
}
// WriteMessage writes the message to the writer.
func (l *LogWriter) WriteMessage(msg Message, duplicates uint64) {
if l == nil {
return
}
// No need to lock in stdout context.
if !l.isStdout {
l.writeLock.Lock()
defer l.writeLock.Unlock()
}
fmt.Fprintln(l.file, formatLine(msg.(*logLine), duplicates, l.isStdout))
}
// IsStdout returns true if writer was initialized with stdout.
func (l *LogWriter) IsStdout() bool {
return l != nil && l.isStdout
}
// Close closes the writer.
func (l *LogWriter) Close() {
if l != nil && !l.isStdout {
_ = l.file.Close()
}
}
// CleanOldLogs deletes all log files in given directory that are older than the given threshold.
func CleanOldLogs(dir string, threshold time.Duration) error {
// Get current log file name.
var currentLogFile string
if GlobalWriter != nil && GlobalWriter.file != nil {
currentLogFile = GlobalWriter.file.Name()
}
// Read dir entries.
files, err := os.ReadDir(dir)
if err != nil {
return fmt.Errorf("failed to read dir: %w", err)
}
// Remove files older than threshold
deleteOlderThan := time.Now().Add(-threshold)
for _, f := range files {
// Skip directories and the current log file.
if f.IsDir() || f.Name() == currentLogFile {
continue
}
// Delete log files.
if fileInfo, err := f.Info(); err == nil {
if fileInfo.ModTime().Before(deleteOlderThan) {
_ = os.Remove(filepath.Join(dir, f.Name()))
}
}
}
return nil
}