2020-02-05 22:16:58 +00:00
|
|
|
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
// Package logger defines a type for writing to logs. It's just a
|
|
|
|
// convenience type so that we don't have to pass verbose func(...)
|
|
|
|
// types around.
|
|
|
|
package logger
|
|
|
|
|
2020-03-06 19:59:46 +00:00
|
|
|
import (
|
2020-05-08 19:21:36 +00:00
|
|
|
"container/list"
|
|
|
|
"fmt"
|
2020-03-06 19:59:46 +00:00
|
|
|
"io"
|
|
|
|
"log"
|
2020-05-08 19:21:36 +00:00
|
|
|
"sync"
|
|
|
|
|
|
|
|
"golang.org/x/time/rate"
|
2020-03-06 19:59:46 +00:00
|
|
|
)
|
|
|
|
|
2020-02-25 19:35:46 +00:00
|
|
|
// Logf is the basic Tailscale logger type: a printf-like func.
|
2020-04-11 15:35:34 +00:00
|
|
|
// Like log.Printf, the format need not end in a newline.
|
2020-05-08 19:21:36 +00:00
|
|
|
// Logf functions should be safe for concurrent use.
|
2020-02-25 19:35:46 +00:00
|
|
|
type Logf func(format string, args ...interface{})
|
|
|
|
|
|
|
|
// WithPrefix wraps f, prefixing each format with the provided prefix.
|
|
|
|
func WithPrefix(f Logf, prefix string) Logf {
|
|
|
|
return func(format string, args ...interface{}) {
|
|
|
|
f(prefix+format, args...)
|
|
|
|
}
|
|
|
|
}
|
2020-03-06 19:59:46 +00:00
|
|
|
|
|
|
|
// FuncWriter returns an io.Writer that writes to f.
|
|
|
|
func FuncWriter(f Logf) io.Writer {
|
|
|
|
return funcWriter{f}
|
|
|
|
}
|
|
|
|
|
|
|
|
// StdLogger returns a standard library logger from a Logf.
|
|
|
|
func StdLogger(f Logf) *log.Logger {
|
|
|
|
return log.New(FuncWriter(f), "", 0)
|
|
|
|
}
|
|
|
|
|
|
|
|
type funcWriter struct{ f Logf }
|
|
|
|
|
|
|
|
func (w funcWriter) Write(p []byte) (int, error) {
|
|
|
|
w.f("%s", p)
|
|
|
|
return len(p), nil
|
|
|
|
}
|
2020-04-07 02:15:19 +00:00
|
|
|
|
|
|
|
// Discard is a Logf that throws away the logs given to it.
|
|
|
|
func Discard(string, ...interface{}) {}
|
2020-05-08 19:21:36 +00:00
|
|
|
|
|
|
|
// 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() {
|
|
|
|
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()
|
|
|
|
}
|
|
|
|
}
|