150 lines
3 KiB
Go
150 lines
3 KiB
Go
package util
|
|
|
|
import (
|
|
"io"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
|
|
jww "github.com/spf13/jwalterweatherman"
|
|
)
|
|
|
|
var (
|
|
loggers = map[string]*Logger{}
|
|
levels = map[string]jww.Threshold{}
|
|
|
|
loggersMux sync.Mutex
|
|
|
|
// OutThreshold is the default console log level
|
|
OutThreshold = jww.LevelError
|
|
|
|
// LogThreshold is the default log file level
|
|
LogThreshold = jww.LevelWarn
|
|
)
|
|
|
|
const padding = 6 // padding of log areas
|
|
|
|
// Logger wraps a jww notepad to avoid leaking implementation detail
|
|
type Logger struct {
|
|
*jww.Notepad
|
|
}
|
|
|
|
// NewLogger creates a logger with the given log area and adds it to the registry
|
|
func NewLogger(area string) *Logger {
|
|
padded := area
|
|
for len(padded) < padding {
|
|
padded = padded + " "
|
|
}
|
|
|
|
level := LogLevelForArea(area)
|
|
notepad := jww.NewNotepad(level, level, os.Stdout, ioutil.Discard, padded, log.Ldate|log.Ltime)
|
|
|
|
loggersMux.Lock()
|
|
defer loggersMux.Unlock()
|
|
|
|
logger := &Logger{notepad}
|
|
loggers[area] = logger
|
|
return logger
|
|
}
|
|
|
|
// Loggers invokes callback for each configured logger
|
|
func Loggers(cb func(string, *Logger)) {
|
|
for name, logger := range loggers {
|
|
cb(name, logger)
|
|
}
|
|
}
|
|
|
|
// LogLevelForArea gets the log level for given log area
|
|
func LogLevelForArea(area string) jww.Threshold {
|
|
level, ok := levels[strings.ToLower(area)]
|
|
if !ok {
|
|
level = OutThreshold
|
|
}
|
|
return level
|
|
}
|
|
|
|
// LogLevel sets log level for all loggers
|
|
func LogLevel(defaultLevel string, areaLevels map[string]string) {
|
|
// default level
|
|
OutThreshold = LogLevelToThreshold(defaultLevel)
|
|
LogThreshold = OutThreshold
|
|
|
|
// area levels
|
|
for area, level := range areaLevels {
|
|
area = strings.ToLower(area)
|
|
levels[area] = LogLevelToThreshold(level)
|
|
}
|
|
|
|
Loggers(func(name string, logger *Logger) {
|
|
logger.SetStdoutThreshold(LogLevelForArea(name))
|
|
})
|
|
}
|
|
|
|
// LogLevelToThreshold converts log level string to a jww Threshold
|
|
func LogLevelToThreshold(level string) jww.Threshold {
|
|
switch strings.ToUpper(level) {
|
|
case "FATAL":
|
|
return jww.LevelFatal
|
|
case "ERROR":
|
|
return jww.LevelError
|
|
case "WARN":
|
|
return jww.LevelWarn
|
|
case "INFO":
|
|
return jww.LevelInfo
|
|
case "DEBUG":
|
|
return jww.LevelDebug
|
|
case "TRACE":
|
|
return jww.LevelTrace
|
|
default:
|
|
panic("invalid log level " + level)
|
|
}
|
|
}
|
|
|
|
var uiChan chan<- Param
|
|
|
|
type uiWriter struct {
|
|
re *regexp.Regexp
|
|
level string
|
|
}
|
|
|
|
func (w *uiWriter) Write(p []byte) (n int, err error) {
|
|
// trim level and timestamp
|
|
s := string(w.re.ReplaceAll(p, []byte{}))
|
|
|
|
uiChan <- Param{
|
|
Key: w.level,
|
|
Val: strings.Trim(strconv.Quote(s), "\"\n"),
|
|
}
|
|
|
|
return 0, nil
|
|
}
|
|
|
|
// CaptureLogs appends uiWriter to relevant log levels
|
|
func CaptureLogs(c chan<- Param) {
|
|
uiChan = c
|
|
|
|
for _, l := range loggers {
|
|
captureLogger("warn", l.Notepad.WARN)
|
|
captureLogger("error", l.Notepad.ERROR)
|
|
captureLogger("error", l.Notepad.FATAL)
|
|
}
|
|
}
|
|
|
|
func captureLogger(level string, l *log.Logger) {
|
|
re, err := regexp.Compile(`^\[\w+\] \w+ .{19} `)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
ui := uiWriter{
|
|
re: re,
|
|
level: level,
|
|
}
|
|
|
|
mw := io.MultiWriter(l.Writer(), &ui)
|
|
l.SetOutput(mw)
|
|
}
|