diff options
Diffstat (limited to 'internal/loglevel/rotate.go')
| -rw-r--r-- | internal/loglevel/rotate.go | 109 |
1 files changed, 109 insertions, 0 deletions
diff --git a/internal/loglevel/rotate.go b/internal/loglevel/rotate.go new file mode 100644 index 0000000..bfd8351 --- /dev/null +++ b/internal/loglevel/rotate.go @@ -0,0 +1,109 @@ +package loglevel + +import ( + "io" + "os" + "sync" + + "codeberg.org/eduVPN/eduvpn-common/internal/atomicfile" +) + +var ( + // MaxSize is the maximum size in bytes from when to start trimming + MaxSize int64 = 10 * 1024 * 1024 + + // TrimSize denotes how much to trim from the beginning + TrimSize = MaxSize / 2 + + // TrimMsg is the message to display when it was trimmed + TrimMsg = "--- previous part was trimmed by eduvpn-common as the file was too big (10MB) ---\n" +) + +// FileRotater is a file that is trimmed when a maximum size is reached +// This is for logging useful +type FileRotater struct { + filename string + file *os.File + mu sync.Mutex +} + +// NewFileRotater creates a new log file rotater +func NewFileRotater(filename string) (*FileRotater, error) { + fr := &FileRotater{ + filename: filename, + } + + err := fr.open() + if err != nil { + return nil, err + } + return fr, nil +} + +func (fr *FileRotater) open() error { + f, err := os.OpenFile(fr.filename, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o666) + if err != nil { + return err + } + fr.file = f + return nil +} + +func (fr *FileRotater) trim() error { + // We need to seek to the trim size to skip over that part as we discard it + _, err := fr.file.Seek(TrimSize, io.SeekStart) + if err != nil { + return err + } + + // get the part of the file that we want to keep + keep, err := io.ReadAll(fr.file) + if err != nil { + return err + } + + all := []byte(TrimMsg) + all = append(all, keep...) + err = atomicfile.WriteFile(fr.file.Name(), all, 0o666) + if err != nil { + return err + } + + // re-open the handle as the file was renamed + err = fr.file.Close() + if err != nil { + return err + } + err = fr.open() + if err != nil { + return err + } + return nil +} + +// Write implements io.Writer for the log rotater +func (fr *FileRotater) Write(p []byte) (n int, err error) { + fr.mu.Lock() + defer fr.mu.Unlock() + fi, err := fr.file.Stat() + if err != nil { + return 0, err + } + + if fi.Size() >= MaxSize { + err = fr.trim() + if err != nil { + return 0, err + } + } + // we don't write atomically here as we want it to be as fast as possible + // and if we lose a part of one log statement it's not a big deal + return fr.file.Write(p) +} + +// Close closes the file in a safe way by locking and unlocking the mutex +func (fr *FileRotater) Close() error { + fr.mu.Lock() + defer fr.mu.Unlock() + return fr.file.Close() +} |
