From 47dd1cb7ae8591b4c63b68371cc1922c0aa5f98e Mon Sep 17 00:00:00 2001
From: zeripath <art27@cantab.net>
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 <techknowlogick@gitea.io>
---
 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 1cfddd90a1..942dbc744a 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 08b20b80a0..0f61c4e592 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 1f9c59965c..4cdeff53dc 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 d8e5776735..5d56fd7390 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 4b89385fe2..ab231bd1bd 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 2a35b5752c..16a6efb75b 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 0e7583081c..810505dea5 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 9704ffd3d8..75f361ccdb 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 0000000000..d28071c3f7
--- /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
+}