-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathplugin_logger.go
More file actions
471 lines (409 loc) · 13.2 KB
/
plugin_logger.go
File metadata and controls
471 lines (409 loc) · 13.2 KB
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
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
/*
* Copyright 2025 The Go-Spring Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package log
import (
"sync/atomic"
"time"
"github.com/go-spring/stdlib/errutil"
)
func init() {
RegisterConverter(ParseBufferFullPolicy)
RegisterPlugin[SyncLogger]("Logger")
RegisterPlugin[SyncLogger]("SyncLogger")
RegisterPlugin[AsyncLogger]("AsyncLogger")
RegisterPlugin[DiscardLogger]("DiscardLogger")
RegisterPlugin[ConsoleLogger]("ConsoleLogger")
RegisterPlugin[FileLogger]("FileLogger")
RegisterPlugin[RollingFileLogger]("RollingFileLogger")
}
// Logger is the interface implemented by all logger implementations.
// A Logger receives log events and forwards them to one or more appenders.
type Logger interface {
Lifecycle // Start/Stop methods for resource management
GetName() string // Appender's name
GetTags() []string // Tags associated with this logger
GetLevel() LevelRange // Level range handled by this logger
Append(e *Event) // Handles writing a log event
}
// AppenderRef represents a reference to an Appender by name.
// During configuration loading, the Ref field is resolved and the
// corresponding Appender instance is injected into the Appender field.
//
// Level optionally restricts the level range forwarded to this appender.
type AppenderRef struct {
Appender
Ref string `PluginAttribute:"ref"`
Level LevelRange `PluginAttribute:"level,default="`
}
// Append forwards the event to the referenced appender if the level matches.
func (c *AppenderRef) Append(e *Event) {
if c.Level.Enable(e.Level) {
c.Appender.Append(e)
}
}
// AppenderRefs is implemented by loggers that support appender references.
type AppenderRefs interface {
// GetAppenderRefs returns the logger's synchronization mode
// and the list of appender references.
//
// In sync mode, appenders may be invoked concurrently by multiple goroutines,
// so they must ensure thread safety.
//
// In async mode, appenders are invoked by a single background goroutine,
// so they do not require strict thread safety.
GetAppenderRefs() (syncMode bool, _ []*AppenderRef)
}
// LoggerBase contains fields shared by all logger configurations.
type LoggerBase struct {
Name string `PluginAttribute:"name"` // Logger name
Tags []string `PluginAttribute:"tag,default=*"` // Optional tags associated with this logger
Level LevelRange `PluginAttribute:"level,default="` // Level range handled by this logger
}
func (c *LoggerBase) GetName() string { return c.Name }
func (c *LoggerBase) GetTags() []string { return c.Tags }
func (c *LoggerBase) GetLevel() LevelRange { return c.Level }
var (
_ Logger = (*DiscardLogger)(nil)
_ Logger = (*ConsoleLogger)(nil)
_ Logger = (*SyncLogger)(nil)
_ Logger = (*AsyncLogger)(nil)
_ Logger = (*FileLogger)(nil)
_ Logger = (*RollingFileLogger)(nil)
)
// SyncLogger is a synchronous logger that forwards events to appenders
// immediately in the caller goroutine.
type SyncLogger struct {
LoggerBase
AppenderRefs []*AppenderRef `PluginElement:"appenderRef"`
}
// GetAppenderRefs returns true for sync mode and the appender refs.
func (c *SyncLogger) GetAppenderRefs() (syncMode bool, _ []*AppenderRef) {
return true, c.AppenderRefs
}
func (c *SyncLogger) Start() error { return nil }
func (c *SyncLogger) Stop() {}
// Append sends the event directly to appenders.
func (c *SyncLogger) Append(e *Event) {
if c.Level.Enable(e.Level) {
for _, r := range c.AppenderRefs {
r.Append(e)
}
}
e.Reset()
}
// BufferFullPolicy specifies how AsyncLogger behaves when its buffer is full.
type BufferFullPolicy int
const (
BufferFullPolicyBlock = BufferFullPolicy(0) // Block until space is available
BufferFullPolicyDiscard = BufferFullPolicy(1) // Drop the new event or data
BufferFullPolicyDropOldest = BufferFullPolicy(2) // Drop the oldest buffered item
)
// ParseBufferFullPolicy converts a string to a BufferFullPolicy.
func ParseBufferFullPolicy(s string) (BufferFullPolicy, error) {
switch s {
case "block":
return BufferFullPolicyBlock, nil
case "discard":
return BufferFullPolicyDiscard, nil
case "drop-oldest":
return BufferFullPolicyDropOldest, nil
default:
return -1, errutil.Explain(nil, "invalid BufferFullPolicy %s", s)
}
}
// AsyncLogger is an asynchronous logger that buffers events in a channel
// and processes them in a background goroutine.
type AsyncLogger struct {
LoggerBase
AppenderRefs []*AppenderRef `PluginElement:"appenderRef"`
BufferSize int `PluginAttribute:"bufferSize,default=10000"`
OnBufferFull BufferFullPolicy `PluginAttribute:"onBufferFull,default=discard"`
buf chan *Event // Channel buffering events
wait chan struct{} // Waiting for the worker goroutine to finish
stop *Event // Sentinel value used to signal shutdown
discardCounter atomic.Int64 // Count of discarded events
}
// GetDiscardCounter returns the total number of discarded events.
func (c *AsyncLogger) GetDiscardCounter() int64 {
return c.discardCounter.Load()
}
// GetAppenderRefs returns false for async mode and the appender references.
func (c *AsyncLogger) GetAppenderRefs() (syncMode bool, _ []*AppenderRef) {
return false, c.AppenderRefs
}
// Start initializes the buffer and starts the background worker goroutine.
func (c *AsyncLogger) Start() error {
if c.BufferSize < 100 {
return errutil.Explain(nil, "bufferSize is too small") // todo details
}
c.buf = make(chan *Event, c.BufferSize)
c.wait = make(chan struct{})
c.stop = &Event{}
// Worker goroutine that processes events from the buffer
// and forwards them to appenders.
go func() {
for e := range c.buf {
// Make a best effort to flush all logs before exiting.
if e == c.stop {
break
}
for _, r := range c.AppenderRefs {
r.Append(e)
}
e.Reset()
}
close(c.wait)
}()
return nil
}
// Stop gracefully shuts down the AsyncLogger.
// It guarantees that events already in the buffer before the stop signal
// are processed before the background worker goroutine exits.
func (c *AsyncLogger) Stop() {
// To ensure that more log events are written, a blocking approach is used here.
c.buf <- c.stop
<-c.wait
close(c.buf)
}
// Append enqueues a log event into the async buffer.
// Behavior on full buffer depends on BufferFullPolicy.
func (c *AsyncLogger) Append(e *Event) {
if !c.Level.Enable(e.Level) {
e.Reset()
return
}
select {
case c.buf <- e:
return
default:
}
switch c.OnBufferFull {
case BufferFullPolicyDropOldest:
for {
select {
case x := <-c.buf: // Remove one element to make space
c.discardCounter.Add(1)
x.Reset()
default: // for linter
}
select {
case c.buf <- e:
return
default: // for linter
}
}
case BufferFullPolicyBlock:
c.buf <- e // Block until space is available
case BufferFullPolicyDiscard:
c.discardCounter.Add(1)
e.Reset()
default: // for linter
}
}
// DiscardLogger ignores all log events (no-op).
type DiscardLogger struct {
LoggerBase
}
func (d DiscardLogger) Start() error { return nil }
func (d DiscardLogger) Stop() {}
func (d DiscardLogger) Append(e *Event) { e.Reset() }
// ConsoleLogger writes log events to standard output.
type ConsoleLogger struct {
LoggerBase
appender *ConsoleAppender
Layout Layout `PluginElement:"layout,default=TextLayout"`
}
// Start initializes the console appender and starts it.
func (c *ConsoleLogger) Start() error {
c.appender = &ConsoleAppender{
AppenderBase: AppenderBase{
Layout: c.Layout,
},
}
// Append operation is not managed by the framework,
// so we start the appender manually.
return c.appender.Start()
}
// Stop stops the console appender manually.
func (c *ConsoleLogger) Stop() {
// Appenders are not managed by the framework,
// so they need to be manually stopped.
c.appender.Stop()
}
// Append writes the event to the console if its level is enabled.
func (c *ConsoleLogger) Append(e *Event) {
if c.Level.Enable(e.Level) {
c.appender.Append(e)
}
e.Reset()
}
// FileLogger writes log events to a file.
type FileLogger struct {
LoggerBase
Layout Layout `PluginElement:"layout,default=TextLayout"`
FileDir string `PluginAttribute:"dir,default=./logs"`
FileName string `PluginAttribute:"file"`
appender *FileAppender
}
// Start initializes the file appender and starts it.
func (c *FileLogger) Start() error {
c.appender = &FileAppender{
AppenderBase: AppenderBase{
Layout: c.Layout,
},
FileDir: c.FileDir,
FileName: c.FileName,
}
// Append operation is not managed by the framework,
// so we start the appender manually.
return c.appender.Start()
}
// Stop stops the file appender manually.
func (c *FileLogger) Stop() {
// Appenders are not managed by the framework,
// so they need to be stopped manually.
c.appender.Stop()
}
// Append writes the log event to the file if its level is enabled.
func (c *FileLogger) Append(e *Event) {
if c.Level.Enable(e.Level) {
c.appender.Append(e)
}
e.Reset()
}
// RollingFileLogger writes log events to files with time-based rotation
// and optional level-based separation. It supports both synchronous and
// asynchronous modes.
type RollingFileLogger struct {
LoggerBase
// Internal logger used to dispatch events.
// It is either a SyncLogger or AsyncLogger depending on AsyncWrite.
logger Logger
// Internal appenders created during Start().
// Not configured directly by users.
appenders []*AppenderRef
// Layout used to format log events.
Layout Layout `PluginElement:"layout,default=TextLayout"`
// Directory where log files are stored.
// Defaults to "./logs".
FileDir string `PluginAttribute:"dir,default=./logs"`
// Base name of the log file.
// Actual file names may include rotation suffixes.
FileName string `PluginAttribute:"file"`
// If true, warning and error logs are written to a separate file
// with ".wf" suffix (e.g. app.log.wf).
//
// In this mode:
// - normal log file contains levels < WARN
// - ".wf" file contains WARN and above
Separate bool `PluginAttribute:"separate,default=false"`
// Rotation interval for log files.
// A new file is created after each interval (e.g. 1h, 24h).
Interval time.Duration `PluginAttribute:"interval,default=1h"`
// Maximum retention duration for old log files.
// Files older than this duration will be automatically removed.
MaxAge time.Duration `PluginAttribute:"maxAge,default=168h"`
// Whether to enable asynchronous logging.
AsyncWrite bool `PluginAttribute:"async,default=false"`
// Size of the buffer used in async mode.
// Ignored if AsyncWrite is false.
BufferSize int `PluginAttribute:"bufferSize,default=10000"`
// Behavior when async buffer is full.
// Ignored if AsyncWrite is false.
OnBufferFull BufferFullPolicy `PluginAttribute:"onBufferFull,default=discard"`
}
// Start initializes the internal logger and configures rolling file appenders.
// Depending on AsyncWrite, either SyncLogger or AsyncLogger will be used.
func (f *RollingFileLogger) Start() error {
normalMaxLevel := MaxLevel
if f.Separate {
normalMaxLevel = WarnLevel
}
// Create the appender for the normal log file
f.appenders = []*AppenderRef{
{
Appender: &RollingFileAppender{
AppenderBase: AppenderBase{
Layout: f.Layout,
},
FileDir: f.FileDir,
FileName: f.FileName,
Interval: f.Interval,
MaxAge: f.MaxAge,
SyncLock: !f.AsyncWrite,
},
Level: LevelRange{
MinLevel: f.Level.MinLevel,
MaxLevel: normalMaxLevel,
},
},
}
if f.Separate {
// Create the second appender for warning/error logs.
f.appenders = append(f.appenders, &AppenderRef{
Appender: &RollingFileAppender{
AppenderBase: AppenderBase{
Layout: f.Layout,
},
FileDir: f.FileDir,
FileName: f.FileName + ".wf",
Interval: f.Interval,
MaxAge: f.MaxAge,
SyncLock: !f.AsyncWrite,
},
Level: LevelRange{
MinLevel: normalMaxLevel,
MaxLevel: f.Level.MaxLevel,
},
})
}
// Initialize the underlay logger
if f.AsyncWrite {
f.logger = &AsyncLogger{
LoggerBase: f.LoggerBase,
AppenderRefs: f.appenders,
BufferSize: f.BufferSize,
OnBufferFull: f.OnBufferFull,
}
} else {
f.logger = &SyncLogger{
LoggerBase: f.LoggerBase,
AppenderRefs: f.appenders,
}
}
// Start the appenders manually (since they aren't managed by the framework)
for _, a := range f.appenders {
if err := a.Start(); err != nil {
return err
}
}
return f.logger.Start()
}
// Stop stops all appenders managed by this logger
// and gracefully shuts down the logger.
func (f *RollingFileLogger) Stop() {
// Stop the logger first to ensure buffered events are written
f.logger.Stop()
// Appenders are not managed by the framework,
// so they need to be stopped manually.
for _, a := range f.appenders {
a.Stop()
}
}
// Append forwards the log event to the internal logger.
func (f *RollingFileLogger) Append(e *Event) {
f.logger.Append(e)
}