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() }