-
Notifications
You must be signed in to change notification settings - Fork 475
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
These loggers can be set up to log to one underlying logging function if if the time or count threshold since the first time it was logged with has not yet been reached, and another log function for if the threshold has been reached. An example use case would be to log a message at Debug level, until the same message has been seen 5 times, or for 1 minute, after which it would be logged at Error level until reset.
- Loading branch information
1 parent
b359dbc
commit a322ae1
Showing
4 changed files
with
210 additions
and
43 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
package ephemeralerror | ||
|
||
import ( | ||
"math" | ||
"time" | ||
|
||
"go.uber.org/atomic" | ||
) | ||
|
||
type LogFn func(msg string, ctx ...interface{}) | ||
|
||
func NoLog(msg string, ctx ...interface{}) { | ||
} | ||
|
||
const notTriggered = math.MinInt64 | ||
|
||
type Logger interface { | ||
Error(msg string, ctx ...interface{}) | ||
Reset() | ||
} | ||
|
||
type TimeEphemeralErrorLogger struct { | ||
logFnBeforeTriggered LogFn | ||
logFnAfterTriggered LogFn | ||
continuousDurationTrigger time.Duration | ||
|
||
firstTriggerTime atomic.Int64 | ||
} | ||
|
||
func NewTimeEphemeralErrorLogger( | ||
logFnBeforeTriggered LogFn, | ||
logFnAfterTriggered LogFn, | ||
continuousDurationTrigger time.Duration, | ||
) *TimeEphemeralErrorLogger { | ||
e := TimeEphemeralErrorLogger{ | ||
logFnBeforeTriggered: logFnBeforeTriggered, | ||
logFnAfterTriggered: logFnAfterTriggered, | ||
continuousDurationTrigger: continuousDurationTrigger, | ||
} | ||
e.Reset() | ||
return &e | ||
} | ||
|
||
func (e *TimeEphemeralErrorLogger) Error(msg string, ctx ...interface{}) { | ||
now := time.Now() | ||
first := e.firstTriggerTime.CompareAndSwap(notTriggered, now.UnixMicro()) | ||
if !first && e.firstTriggerTime.Load() < now.Add(-e.continuousDurationTrigger).UnixMicro() { | ||
e.logFnAfterTriggered(msg, ctx) | ||
} else { | ||
e.logFnBeforeTriggered(msg, ctx) | ||
} | ||
} | ||
|
||
func (e *TimeEphemeralErrorLogger) Reset() { | ||
e.firstTriggerTime.Store(notTriggered) | ||
} | ||
|
||
type CountEphemeralErrorLogger struct { | ||
logFnBeforeTriggered LogFn | ||
logFnAfterTriggered LogFn | ||
errorCountTrigger int64 | ||
|
||
errorCount atomic.Int64 | ||
} | ||
|
||
func NewCountEphemeralErrorLogger( | ||
logFnBeforeTriggered LogFn, | ||
logFnAfterTriggered LogFn, | ||
errorCountTrigger int64, | ||
) *CountEphemeralErrorLogger { | ||
e := CountEphemeralErrorLogger{ | ||
logFnBeforeTriggered: logFnBeforeTriggered, | ||
logFnAfterTriggered: logFnAfterTriggered, | ||
errorCountTrigger: errorCountTrigger, | ||
} | ||
e.Reset() | ||
return &e | ||
} | ||
|
||
func (e *CountEphemeralErrorLogger) Error(msg string, ctx ...interface{}) { | ||
if e.errorCount.Add(1) > e.errorCountTrigger { | ||
e.logFnAfterTriggered(msg, ctx) | ||
} else { | ||
e.logFnBeforeTriggered(msg, ctx) | ||
} | ||
|
||
} | ||
|
||
func (e *CountEphemeralErrorLogger) Reset() { | ||
e.errorCount.Store(0) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
package ephemeralerror | ||
|
||
import ( | ||
"math/rand" | ||
"sync" | ||
"sync/atomic" | ||
"testing" | ||
"time" | ||
) | ||
|
||
func TestCountEphemeralError(t *testing.T) { | ||
var warnCount, errorCount atomic.Int64 | ||
e := NewCountEphemeralErrorLogger( | ||
func(msg string, ctx ...interface{}) { warnCount.Add(1) }, | ||
func(msg string, ctx ...interface{}) { errorCount.Add(1) }, | ||
10, | ||
) | ||
|
||
for run := 0; run < 1000; run++ { | ||
var errorCountTrigger, nEvents int64 = rand.Int63n(100), rand.Int63n(100) | ||
e.errorCountTrigger = errorCountTrigger | ||
expectedWarns := errorCountTrigger | ||
expectedErrors := nEvents - expectedWarns | ||
|
||
if expectedErrors < 0 { | ||
expectedWarns = nEvents | ||
expectedErrors = 0 | ||
} | ||
|
||
wg := sync.WaitGroup{} | ||
for i := int64(0); i < nEvents; i++ { | ||
wg.Add(1) | ||
go func() { | ||
e.Error("bbq!") | ||
wg.Done() | ||
}() | ||
} | ||
|
||
wg.Wait() | ||
|
||
if warnCount.Load() != expectedWarns || errorCount.Load() != expectedErrors { | ||
t.Fatalf("unexpected warnCount, errorCount (%d, %d), expected (%d, %d), %d", warnCount.Load(), errorCount.Load(), expectedWarns, expectedErrors, nEvents) | ||
} | ||
|
||
e.Reset() | ||
warnCount.Store(0) | ||
errorCount.Store(0) | ||
} | ||
} | ||
|
||
func TestTimeEphemeralError(t *testing.T) { | ||
var warnCount, errorCount atomic.Int64 | ||
e := NewTimeEphemeralErrorLogger( | ||
func(msg string, ctx ...interface{}) { warnCount.Add(1) }, | ||
func(msg string, ctx ...interface{}) { errorCount.Add(1) }, | ||
time.Second, | ||
) | ||
|
||
for run := 0; run < 10; run++ { | ||
totalDuration := (time.Duration(rand.Int63n(20)) + 5) * time.Millisecond * 50 | ||
e.continuousDurationTrigger = totalDuration | ||
|
||
var expectedWarns, expectedErrors int64 = rand.Int63n(9) + 1, rand.Int63n(9) + 1 | ||
totalEvents := expectedWarns + expectedErrors | ||
period := totalDuration / time.Duration(expectedWarns) | ||
for i := int64(0); i < totalEvents; i++ { | ||
e.Error("bbq!") | ||
time.Sleep(period) | ||
} | ||
|
||
if warnCount.Load() != expectedWarns || errorCount.Load() != expectedErrors { | ||
t.Fatalf("unexpected warnCount, errorCount (%d, %d), expected (%d, %d), %v, %v", warnCount.Load(), errorCount.Load(), expectedWarns, expectedErrors, totalDuration, period) | ||
} | ||
|
||
e.Reset() | ||
warnCount.Store(0) | ||
errorCount.Store(0) | ||
} | ||
|
||
} |