1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
|
package loglevel
import (
"io"
"os"
"sync"
"herkulessi.de/git/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()
}
|