Refactor Logger to make a logger interface and make it possible to wrap loggers for specific purposes. Co-authored-by: techknowlogick <techknowlogick@gitea.io>tags/v1.15.0-dev
@@ -15,7 +15,7 @@ import ( | |||||
// XORMLogBridge a logger bridge from Logger to xorm | // XORMLogBridge a logger bridge from Logger to xorm | ||||
type XORMLogBridge struct { | type XORMLogBridge struct { | ||||
showSQL bool | showSQL bool | ||||
logger *log.Logger | |||||
logger log.Logger | |||||
} | } | ||||
// NewXORMLogger inits a log bridge for xorm | // NewXORMLogger inits a log bridge for xorm | ||||
@@ -40,7 +40,7 @@ type ElasticSearchIndexer struct { | |||||
} | } | ||||
type elasticLogger struct { | type elasticLogger struct { | ||||
*log.Logger | |||||
log.Logger | |||||
} | } | ||||
func (l elasticLogger) Printf(format string, args ...interface{}) { | func (l elasticLogger) Printf(format string, args ...interface{}) { | ||||
@@ -27,11 +27,11 @@ type ElasticSearchIndexer struct { | |||||
} | } | ||||
type elasticLogger struct { | type elasticLogger struct { | ||||
*log.Logger | |||||
log.LevelLogger | |||||
} | } | ||||
func (l elasticLogger) Printf(format string, args ...interface{}) { | 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 | // NewElasticSearchIndexer creates a new elasticsearch indexer | ||||
@@ -158,15 +158,15 @@ func ColorBytes(attrs ...ColorAttribute) []byte { | |||||
return bytes | 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) | var resetBytes = ColorBytes(Reset) | ||||
@@ -73,6 +73,16 @@ func (l Level) String() string { | |||||
return "info" | 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 | // MarshalJSON takes a Level and turns it into text | ||||
func (l Level) MarshalJSON() ([]byte, error) { | func (l Level) MarshalJSON() ([]byte, error) { | ||||
buffer := bytes.NewBufferString(`"`) | buffer := bytes.NewBufferString(`"`) | ||||
@@ -16,16 +16,16 @@ type loggerMap struct { | |||||
sync.Map | sync.Map | ||||
} | } | ||||
func (m *loggerMap) Load(k string) (*Logger, bool) { | |||||
func (m *loggerMap) Load(k string) (*MultiChannelledLogger, bool) { | |||||
v, ok := m.Map.Load(k) | v, ok := m.Map.Load(k) | ||||
if !ok { | if !ok { | ||||
return nil, false | return nil, false | ||||
} | } | ||||
l, ok := v.(*Logger) | |||||
l, ok := v.(*MultiChannelledLogger) | |||||
return l, ok | return l, ok | ||||
} | } | ||||
func (m *loggerMap) Store(k string, v *Logger) { | |||||
func (m *loggerMap) Store(k string, v *MultiChannelledLogger) { | |||||
m.Map.Store(k, v) | m.Map.Store(k, v) | ||||
} | } | ||||
@@ -42,7 +42,7 @@ var ( | |||||
) | ) | ||||
// NewLogger create a logger for the default logger | // 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) | err := NewNamedLogger(DEFAULT, bufLen, name, provider, config) | ||||
if err != nil { | if err != nil { | ||||
CriticalWithSkip(1, "Unable to create default logger: %v", err) | 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 | // 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) | logger, ok := NamedLoggers.Load(name) | ||||
if ok { | if ok { | ||||
return logger | return logger | ||||
@@ -196,7 +196,7 @@ func IsFatal() bool { | |||||
// Pause pauses all the loggers | // Pause pauses all the loggers | ||||
func Pause() { | func Pause() { | ||||
NamedLoggers.Range(func(key, value interface{}) bool { | NamedLoggers.Range(func(key, value interface{}) bool { | ||||
logger := value.(*Logger) | |||||
logger := value.(*MultiChannelledLogger) | |||||
logger.Pause() | logger.Pause() | ||||
logger.Flush() | logger.Flush() | ||||
return true | return true | ||||
@@ -206,7 +206,7 @@ func Pause() { | |||||
// Resume resumes all the loggers | // Resume resumes all the loggers | ||||
func Resume() { | func Resume() { | ||||
NamedLoggers.Range(func(key, value interface{}) bool { | NamedLoggers.Range(func(key, value interface{}) bool { | ||||
logger := value.(*Logger) | |||||
logger := value.(*MultiChannelledLogger) | |||||
logger.Resume() | logger.Resume() | ||||
return true | return true | ||||
}) | }) | ||||
@@ -216,7 +216,7 @@ func Resume() { | |||||
func ReleaseReopen() error { | func ReleaseReopen() error { | ||||
var accumulatedErr error | var accumulatedErr error | ||||
NamedLoggers.Range(func(key, value interface{}) bool { | NamedLoggers.Range(func(key, value interface{}) bool { | ||||
logger := value.(*Logger) | |||||
logger := value.(*MultiChannelledLogger) | |||||
if err := logger.ReleaseReopen(); err != nil { | if err := logger.ReleaseReopen(); err != nil { | ||||
if accumulatedErr == nil { | if accumulatedErr == nil { | ||||
accumulatedErr = fmt.Errorf("Error reopening %s: %v", key.(string), err) | 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 | // LoggerAsWriter is a io.Writer shim around the gitea log | ||||
type LoggerAsWriter struct { | type LoggerAsWriter struct { | ||||
ourLoggers []*Logger | |||||
ourLoggers []*MultiChannelledLogger | |||||
level Level | level Level | ||||
} | } | ||||
// NewLoggerAsWriter creates a Writer representation of the logger with setable log 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 { | if len(ourLoggers) == 0 { | ||||
l, _ := NamedLoggers.Load(DEFAULT) | l, _ := NamedLoggers.Load(DEFAULT) | ||||
ourLoggers = []*Logger{l} | |||||
ourLoggers = []*MultiChannelledLogger{l} | |||||
} | } | ||||
l := &LoggerAsWriter{ | l := &LoggerAsWriter{ | ||||
ourLoggers: ourLoggers, | ourLoggers: ourLoggers, | ||||
@@ -11,7 +11,7 @@ import ( | |||||
"github.com/stretchr/testify/assert" | "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) | written := make(chan []byte) | ||||
closed := make(chan bool) | closed := make(chan bool) | ||||
@@ -4,149 +4,140 @@ | |||||
package log | package log | ||||
import ( | |||||
"fmt" | |||||
"os" | |||||
"runtime" | |||||
"strings" | |||||
"time" | |||||
) | |||||
// 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 | |||||
} | |||||
// newLogger initializes and returns a new logger. | |||||
func newLogger(name string, buffer int64) *Logger { | |||||
l := &Logger{ | |||||
MultiChannelledLog: NewMultiChannelledLog(name, buffer), | |||||
bufferLength: buffer, | |||||
} | |||||
return l | |||||
} | |||||
// 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 | |||||
} | |||||
// DelLogger deletes a sublogger from this logger. | |||||
func (l *Logger) 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 *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 | |||||
import "os" | |||||
// 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 | |||||
} | |||||
// LevelLogger is the simplest logging interface | |||||
type LevelLogger interface { | |||||
Flush() | |||||
Close() | |||||
GetLevel() Level | |||||
Log(skip int, level Level, format string, v ...interface{}) error | |||||
} | |||||
// SettableLogger is the interface of loggers which have subloggers | |||||
type SettableLogger interface { | |||||
SetLogger(name, provider, config string) error | |||||
DelLogger(name string) (bool, error) | |||||
} | |||||
// StacktraceLogger is a logger that can log stacktraces | |||||
type StacktraceLogger interface { | |||||
GetStacktraceLevel() Level | |||||
} | |||||
// LevelLoggerLogger wraps a LevelLogger as a Logger | |||||
type LevelLoggerLogger struct { | |||||
LevelLogger | |||||
} | } | ||||
// Trace records trace log | // 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...) | 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 | // 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...) | 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 | // 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...) | 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 | // 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...) | 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 | // 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...) | l.Log(1, ERROR, format, v...) | ||||
} | } | ||||
// ErrorWithSkip records error log from "skip" calls back from this function | // 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...) | 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 | // 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...) | l.Log(1, CRITICAL, format, v...) | ||||
} | } | ||||
// CriticalWithSkip records critical log from "skip" calls back from this function | // 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...) | 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 | // 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.Log(1, FATAL, format, v...) | ||||
l.Close() | l.Close() | ||||
os.Exit(1) | os.Exit(1) | ||||
} | } | ||||
// FatalWithSkip records fatal log from "skip" calls back from this function and exits the process | // 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.Log(skip+1, FATAL, format, v...) | ||||
l.Close() | l.Close() | ||||
os.Exit(1) | os.Exit(1) | ||||
} | } | ||||
// IsFatal returns true if the logger is FATAL | |||||
func (l *LevelLoggerLogger) IsFatal() bool { | |||||
return l.GetLevel() <= FATAL | |||||
} |
@@ -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 | |||||
} |