164 lines
3.9 KiB
Go
164 lines
3.9 KiB
Go
|
// Package log provides logging utilities for the tracer.
|
||
|
package log
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"log"
|
||
|
"os"
|
||
|
"strconv"
|
||
|
"sync"
|
||
|
"time"
|
||
|
|
||
|
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace"
|
||
|
"gopkg.in/DataDog/dd-trace-go.v1/internal/version"
|
||
|
)
|
||
|
|
||
|
// Level specifies the logging level that the log package prints at.
|
||
|
type Level int
|
||
|
|
||
|
const (
|
||
|
// LevelDebug represents debug level messages.
|
||
|
LevelDebug Level = iota
|
||
|
// LevelWarn represents warning and errors.
|
||
|
LevelWarn
|
||
|
)
|
||
|
|
||
|
var prefixMsg = fmt.Sprintf("Datadog Tracer %s", version.Tag)
|
||
|
|
||
|
var (
|
||
|
mu sync.RWMutex // guards below fields
|
||
|
level = LevelWarn
|
||
|
logger ddtrace.Logger = &defaultLogger{l: log.New(os.Stderr, "", log.LstdFlags)}
|
||
|
)
|
||
|
|
||
|
// UseLogger sets l as the active logger.
|
||
|
func UseLogger(l ddtrace.Logger) {
|
||
|
mu.Lock()
|
||
|
defer mu.Unlock()
|
||
|
logger = l
|
||
|
}
|
||
|
|
||
|
// SetLevel sets the given lvl for logging.
|
||
|
func SetLevel(lvl Level) {
|
||
|
mu.Lock()
|
||
|
defer mu.Unlock()
|
||
|
level = lvl
|
||
|
}
|
||
|
|
||
|
// Debug prints the given message if the level is LevelDebug.
|
||
|
func Debug(fmt string, a ...interface{}) {
|
||
|
mu.RLock()
|
||
|
lvl := level
|
||
|
mu.RUnlock()
|
||
|
if lvl != LevelDebug {
|
||
|
return
|
||
|
}
|
||
|
printMsg("DEBUG", fmt, a...)
|
||
|
}
|
||
|
|
||
|
// Warn prints a warning message.
|
||
|
func Warn(fmt string, a ...interface{}) {
|
||
|
printMsg("WARN", fmt, a...)
|
||
|
}
|
||
|
|
||
|
var (
|
||
|
errmu sync.RWMutex // guards below fields
|
||
|
erragg = map[string]*errorReport{} // aggregated errors
|
||
|
errrate = time.Minute // the rate at which errors are reported
|
||
|
erron bool // true if errors are being aggregated
|
||
|
)
|
||
|
|
||
|
func init() {
|
||
|
if v := os.Getenv("DD_LOGGING_RATE"); v != "" {
|
||
|
if sec, err := strconv.ParseUint(v, 10, 64); err != nil {
|
||
|
Warn("Invalid value for DD_LOGGING_RATE: %v", err)
|
||
|
} else {
|
||
|
errrate = time.Duration(sec) * time.Second
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type errorReport struct {
|
||
|
first time.Time // time when first error occurred
|
||
|
err error
|
||
|
count uint64
|
||
|
}
|
||
|
|
||
|
// Error reports an error. Errors get aggregated and logged periodically. The
|
||
|
// default is once per minute or once every DD_LOGGING_RATE number of seconds.
|
||
|
func Error(format string, a ...interface{}) {
|
||
|
key := format // format should 99.9% of the time be constant
|
||
|
if reachedLimit(key) {
|
||
|
// avoid too much lock contention on spammy errors
|
||
|
return
|
||
|
}
|
||
|
errmu.Lock()
|
||
|
defer errmu.Unlock()
|
||
|
report, ok := erragg[key]
|
||
|
if !ok {
|
||
|
erragg[key] = &errorReport{
|
||
|
err: fmt.Errorf(format, a...),
|
||
|
first: time.Now(),
|
||
|
}
|
||
|
report = erragg[key]
|
||
|
}
|
||
|
report.count++
|
||
|
if errrate == 0 {
|
||
|
flushLocked()
|
||
|
return
|
||
|
}
|
||
|
if !erron {
|
||
|
erron = true
|
||
|
time.AfterFunc(errrate, Flush)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// defaultErrorLimit specifies the maximum number of errors gathered in a report.
|
||
|
const defaultErrorLimit = 200
|
||
|
|
||
|
// reachedLimit reports whether the maximum count has been reached for this key.
|
||
|
func reachedLimit(key string) bool {
|
||
|
errmu.RLock()
|
||
|
e, ok := erragg[key]
|
||
|
confirm := ok && e.count > defaultErrorLimit
|
||
|
errmu.RUnlock()
|
||
|
return confirm
|
||
|
}
|
||
|
|
||
|
// Flush flushes and resets all aggregated errors to the logger.
|
||
|
func Flush() {
|
||
|
errmu.Lock()
|
||
|
defer errmu.Unlock()
|
||
|
flushLocked()
|
||
|
}
|
||
|
|
||
|
func flushLocked() {
|
||
|
for _, report := range erragg {
|
||
|
msg := fmt.Sprintf("%v", report.err)
|
||
|
if report.count > defaultErrorLimit {
|
||
|
msg += fmt.Sprintf(", %d+ additional messages skipped (first occurrence: %s)", defaultErrorLimit, report.first.Format(time.RFC822))
|
||
|
} else if report.count > 1 {
|
||
|
msg += fmt.Sprintf(", %d additional messages skipped (first occurrence: %s)", report.count-1, report.first.Format(time.RFC822))
|
||
|
} else {
|
||
|
msg += fmt.Sprintf(" (occurred: %s)", report.first.Format(time.RFC822))
|
||
|
}
|
||
|
printMsg("ERROR", msg)
|
||
|
}
|
||
|
for k := range erragg {
|
||
|
// compiler-optimized map-clearing post go1.11 (golang/go#20138)
|
||
|
delete(erragg, k)
|
||
|
}
|
||
|
erron = false
|
||
|
}
|
||
|
|
||
|
func printMsg(lvl, format string, a ...interface{}) {
|
||
|
msg := fmt.Sprintf("%s %s: %s", prefixMsg, lvl, fmt.Sprintf(format, a...))
|
||
|
mu.RLock()
|
||
|
logger.Log(msg)
|
||
|
mu.RUnlock()
|
||
|
}
|
||
|
|
||
|
type defaultLogger struct{ l *log.Logger }
|
||
|
|
||
|
func (p *defaultLogger) Log(msg string) { p.l.Print(msg) }
|