From 47dd1cb7ae8591b4c63b68371cc1922c0aa5f98e Mon Sep 17 00:00:00 2001 From: zeripath Date: Sat, 31 Oct 2020 05:36:46 +0000 Subject: [PATCH] Refactor Logger (#13294) Refactor Logger to make a logger interface and make it possible to wrap loggers for specific purposes. Co-authored-by: techknowlogick --- models/log.go | 2 +- modules/indexer/code/elastic_search.go | 2 +- modules/indexer/issues/elastic_search.go | 4 +- modules/log/colors.go | 18 +-- modules/log/level.go | 10 ++ modules/log/log.go | 22 +-- modules/log/log_test.go | 2 +- modules/log/logger.go | 173 +++++++++++------------ modules/log/multichannel.go | 98 +++++++++++++ 9 files changed, 215 insertions(+), 116 deletions(-) create mode 100644 modules/log/multichannel.go diff --git a/models/log.go b/models/log.go index 1cfddd90a..942dbc744 100644 --- a/models/log.go +++ b/models/log.go @@ -15,7 +15,7 @@ import ( // XORMLogBridge a logger bridge from Logger to xorm type XORMLogBridge struct { showSQL bool - logger *log.Logger + logger log.Logger } // NewXORMLogger inits a log bridge for xorm diff --git a/modules/indexer/code/elastic_search.go b/modules/indexer/code/elastic_search.go index 08b20b80a..0f61c4e59 100644 --- a/modules/indexer/code/elastic_search.go +++ b/modules/indexer/code/elastic_search.go @@ -40,7 +40,7 @@ type ElasticSearchIndexer struct { } type elasticLogger struct { - *log.Logger + log.Logger } func (l elasticLogger) Printf(format string, args ...interface{}) { diff --git a/modules/indexer/issues/elastic_search.go b/modules/indexer/issues/elastic_search.go index 1f9c59965..4cdeff53d 100644 --- a/modules/indexer/issues/elastic_search.go +++ b/modules/indexer/issues/elastic_search.go @@ -27,11 +27,11 @@ type ElasticSearchIndexer struct { } type elasticLogger struct { - *log.Logger + log.LevelLogger } func (l elasticLogger) Printf(format string, args ...interface{}) { - _ = l.Logger.Log(2, l.Logger.GetLevel(), format, args...) + _ = l.Log(2, l.GetLevel(), format, args...) } // NewElasticSearchIndexer creates a new elasticsearch indexer diff --git a/modules/log/colors.go b/modules/log/colors.go index d8e577673..5d56fd739 100644 --- a/modules/log/colors.go +++ b/modules/log/colors.go @@ -158,15 +158,15 @@ func ColorBytes(attrs ...ColorAttribute) []byte { return bytes } -var levelToColor = map[Level]string{ - TRACE: ColorString(Bold, FgCyan), - DEBUG: ColorString(Bold, FgBlue), - INFO: ColorString(Bold, FgGreen), - WARN: ColorString(Bold, FgYellow), - ERROR: ColorString(Bold, FgRed), - CRITICAL: ColorString(Bold, BgMagenta), - FATAL: ColorString(Bold, BgRed), - NONE: ColorString(Reset), +var levelToColor = map[Level][]byte{ + TRACE: ColorBytes(Bold, FgCyan), + DEBUG: ColorBytes(Bold, FgBlue), + INFO: ColorBytes(Bold, FgGreen), + WARN: ColorBytes(Bold, FgYellow), + ERROR: ColorBytes(Bold, FgRed), + CRITICAL: ColorBytes(Bold, BgMagenta), + FATAL: ColorBytes(Bold, BgRed), + NONE: ColorBytes(Reset), } var resetBytes = ColorBytes(Reset) diff --git a/modules/log/level.go b/modules/log/level.go index 4b89385fe..ab231bd1b 100644 --- a/modules/log/level.go +++ b/modules/log/level.go @@ -73,6 +73,16 @@ func (l Level) String() string { return "info" } +// Color returns the color string for this Level +func (l Level) Color() *[]byte { + color, ok := levelToColor[l] + if ok { + return &(color) + } + none := levelToColor[NONE] + return &none +} + // MarshalJSON takes a Level and turns it into text func (l Level) MarshalJSON() ([]byte, error) { buffer := bytes.NewBufferString(`"`) diff --git a/modules/log/log.go b/modules/log/log.go index 2a35b5752..16a6efb75 100644 --- a/modules/log/log.go +++ b/modules/log/log.go @@ -16,16 +16,16 @@ type loggerMap struct { sync.Map } -func (m *loggerMap) Load(k string) (*Logger, bool) { +func (m *loggerMap) Load(k string) (*MultiChannelledLogger, bool) { v, ok := m.Map.Load(k) if !ok { return nil, false } - l, ok := v.(*Logger) + l, ok := v.(*MultiChannelledLogger) return l, ok } -func (m *loggerMap) Store(k string, v *Logger) { +func (m *loggerMap) Store(k string, v *MultiChannelledLogger) { m.Map.Store(k, v) } @@ -42,7 +42,7 @@ var ( ) // NewLogger create a logger for the default logger -func NewLogger(bufLen int64, name, provider, config string) *Logger { +func NewLogger(bufLen int64, name, provider, config string) *MultiChannelledLogger { err := NewNamedLogger(DEFAULT, bufLen, name, provider, config) if err != nil { CriticalWithSkip(1, "Unable to create default logger: %v", err) @@ -83,7 +83,7 @@ func DelLogger(name string) error { } // GetLogger returns either a named logger or the default logger -func GetLogger(name string) *Logger { +func GetLogger(name string) *MultiChannelledLogger { logger, ok := NamedLoggers.Load(name) if ok { return logger @@ -196,7 +196,7 @@ func IsFatal() bool { // Pause pauses all the loggers func Pause() { NamedLoggers.Range(func(key, value interface{}) bool { - logger := value.(*Logger) + logger := value.(*MultiChannelledLogger) logger.Pause() logger.Flush() return true @@ -206,7 +206,7 @@ func Pause() { // Resume resumes all the loggers func Resume() { NamedLoggers.Range(func(key, value interface{}) bool { - logger := value.(*Logger) + logger := value.(*MultiChannelledLogger) logger.Resume() return true }) @@ -216,7 +216,7 @@ func Resume() { func ReleaseReopen() error { var accumulatedErr error NamedLoggers.Range(func(key, value interface{}) bool { - logger := value.(*Logger) + logger := value.(*MultiChannelledLogger) if err := logger.ReleaseReopen(); err != nil { if accumulatedErr == nil { accumulatedErr = fmt.Errorf("Error reopening %s: %v", key.(string), err) @@ -250,15 +250,15 @@ func Log(skip int, level Level, format string, v ...interface{}) { // LoggerAsWriter is a io.Writer shim around the gitea log type LoggerAsWriter struct { - ourLoggers []*Logger + ourLoggers []*MultiChannelledLogger level Level } // NewLoggerAsWriter creates a Writer representation of the logger with setable log level -func NewLoggerAsWriter(level string, ourLoggers ...*Logger) *LoggerAsWriter { +func NewLoggerAsWriter(level string, ourLoggers ...*MultiChannelledLogger) *LoggerAsWriter { if len(ourLoggers) == 0 { l, _ := NamedLoggers.Load(DEFAULT) - ourLoggers = []*Logger{l} + ourLoggers = []*MultiChannelledLogger{l} } l := &LoggerAsWriter{ ourLoggers: ourLoggers, diff --git a/modules/log/log_test.go b/modules/log/log_test.go index 0e7583081..810505dea 100644 --- a/modules/log/log_test.go +++ b/modules/log/log_test.go @@ -11,7 +11,7 @@ import ( "github.com/stretchr/testify/assert" ) -func baseConsoleTest(t *testing.T, logger *Logger) (chan []byte, chan bool) { +func baseConsoleTest(t *testing.T, logger *MultiChannelledLogger) (chan []byte, chan bool) { written := make(chan []byte) closed := make(chan bool) diff --git a/modules/log/logger.go b/modules/log/logger.go index 9704ffd3d..75f361ccd 100644 --- a/modules/log/logger.go +++ b/modules/log/logger.go @@ -4,149 +4,140 @@ package log -import ( - "fmt" - "os" - "runtime" - "strings" - "time" -) +import "os" -// Logger is default logger in the Gitea application. -// it can contain several providers and log message into all providers. -type Logger struct { - *MultiChannelledLog - bufferLength int64 +// Logger is the basic interface for logging +type Logger interface { + LevelLogger + Trace(format string, v ...interface{}) + IsTrace() bool + Debug(format string, v ...interface{}) + IsDebug() bool + Info(format string, v ...interface{}) + IsInfo() bool + Warn(format string, v ...interface{}) + IsWarn() bool + Error(format string, v ...interface{}) + ErrorWithSkip(skip int, format string, v ...interface{}) + IsError() bool + Critical(format string, v ...interface{}) + CriticalWithSkip(skip int, format string, v ...interface{}) + IsCritical() bool + Fatal(format string, v ...interface{}) + FatalWithSkip(skip int, format string, v ...interface{}) + IsFatal() bool } -// newLogger initializes and returns a new logger. -func newLogger(name string, buffer int64) *Logger { - l := &Logger{ - MultiChannelledLog: NewMultiChannelledLog(name, buffer), - bufferLength: buffer, - } - return l +// LevelLogger is the simplest logging interface +type LevelLogger interface { + Flush() + Close() + GetLevel() Level + Log(skip int, level Level, format string, v ...interface{}) error } -// SetLogger sets new logger instance with given logger provider and config. -func (l *Logger) SetLogger(name, provider, config string) error { - eventLogger, err := NewChannelledLog(name, provider, config, l.bufferLength) - if err != nil { - return fmt.Errorf("Failed to create sublogger (%s): %v", name, err) - } - - l.MultiChannelledLog.DelLogger(name) - - err = l.MultiChannelledLog.AddLogger(eventLogger) - if err != nil { - if IsErrDuplicateName(err) { - return fmt.Errorf("Duplicate named sublogger %s %v", name, l.MultiChannelledLog.GetEventLoggerNames()) - } - return fmt.Errorf("Failed to add sublogger (%s): %v", name, err) - } - - return nil +// SettableLogger is the interface of loggers which have subloggers +type SettableLogger interface { + SetLogger(name, provider, config string) error + DelLogger(name string) (bool, error) } -// DelLogger deletes a sublogger from this logger. -func (l *Logger) DelLogger(name string) (bool, error) { - return l.MultiChannelledLog.DelLogger(name), nil +// StacktraceLogger is a logger that can log stacktraces +type StacktraceLogger interface { + GetStacktraceLevel() Level } -// Log msg at the provided level with the provided caller defined by skip (0 being the function that calls this function) -func (l *Logger) Log(skip int, level Level, format string, v ...interface{}) error { - if l.GetLevel() > level { - return nil - } - caller := "?()" - pc, filename, line, ok := runtime.Caller(skip + 1) - if ok { - // Get caller function name. - fn := runtime.FuncForPC(pc) - if fn != nil { - caller = fn.Name() + "()" - } - } - msg := format - if len(v) > 0 { - msg = ColorSprintf(format, v...) - } - stack := "" - if l.GetStacktraceLevel() <= level { - stack = Stack(skip + 1) - } - return l.SendLog(level, caller, strings.TrimPrefix(filename, prefix), line, msg, stack) -} - -// SendLog sends a log event at the provided level with the information given -func (l *Logger) SendLog(level Level, caller, filename string, line int, msg string, stack string) error { - if l.GetLevel() > level { - return nil - } - event := &Event{ - level: level, - caller: caller, - filename: filename, - line: line, - msg: msg, - time: time.Now(), - stacktrace: stack, - } - l.LogEvent(event) - return nil +// LevelLoggerLogger wraps a LevelLogger as a Logger +type LevelLoggerLogger struct { + LevelLogger } // Trace records trace log -func (l *Logger) Trace(format string, v ...interface{}) { +func (l *LevelLoggerLogger) Trace(format string, v ...interface{}) { l.Log(1, TRACE, format, v...) } +// IsTrace returns true if the logger is TRACE +func (l *LevelLoggerLogger) IsTrace() bool { + return l.GetLevel() <= TRACE +} + // Debug records debug log -func (l *Logger) Debug(format string, v ...interface{}) { +func (l *LevelLoggerLogger) Debug(format string, v ...interface{}) { l.Log(1, DEBUG, format, v...) } +// IsDebug returns true if the logger is DEBUG +func (l *LevelLoggerLogger) IsDebug() bool { + return l.GetLevel() <= DEBUG +} + // Info records information log -func (l *Logger) Info(format string, v ...interface{}) { +func (l *LevelLoggerLogger) Info(format string, v ...interface{}) { l.Log(1, INFO, format, v...) } +// IsInfo returns true if the logger is INFO +func (l *LevelLoggerLogger) IsInfo() bool { + return l.GetLevel() <= INFO +} + // Warn records warning log -func (l *Logger) Warn(format string, v ...interface{}) { +func (l *LevelLoggerLogger) Warn(format string, v ...interface{}) { l.Log(1, WARN, format, v...) } +// IsWarn returns true if the logger is WARN +func (l *LevelLoggerLogger) IsWarn() bool { + return l.GetLevel() <= WARN +} + // Error records error log -func (l *Logger) Error(format string, v ...interface{}) { +func (l *LevelLoggerLogger) Error(format string, v ...interface{}) { l.Log(1, ERROR, format, v...) } // ErrorWithSkip records error log from "skip" calls back from this function -func (l *Logger) ErrorWithSkip(skip int, format string, v ...interface{}) { +func (l *LevelLoggerLogger) ErrorWithSkip(skip int, format string, v ...interface{}) { l.Log(skip+1, ERROR, format, v...) } +// IsError returns true if the logger is ERROR +func (l *LevelLoggerLogger) IsError() bool { + return l.GetLevel() <= ERROR +} + // Critical records critical log -func (l *Logger) Critical(format string, v ...interface{}) { +func (l *LevelLoggerLogger) Critical(format string, v ...interface{}) { l.Log(1, CRITICAL, format, v...) } // CriticalWithSkip records critical log from "skip" calls back from this function -func (l *Logger) CriticalWithSkip(skip int, format string, v ...interface{}) { +func (l *LevelLoggerLogger) CriticalWithSkip(skip int, format string, v ...interface{}) { l.Log(skip+1, CRITICAL, format, v...) } +// IsCritical returns true if the logger is CRITICAL +func (l *LevelLoggerLogger) IsCritical() bool { + return l.GetLevel() <= CRITICAL +} + // Fatal records fatal log and exit the process -func (l *Logger) Fatal(format string, v ...interface{}) { +func (l *LevelLoggerLogger) Fatal(format string, v ...interface{}) { l.Log(1, FATAL, format, v...) l.Close() os.Exit(1) } // FatalWithSkip records fatal log from "skip" calls back from this function and exits the process -func (l *Logger) FatalWithSkip(skip int, format string, v ...interface{}) { +func (l *LevelLoggerLogger) FatalWithSkip(skip int, format string, v ...interface{}) { l.Log(skip+1, FATAL, format, v...) l.Close() os.Exit(1) } + +// IsFatal returns true if the logger is FATAL +func (l *LevelLoggerLogger) IsFatal() bool { + return l.GetLevel() <= FATAL +} diff --git a/modules/log/multichannel.go b/modules/log/multichannel.go new file mode 100644 index 000000000..d28071c3f --- /dev/null +++ b/modules/log/multichannel.go @@ -0,0 +1,98 @@ +// Copyright 2020 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package log + +import ( + "fmt" + "runtime" + "strings" + "time" +) + +// MultiChannelledLogger is default logger in the Gitea application. +// it can contain several providers and log message into all providers. +type MultiChannelledLogger struct { + LevelLoggerLogger + *MultiChannelledLog + bufferLength int64 +} + +// newLogger initializes and returns a new logger. +func newLogger(name string, buffer int64) *MultiChannelledLogger { + l := &MultiChannelledLogger{ + MultiChannelledLog: NewMultiChannelledLog(name, buffer), + bufferLength: buffer, + } + l.LevelLogger = l + return l +} + +// SetLogger sets new logger instance with given logger provider and config. +func (l *MultiChannelledLogger) SetLogger(name, provider, config string) error { + eventLogger, err := NewChannelledLog(name, provider, config, l.bufferLength) + if err != nil { + return fmt.Errorf("Failed to create sublogger (%s): %v", name, err) + } + + l.MultiChannelledLog.DelLogger(name) + + err = l.MultiChannelledLog.AddLogger(eventLogger) + if err != nil { + if IsErrDuplicateName(err) { + return fmt.Errorf("Duplicate named sublogger %s %v", name, l.MultiChannelledLog.GetEventLoggerNames()) + } + return fmt.Errorf("Failed to add sublogger (%s): %v", name, err) + } + + return nil +} + +// DelLogger deletes a sublogger from this logger. +func (l *MultiChannelledLogger) DelLogger(name string) (bool, error) { + return l.MultiChannelledLog.DelLogger(name), nil +} + +// Log msg at the provided level with the provided caller defined by skip (0 being the function that calls this function) +func (l *MultiChannelledLogger) Log(skip int, level Level, format string, v ...interface{}) error { + if l.GetLevel() > level { + return nil + } + caller := "?()" + pc, filename, line, ok := runtime.Caller(skip + 1) + if ok { + // Get caller function name. + fn := runtime.FuncForPC(pc) + if fn != nil { + caller = fn.Name() + "()" + } + } + msg := format + if len(v) > 0 { + msg = ColorSprintf(format, v...) + } + stack := "" + if l.GetStacktraceLevel() <= level { + stack = Stack(skip + 1) + } + return l.SendLog(level, caller, strings.TrimPrefix(filename, prefix), line, msg, stack) +} + +// SendLog sends a log event at the provided level with the information given +func (l *MultiChannelledLogger) SendLog(level Level, caller, filename string, line int, msg string, stack string) error { + if l.GetLevel() > level { + return nil + } + event := &Event{ + level: level, + caller: caller, + filename: filename, + line: line, + msg: msg, + time: time.Now(), + stacktrace: stack, + } + l.LogEvent(event) + return nil +}