-
Notifications
You must be signed in to change notification settings - Fork 3
/
rolling_file_appender.go
170 lines (138 loc) · 4.24 KB
/
rolling_file_appender.go
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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
package logbuch
import (
"errors"
"os"
"path/filepath"
"sync"
)
const (
defaultFiles = 1
defaultFileSize = 1024 * 1024 * 5 // 5 MB
defaultBufferSize = 4096 // 4 KB
)
// NameSchema is an interface to generate log file names.
// If you implement this interface, make sure the Name() method returns unique file names.
type NameSchema interface {
// Name returns the next file name used to store log data.
Name() string
}
// RollingFileAppender is a manager for rolling log files.
// It needs to be closed using the Close() method.
type RollingFileAppender struct {
files int
fileSize int
fileName NameSchema
fileDir string
buffer []byte
maxBufferSize int
currentFile *os.File
currentFileSize int
fileNames []string
m sync.Mutex
}
// NewRollingFileAppender creates a new RollingFileAppender.
// If you pass values below or equal to 0 for files, size or bufferSize, default values will be used.
// The file output directory is created if required and can be left empty to use the current directory.
// The filename schema is required. Note that the rolling file appender uses the filename schema you provide,
// so if it returns the same name on each call, it will overwrite the existing log file.
// The RollingFileAppender won't clean the log directory on startup. Old log files will stay in place.
func NewRollingFileAppender(files, size, bufferSize int, dir string, filename NameSchema) (*RollingFileAppender, error) {
if files <= 0 {
files = defaultFiles
}
if size <= 0 {
size = defaultFileSize
}
if bufferSize <= 0 {
bufferSize = defaultBufferSize
}
if filename == nil {
return nil, errors.New("filename schema must be specified")
}
if err := os.MkdirAll(dir, 0774); err != nil {
return nil, err
}
appender := &RollingFileAppender{files: files,
fileSize: size,
fileName: filename,
fileDir: dir,
buffer: make([]byte, 0, bufferSize),
maxBufferSize: bufferSize,
fileNames: make([]string, 0, files)}
if err := appender.nextFile(); err != nil {
return nil, err
}
return appender, nil
}
// Write writes given data to the rolling log files.
// This might not happen immediately as the RollingFileAppender uses a buffer.
// If you want the data to be persisted, call Flush().
func (appender *RollingFileAppender) Write(p []byte) (n int, err error) {
appender.m.Lock()
defer appender.m.Unlock()
if len(appender.buffer) >= appender.maxBufferSize {
if err := appender.flush(); err != nil {
return 0, err
}
}
appender.buffer = append(appender.buffer, p...)
return len(p), nil
}
// Flush writes all log data currently in buffer into the currently active log file.
func (appender *RollingFileAppender) Flush() error {
appender.m.Lock()
defer appender.m.Unlock()
return appender.flush()
}
// Close flushes the log data and closes all open file handlers.
func (appender *RollingFileAppender) Close() error {
appender.m.Lock()
defer appender.m.Unlock()
if err := appender.flush(); err != nil {
return err
}
return appender.currentFile.Close()
}
func (appender *RollingFileAppender) flush() error {
n, err := appender.currentFile.Write(appender.buffer)
if err != nil {
return err
}
appender.buffer = appender.buffer[:0]
appender.currentFileSize += n
if appender.currentFileSize >= appender.fileSize {
if err := appender.nextFile(); err != nil {
return err
}
}
return nil
}
func (appender *RollingFileAppender) nextFile() error {
if appender.currentFile != nil {
if err := appender.currentFile.Close(); err != nil {
return err
}
}
path := filepath.Join(appender.fileDir, appender.fileName.Name())
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0664)
if err != nil {
return err
}
appender.currentFile = f
appender.currentFileSize = 0
return appender.updateFiles(path)
}
func (appender *RollingFileAppender) updateFiles(path string) error {
appender.fileNames = append(appender.fileNames, path)
if len(appender.fileNames) > appender.files {
n := len(appender.fileNames) - appender.files
filesToDelete := appender.fileNames[:n]
appender.fileNames = appender.fileNames[n:]
for _, file := range filesToDelete {
if err := os.Remove(file); err != nil {
return err
}
}
}
return nil
}