@ -8,12 +8,18 @@
package logger
import (
"container/list"
"fmt"
"io"
"log"
"sync"
"golang.org/x/time/rate"
)
// Logf is the basic Tailscale logger type: a printf-like func.
// Like log.Printf, the format need not end in a newline.
// Logf functions should be safe for concurrent use.
type Logf func ( format string , args ... interface { } )
// WithPrefix wraps f, prefixing each format with the provided prefix.
@ -42,3 +48,56 @@ func (w funcWriter) Write(p []byte) (int, error) {
// Discard is a Logf that throws away the logs given to it.
func Discard ( string , ... interface { } ) { }
// limitData is used to keep track of each format string's associated
// rate-limiting data.
type limitData struct {
lim * rate . Limiter // the token bucket associated with this string
msgBlocked bool // whether a "duplicate error" message has already been logged
ele * list . Element // list element used to access this string in the cache
}
// RateLimitedFn implements rate limiting by fstring on a given Logf.
// Messages are allowed through at a maximum of f messages/second, in
// bursts of up to b messages at a time. Up to m strings will be held at a time.
func RateLimitedFn ( logf Logf , f float64 , b int , m int ) Logf {
r := rate . Limit ( f )
msgLim := make ( map [ string ] * limitData )
msgCache := list . New ( ) // a rudimentary LRU that limits the size of the map
mu := & sync . Mutex { }
return func ( format string , args ... interface { } ) {
mu . Lock ( )
rl , ok := msgLim [ format ]
if ok {
msgCache . MoveToFront ( rl . ele )
if rl . lim . Allow ( ) {
mu . Lock ( )
rl . msgBlocked = false
mu . Unlock ( )
logf ( format , args ... )
} else {
if ! rl . msgBlocked {
rl . msgBlocked = true
mu . Unlock ( )
logf ( "Repeated messages were suppressed by rate limiting. Original message: %s" ,
fmt . Sprintf ( format , args ... ) )
} else {
mu . Unlock ( )
}
}
} else {
msgLim [ format ] = & limitData { rate . NewLimiter ( r , b ) , false , msgCache . PushFront ( format ) }
msgLim [ format ] . lim . Allow ( )
mu . Unlock ( )
logf ( format , args ... )
}
mu . Lock ( )
if msgCache . Len ( ) > m {
delete ( msgLim , msgCache . Back ( ) . Value . ( string ) )
msgCache . Remove ( msgCache . Back ( ) )
}
mu . Unlock ( )
}
}