Skip to content

Commit c19fc0d

Browse files
committed
log: suppress identical messages within every 5-second window
1 parent 6a22f95 commit c19fc0d

File tree

6 files changed

+85
-41
lines changed

6 files changed

+85
-41
lines changed

cli/supervise.go

+14
Original file line numberDiff line numberDiff line change
@@ -181,3 +181,17 @@ func HandleDaemonSignals() {
181181
signal.Ignore(syscall.SIGPIPE)
182182
signal.Ignore(syscall.SIGHUP)
183183
}
184+
185+
// ClearDedupBuffersInBackground periodically clears the global LRU buffers used
186+
// for de-duplicating log messages.
187+
func ClearDedupBuffersInBackground() {
188+
tickerChan := time.Tick(5 * time.Second)
189+
go func() {
190+
for {
191+
select {
192+
case <-tickerChan:
193+
lalog.ClearDedupBuffers()
194+
}
195+
}
196+
}()
197+
}

datastruct/lru.go

+8-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ func NewLeastRecentlyUsedBuffer(maxCapacity int) *LeastRecentlyUsedBuffer {
2323
return &LeastRecentlyUsedBuffer{
2424
maxCapacity: maxCapacity,
2525
usageCounter: 0,
26-
lastUsed: make(map[string]uint64),
26+
lastUsed: make(map[string]uint64, maxCapacity),
2727
mutex: new(sync.RWMutex),
2828
}
2929
}
@@ -81,6 +81,13 @@ func (lru *LeastRecentlyUsedBuffer) Len() int {
8181
return len(lru.lastUsed)
8282
}
8383

84+
// Clear the buffer.
85+
func (lru *LeastRecentlyUsedBuffer) Clear() {
86+
lru.mutex.Lock()
87+
defer lru.mutex.Unlock()
88+
lru.lastUsed = make(map[string]uint64, lru.maxCapacity)
89+
}
90+
8491
func (lru *LeastRecentlyUsedBuffer) String() string {
8592
return fmt.Sprintf("%+v", lru.lastUsed)
8693
}

datastruct/lru_test.go

+5
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@ func TestLeastRecentlyUsedBuffer(t *testing.T) {
7070
t.Fatal("element went missing", addAndEvict.add)
7171
}
7272
}
73+
74+
lru.Clear()
75+
if lru.Len() != 0 {
76+
t.Fatal("did not clear: ", lru.Len())
77+
}
7378
}
7479

7580
func TestLeastRecentlyUsedBuffer_Remove(t *testing.T) {

lalog/logger.go

+53-37
Original file line numberDiff line numberDiff line change
@@ -28,25 +28,37 @@ const (
2828
type LogWarningCallbackFunc func(componentName, componentID, funcName string, actorName interface{}, err error, msg string)
2929

3030
var (
31-
// LatestWarnings are a small number of the most recent log messages (warnings and info messages ) kept in memory for retrieval and inspection.
31+
// LatestWarnings are a small number of the most recent log messages
32+
// (warnings and info messages) kept in memory for retrieval and inspection.
3233
LatestLogs = datastruct.NewRingBuffer(NumLatestLogEntries)
3334

3435
// LatestWarnings are a small number of the most recent warning log messages kept in memory for retrieval and inspection.
3536
LatestWarnings = datastruct.NewRingBuffer(NumLatestLogEntries)
3637

37-
// LatestWarningActors are a small number of actors that have recently generated warning messages.
38-
// The LRU buffer helps to gain a more comprehensive picture of the actors that have resulted in warning log messages by
39-
// working as a conditional filter, so that only the first instance of warning from an actor (identified by component name + function name +
40-
// actor name) will be added to the in-memory warning log buffer. The subsequent warning messages of that actor will be excluded from the
41-
// warning buffer. The actor will get another chance to show up in the warning buffer when it eventually becomes stale and is subsequently
42-
// evicted by this LRU buffer.
43-
LatestWarningActors = datastruct.NewLeastRecentlyUsedBuffer(NumLatestLogEntries / 4)
38+
// LatestWarningActors a small number of log entry actors that have
39+
// generated the latest log messages. The buffer provides a daemon-agnostic
40+
// mechanism that de-duplicates repeated log messages, to avoid flooding
41+
// stderr too hard, and makes the latest log entries retrieved on-demand
42+
// much easier to read.
43+
LatestWarningActors = datastruct.NewLeastRecentlyUsedBuffer(NumLatestLogEntries)
44+
45+
// LatestLogMessageContent are small number of recent log messages.
46+
// The buffer provides a daemon-agnostic mechanism that de-duplicates
47+
// repeated log messages, to avoid flooding stderr too hard, and makes the
48+
// latest log entries retrieved on-demand much easier to read.
49+
LatestLogMessageContent = datastruct.NewLeastRecentlyUsedBuffer(NumLatestLogEntries)
4450

4551
// LogWarningCallback is invoked in a separate goroutine after any logger has processed a warning message.
4652
// The function must avoid generating a warning log message of itself, to avoid an infinite recursion.
4753
GlobalLogWarningCallback LogWarningCallbackFunc = nil
4854
)
4955

56+
// Clear the global LRU buffers used for de-duplicating log messages.
57+
func ClearDedupBuffers() {
58+
LatestWarningActors.Clear()
59+
LatestLogMessageContent.Clear()
60+
}
61+
5062
/*
5163
LoggerIDField is a field of Logger's ComponentID, all fields that make up a ComponentID offer log entry a clue as to
5264
which component instance generated the log message.
@@ -124,45 +136,49 @@ func callerName(skip int) string {
124136
return filepath.Base(file) + ":" + funName
125137
}
126138

127-
// Print a log message and keep the message in warnings buffer.
128-
func (logger *Logger) Warning(actorName interface{}, err error, template string, values ...interface{}) {
129-
functionName := callerName(2)
130-
msg := logger.Format(functionName, actorName, err, template, values...)
131-
msgWithTime := time.Now().Format("2006-01-02 15:04:05 ") + msg
132-
log.Print(msg)
133-
// All warning messages to to the latest logs buffer
134-
LatestLogs.Push(msgWithTime)
139+
func (logger *Logger) warning(funcName string, actorName interface{}, err error, template string, values ...interface{}) {
135140
// As determined by the LRU buffer, only the first instance of warning from this actor (identified by component name + function name +
136141
// actor name) will be added to the in-memory warning log buffer, this helps to gain a more comprehensive picture of actors behind latest
137142
// warning messages by suppressing the most noisy actors.
138-
if alreadyPresent, _ := LatestWarningActors.Add(functionName + fmt.Sprint(actorName)); !alreadyPresent {
139-
LatestWarnings.Push(msgWithTime)
140-
if GlobalLogWarningCallback != nil {
141-
go GlobalLogWarningCallback(logger.ComponentName, logger.getComponentIDs(), functionName, actorName, err, fmt.Sprintf(template, values...))
142-
}
143+
if alreadyPresent, _ := LatestWarningActors.Add(funcName + fmt.Sprint(actorName)); alreadyPresent {
144+
return
143145
}
144-
}
146+
msg := logger.Format(funcName, actorName, err, template, values...)
147+
log.Print(msg)
145148

146-
// Print a log message and keep the message in latest log buffer. If there is an error, also keep the message in warnings buffer.
147-
func (logger *Logger) Info(actorName interface{}, err error, template string, values ...interface{}) {
148-
functionName := callerName(2)
149-
msg := logger.Format(functionName, actorName, err, template, values...)
150149
msgWithTime := time.Now().Format("2006-01-02 15:04:05 ") + msg
151150
LatestLogs.Push(msgWithTime)
152-
log.Print(msg)
153-
// If the log message comes with an error, treat it as a warning.
151+
LatestWarnings.Push(msgWithTime)
152+
153+
if GlobalLogWarningCallback != nil {
154+
go GlobalLogWarningCallback(logger.ComponentName, logger.getComponentIDs(), funcName, actorName, err, fmt.Sprintf(template, values...))
155+
}
156+
}
157+
158+
// Print a log message and keep the message in warnings buffer.
159+
func (logger *Logger) Warning(actorName interface{}, err error, template string, values ...interface{}) {
160+
funcName := callerName(2)
161+
logger.warning(funcName, actorName, err, template, values...)
162+
}
163+
164+
func (logger *Logger) info(funcName string, actorName interface{}, err error, template string, values ...interface{}) {
154165
if err != nil {
155-
// As determined by the LRU buffer, only the first instance of warning from this actor (identified by component name + function name +
156-
// actor name) will be added to the in-memory warning log buffer, this helps to gain a more comprehensive picture of actors behind latest
157-
// warning messages by suppressing the most noisy actors.
158-
if alreadyPresent, _ := LatestWarningActors.Add(functionName + fmt.Sprint(actorName)); !alreadyPresent {
159-
LatestWarnings.Push(msgWithTime)
160-
if GlobalLogWarningCallback != nil {
161-
go GlobalLogWarningCallback(logger.ComponentName, logger.getComponentIDs(), functionName, actorName, err, fmt.Sprintf(template, values...))
162-
}
163-
}
166+
// If the log message comes with an error, treat it as a warning.
167+
logger.warning(funcName, actorName, err, template, values...)
168+
}
169+
msg := logger.Format(funcName, actorName, err, template, values...)
170+
if alreadyPresent, _ := LatestLogMessageContent.Add(msg); alreadyPresent {
164171
return
165172
}
173+
msgWithTime := time.Now().Format("2006-01-02 15:04:05 ") + msg
174+
log.Print(msg)
175+
LatestLogs.Push(msgWithTime)
176+
}
177+
178+
// Print a log message and keep the message in latest log buffer. If there is an error, also keep the message in warnings buffer.
179+
func (logger *Logger) Info(actorName interface{}, err error, template string, values ...interface{}) {
180+
funcName := callerName(2)
181+
logger.info(funcName, actorName, err, template, values...)
166182
}
167183

168184
func (logger *Logger) Abort(actorName interface{}, err error, template string, values ...interface{}) {

lalog/logger_test.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ func TestLogger_Warningf(t *testing.T) {
106106
logger := Logger{}
107107
// The first warning originated from Adam went into in-memory warning buffer
108108
logger.Warning("adam", nil, "")
109-
// The second warning also originated from Adam, therefore it still gets printed though it is excluded from the buffer.
109+
// The second warning is de-duplicated.
110110
logger.Warning("adam", nil, "")
111111

112112
var countLog, countWarn int
@@ -122,7 +122,7 @@ func TestLogger_Warningf(t *testing.T) {
122122
}
123123
return true
124124
})
125-
if countLog != 2 || countWarn != 1 {
125+
if countLog != 1 || countWarn != 1 {
126126
t.Fatal(countLog, countWarn)
127127
}
128128

@@ -144,7 +144,8 @@ func TestLogger_Warningf(t *testing.T) {
144144
}
145145
return true
146146
})
147-
if countLog != 4 || countWarn != 2 {
147+
// "adam" and "eve" are recorded in the all-message and warning buffers.
148+
if countLog != 2 || countWarn != 2 {
148149
t.Fatal(countLog, countWarn)
149150
}
150151
}

main.go

+1
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ func main() {
112112
// Enable common diagnosis and security features.
113113
logger.Info(nil, nil, "program is starting, here is a summary of the runtime environment:\n%s", platform.GetProgramStatusSummary(false))
114114
platform.LockMemory()
115+
cli.ClearDedupBuffersInBackground()
115116
cli.ReseedPseudoRandAndInBackground(logger)
116117
cli.StartProfilingServer(logger, pprofHTTPPort)
117118
if debug {

0 commit comments

Comments
 (0)