@@ -51,6 +51,7 @@ type FileLogger struct {
51
51
// collateBuffer is used to store log entries to batch up multiple logs.
52
52
collateBuffer chan string
53
53
collateBufferWg * sync.WaitGroup
54
+ closed chan struct {}
54
55
flushChan chan struct {}
55
56
level LogLevel
56
57
name string
@@ -87,11 +88,11 @@ func NewFileLogger(ctx context.Context, config *FileLoggerConfig, level LogLevel
87
88
config = & FileLoggerConfig {}
88
89
}
89
90
90
- rotationDoneChan := make (chan struct {})
91
91
cancelCtx , cancelFunc := context .WithCancel (ctx )
92
92
93
93
// validate and set defaults
94
- if err := config .init (cancelCtx , level , name , logFilePath , minAge , rotationDoneChan ); err != nil {
94
+ rotationDoneChan , err := config .init (cancelCtx , level , name , logFilePath , minAge )
95
+ if err != nil {
95
96
cancelFunc ()
96
97
return nil , err
97
98
}
@@ -103,6 +104,7 @@ func NewFileLogger(ctx context.Context, config *FileLoggerConfig, level LogLevel
103
104
output : config .Output ,
104
105
logger : log .New (config .Output , "" , 0 ),
105
106
config : * config ,
107
+ closed : make (chan struct {}),
106
108
cancelFunc : cancelFunc ,
107
109
rotationDoneChan : rotationDoneChan ,
108
110
}
@@ -119,7 +121,7 @@ func NewFileLogger(ctx context.Context, config *FileLoggerConfig, level LogLevel
119
121
logger .collateBufferWg = & sync.WaitGroup {}
120
122
121
123
// Start up a single worker to consume messages from the buffer
122
- go logCollationWorker (logger .collateBuffer , logger .flushChan , logger .collateBufferWg , logger .logger , * config .CollationBufferSize , fileLoggerCollateFlushTimeout )
124
+ go logCollationWorker (logger .closed , logger . collateBuffer , logger .flushChan , logger .collateBufferWg , logger .logger , * config .CollationBufferSize , fileLoggerCollateFlushTimeout )
123
125
}
124
126
125
127
return logger , nil
@@ -152,21 +154,26 @@ func (l *FileLogger) Rotate() error {
152
154
153
155
// Close cancels the log rotation rotation and the underlying file descriptor for the active log file.
154
156
func (l * FileLogger ) Close () error {
157
+ // cancelFunc will stop the log rotionation/deletion goroutine
158
+ // once all log rotation is done and log output is closed, shut down the logCollationWorker
159
+ defer close (l .closed )
155
160
// cancel the log rotation goroutine and wait for it to stop
156
161
if l .cancelFunc != nil {
157
162
l .cancelFunc ()
158
163
}
164
+ // wait for the rotation goroutine to stop
159
165
if l .rotationDoneChan != nil {
160
166
<- l .rotationDoneChan
161
167
}
168
+
162
169
if c , ok := l .output .(io.Closer ); ok {
163
170
return c .Close ()
164
171
}
165
172
return nil
166
173
}
167
174
168
175
func (l * FileLogger ) String () string {
169
- return "FileLogger(" + l .level . String () + ")"
176
+ return "FileLogger(" + l .name + ")"
170
177
}
171
178
172
179
// logf will put the given message into the collation buffer if it exists,
@@ -210,9 +217,9 @@ func (l *FileLogger) getFileLoggerConfig() *FileLoggerConfig {
210
217
return & fileLoggerConfig
211
218
}
212
219
213
- func (lfc * FileLoggerConfig ) init (ctx context.Context , level LogLevel , name string , logFilePath string , minAge int , rotationDoneChan chan struct {}) error {
220
+ func (lfc * FileLoggerConfig ) init (ctx context.Context , level LogLevel , name string , logFilePath string , minAge int ) ( chan struct {}, error ) {
214
221
if lfc == nil {
215
- return errors .New ("nil LogFileConfig" )
222
+ return nil , errors .New ("nil LogFileConfig" )
216
223
}
217
224
218
225
if lfc .Enabled == nil {
@@ -221,18 +228,12 @@ func (lfc *FileLoggerConfig) init(ctx context.Context, level LogLevel, name stri
221
228
}
222
229
223
230
if err := lfc .initRotationConfig (name , defaultMaxSize , minAge ); err != nil {
224
- return err
231
+ return nil , err
225
232
}
226
233
227
- var rotateableLogger * lumberjack. Logger
234
+ var rotationDoneChan chan struct {}
228
235
if lfc .Output == nil {
229
- rotateableLogger = newLumberjackOutput (
230
- filepath .Join (filepath .FromSlash (logFilePath ), logFilePrefix + name + ".log" ),
231
- * lfc .Rotation .MaxSize ,
232
- * lfc .Rotation .MaxAge ,
233
- BoolDefault (lfc .Rotation .Compress , true ),
234
- )
235
- lfc .Output = rotateableLogger
236
+ rotationDoneChan = lfc .initLumberjack (ctx , name , filepath .Join (filepath .FromSlash (logFilePath ), logFilePrefix + name + ".log" ))
236
237
}
237
238
238
239
if lfc .CollationBufferSize == nil {
@@ -244,9 +245,25 @@ func (lfc *FileLoggerConfig) init(ctx context.Context, level LogLevel, name stri
244
245
lfc .CollationBufferSize = & bufferSize
245
246
}
246
247
248
+ return rotationDoneChan , nil
249
+ }
250
+
251
+ // initLumberjack will create a new Lumberjack logger from the given config settings. Returns a doneChan which fires when the log rotation is stopped.
252
+ func (lfc * FileLoggerConfig ) initLumberjack (ctx context.Context , name string , lumberjackFilename string ) chan struct {} {
253
+ rotationDoneChan := make (chan struct {})
254
+ dir , path := filepath .Split (lumberjackFilename )
255
+ prefix := path [0 :strings .Index (path , "." )]
256
+ rotateableLogger := & lumberjack.Logger {
257
+ Filename : lumberjackFilename ,
258
+ MaxSize : * lfc .Rotation .MaxSize ,
259
+ MaxAge : * lfc .Rotation .MaxAge ,
260
+ Compress : BoolDefault (lfc .Rotation .Compress , true ),
261
+ }
262
+ lfc .Output = rotateableLogger
263
+
247
264
var rotationTicker * time.Ticker
248
265
var rotationTickerCh <- chan time.Time
249
- if i := lfc .Rotation .RotationInterval .Value (); i > 0 && rotateableLogger != nil {
266
+ if i := lfc .Rotation .RotationInterval .Value (); i > 0 {
250
267
rotationTicker = time .NewTicker (i )
251
268
rotationTickerCh = rotationTicker .C
252
269
}
@@ -268,7 +285,7 @@ func (lfc *FileLoggerConfig) init(ctx context.Context, level LogLevel, name stri
268
285
close (rotationDoneChan )
269
286
return
270
287
case <- logDeletionTicker .C :
271
- err := runLogDeletion (ctx , logFilePath , level . String () , int (float64 (* lfc .Rotation .RotatedLogsSizeLimit )* rotatedLogsLowWatermarkMultiplier ), * lfc .Rotation .RotatedLogsSizeLimit )
288
+ err := runLogDeletion (ctx , dir , prefix , int (float64 (* lfc .Rotation .RotatedLogsSizeLimit )* rotatedLogsLowWatermarkMultiplier ), * lfc .Rotation .RotatedLogsSizeLimit )
272
289
if err != nil {
273
290
WarnfCtx (ctx , "%s" , err )
274
291
}
@@ -281,10 +298,10 @@ func (lfc *FileLoggerConfig) init(ctx context.Context, level LogLevel, name stri
281
298
}
282
299
}
283
300
}()
284
-
285
- return nil
301
+ return rotationDoneChan
286
302
}
287
303
304
+ // initRotationConfig will validate the log rotation settings and set defaults where necessary.
288
305
func (lfc * FileLoggerConfig ) initRotationConfig (name string , defaultMaxSize , minAge int ) error {
289
306
if lfc .Rotation .MaxSize == nil {
290
307
lfc .Rotation .MaxSize = & defaultMaxSize
@@ -322,20 +339,10 @@ func (lfc *FileLoggerConfig) initRotationConfig(name string, defaultMaxSize, min
322
339
return nil
323
340
}
324
341
325
- func newLumberjackOutput (filename string , maxSize , maxAge int , compress bool ) * lumberjack.Logger {
326
- return & lumberjack.Logger {
327
- Filename : filename ,
328
- MaxSize : maxSize ,
329
- MaxAge : maxAge ,
330
- Compress : compress ,
331
- }
332
- }
333
-
334
342
// runLogDeletion will delete rotated logs for the supplied logLevel. It will only perform these deletions when the
335
343
// cumulative size of the logs are above the supplied sizeLimitMB.
336
344
// logDirectory is the supplied directory where the logs are stored.
337
- func runLogDeletion (ctx context.Context , logDirectory string , logLevel string , sizeLimitMBLowWatermark int , sizeLimitMBHighWatermark int ) (err error ) {
338
-
345
+ func runLogDeletion (ctx context.Context , logDirectory string , logPrefix string , sizeLimitMBLowWatermark int , sizeLimitMBHighWatermark int ) (err error ) {
339
346
sizeLimitMBLowWatermark = sizeLimitMBLowWatermark * 1024 * 1024 // Convert MB input to bytes
340
347
sizeLimitMBHighWatermark = sizeLimitMBHighWatermark * 1024 * 1024 // Convert MB input to bytes
341
348
@@ -352,7 +359,7 @@ func runLogDeletion(ctx context.Context, logDirectory string, logLevel string, s
352
359
willDelete := false
353
360
for i := len (files ) - 1 ; i >= 0 ; i -- {
354
361
file := files [i ]
355
- if strings .HasPrefix (file .Name (), logFilePrefix + logLevel ) && strings .HasSuffix (file .Name (), ".log .gz" ) {
362
+ if strings .HasPrefix (file .Name (), logPrefix ) && strings .HasSuffix (file .Name (), ".gz" ) {
356
363
fi , err := file .Info ()
357
364
if err != nil {
358
365
InfofCtx (ctx , KeyAll , "Couldn't get size of log file %q: %v - ignoring for cleanup calculation" , file .Name (), err )
@@ -373,7 +380,7 @@ func runLogDeletion(ctx context.Context, logDirectory string, logLevel string, s
373
380
if willDelete {
374
381
for j := indexDeletePoint ; j >= 0 ; j -- {
375
382
file := files [j ]
376
- if strings .HasPrefix (file .Name (), logFilePrefix + logLevel ) && strings .HasSuffix (file .Name (), ".log .gz" ) {
383
+ if strings .HasPrefix (file .Name (), logPrefix ) && strings .HasSuffix (file .Name (), ".gz" ) {
377
384
err = os .Remove (filepath .Join (logDirectory , file .Name ()))
378
385
if err != nil {
379
386
return errors .New (fmt .Sprintf ("Error deleting stale log file: %v" , err ))
0 commit comments