-
Notifications
You must be signed in to change notification settings - Fork 23
/
handler_rotating_file.go
291 lines (266 loc) · 7.84 KB
/
handler_rotating_file.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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
package logging
import (
"fmt"
"os"
"sync"
"time"
)
// Check whether the specified directory/file exists or not.
func FileExists(filename string) bool {
if _, err := os.Stat(filename); err != nil {
if os.IsNotExist(err) {
return false
}
}
return true
}
// An interface for rotating handler abstraction.
type RotatingHandler interface {
Handler
// Determine if rollover should occur.
ShouldRollover(record *LogRecord) (doRollover bool, message string)
// Do a rollover.
DoRollover() error
}
// Base class for handlers that rotate log files at certain point.
// Not meant to be instantiated directly. Insteed, use RotatingFileHandler
// or TimedRotatingFileHandler.
type BaseRotatingHandler struct {
*FileHandler
}
// Initialize base rotating handler with specified filename for stream logging.
func NewBaseRotatingHandler(
filepath string, mode, bufferSize int) (*BaseRotatingHandler, error) {
fileHandler, err := NewFileHandler(filepath, mode, bufferSize)
if err != nil {
return nil, err
}
object := &BaseRotatingHandler{
FileHandler: fileHandler,
}
Closer.RemoveHandler(object.FileHandler)
Closer.AddHandler(object)
return object, nil
}
// A helper function for subclass to emit record.
func (self *BaseRotatingHandler) RolloverEmit(
handler RotatingHandler, record *LogRecord) error {
// We don't use the implementation of StreamHandler.Emit2() but directly
// write to stream here in order to avoid calling self.Format() twice
// for performance optimization.
doRollover, message := handler.ShouldRollover(record)
if doRollover {
if err := handler.Flush(); err != nil {
return err
}
if err := handler.DoRollover(); err != nil {
return err
}
}
// Message already has a trailing '\n'.
err := self.GetStream().Write(message)
if err != nil {
return err
}
return nil
}
type HandleFunc func(record *LogRecord) int
// Handler for logging to a set of files, which switches from one file to
// the next when the current file reaches a certain size.
type RotatingFileHandler struct {
*BaseRotatingHandler
maxBytes uint64
backupCount uint32
bufferFlushTime time.Duration
inputChanSize int
handleFunc HandleFunc
inputChan chan *LogRecord
group *sync.WaitGroup
}
// Open the specified file and use it as the stream for logging.
//
// By default, the file grows indefinitely. You can specify particular values
// of maxBytes and backupCount to allow the file to rollover at a predetermined
// size.
//
// Rollover occurs whenever the current log file is nearly maxBytes in length.
// If backupCount is >= 1, the system will successively create new files with
// the same pathname as the base file, but with extensions ".1", ".2" etc.
// append to it. For example, with a backupCount of 5 and a base file name of
// "app.log", you would get "app.log", "app.log.1", "app.log.2", ...
// through to "app.log.5". The file being written to is always "app.log" - when
// it gets filled up, it is closed and renamed to "app.log.1", and if files
// "app.log.1", "app.log.2" etc. exist, then they are renamed to "app.log.2",
// "app.log.3" etc. respectively.
//
// If maxBytes is zero, rollover never occurs.
//
// bufferSize specifies the size of the internal buffer. If it is positive,
// the internal buffer will be enabled, the logs will be first written into
// the internal buffer, when the internal buffer is full all buffer content
// will be flushed to file.
// bufferFlushTime specifies the time for flushing the internal buffer
// in period, no matter the buffer is full or not. Its value is used only when
// inputChanSize is set to positive.
// inputChanSize specifies the chan size of the handler. If it is positive,
// this handler will be initialized as a standardlone go routine to handle
// log message.
func NewRotatingFileHandler(
filepath string,
mode int,
bufferSize int,
bufferFlushTime time.Duration,
inputChanSize int,
maxBytes uint64,
backupCount uint32) (*RotatingFileHandler, error) {
// If rotation/rollover is wanted, it doesn't make sense to use another
// mode. If for example 'w' were specified, then if there were multiple
// runs of the calling application, the logs from previous runs would be
// lost if the "os.O_TRUNC" is respected, because the log file would be
// truncated on each run.
if maxBytes > 0 {
mode = os.O_APPEND
}
base, err := NewBaseRotatingHandler(filepath, mode, bufferSize)
if err != nil {
return nil, err
}
object := &RotatingFileHandler{
BaseRotatingHandler: base,
maxBytes: maxBytes,
backupCount: backupCount,
bufferFlushTime: bufferFlushTime,
inputChanSize: inputChanSize,
}
// register object to closer
Closer.RemoveHandler(object.BaseRotatingHandler)
Closer.AddHandler(object)
if inputChanSize > 0 {
object.handleFunc = object.handleChan
object.inputChan = make(chan *LogRecord, inputChanSize)
object.group = &sync.WaitGroup{}
object.group.Add(1)
go func() {
defer object.group.Done()
object.loop()
}()
} else {
object.handleFunc = object.handleCall
}
return object, nil
}
func MustNewRotatingFileHandler(
filepath string,
mode int,
bufferSize int,
bufferFlushTime time.Duration,
inputChanSize int,
maxBytes uint64,
backupCount uint32) *RotatingFileHandler {
handler, err := NewRotatingFileHandler(
filepath,
mode,
bufferSize,
bufferFlushTime,
inputChanSize,
maxBytes,
backupCount)
if err != nil {
panic("NewRotatingFileHandler(), error: " + err.Error())
}
return handler
}
// Determine if rollover should occur.
// Basically, see if the supplied record would cause the file to exceed the
// size limit we have.
func (self *RotatingFileHandler) ShouldRollover(
record *LogRecord) (bool, string) {
message := self.Format(record)
if self.maxBytes > 0 {
offset, err := self.GetStream().Tell()
if err != nil {
// don't trigger rollover action if we lose offset info
return false, message
}
if (uint64(offset) + uint64(len(message))) > self.maxBytes {
return true, message
}
}
return false, message
}
// Rotate source file to destination file if source file exists.
func (self *RotatingFileHandler) RotateFile(sourceFile, destFile string) error {
if FileExists(sourceFile) {
if FileExists(destFile) {
if err := os.Remove(destFile); err != nil {
return err
}
}
if err := os.Rename(sourceFile, destFile); err != nil {
return err
}
}
return nil
}
// Do a rollover, as described above.
func (self *RotatingFileHandler) DoRollover() (err error) {
self.FileHandler.Close()
defer func() {
if e := self.FileHandler.Open(); e != nil {
if e == nil {
err = e
}
}
}()
if self.backupCount > 0 {
filepath := self.GetFilePath()
for i := self.backupCount - 1; i > 0; i-- {
sourceFile := fmt.Sprintf("%s.%d", filepath, i)
destFile := fmt.Sprintf("%s.%d", filepath, i+1)
if err := self.RotateFile(sourceFile, destFile); err != nil {
return err
}
}
destFile := fmt.Sprintf("%s.%d", filepath, 1)
if err := self.RotateFile(filepath, destFile); err != nil {
return err
}
}
return nil
}
// Emit a record.
func (self *RotatingFileHandler) Emit(record *LogRecord) error {
return self.RolloverEmit(self, record)
}
func (self *RotatingFileHandler) handleCall(record *LogRecord) int {
return self.Handle2(self, record)
}
func (self *RotatingFileHandler) handleChan(record *LogRecord) int {
self.inputChan <- record
return 0
}
func (self *RotatingFileHandler) loop() {
ticker := time.NewTicker(self.bufferFlushTime)
for {
select {
case r := <-self.inputChan:
if r == nil {
return
}
self.Handle2(self, r)
case <-ticker.C:
self.Flush()
}
}
}
func (self *RotatingFileHandler) Handle(record *LogRecord) int {
return self.handleFunc(record)
}
func (self *RotatingFileHandler) Close() {
if self.inputChanSize > 0 {
// send a nil record as "stop signal" to exit loop.
self.inputChan <- nil
self.group.Wait()
}
self.BaseRotatingHandler.Close()
}