mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-03-30 15:08:33 +00:00

* Remove Subsystems map and replace with RegisterSubSystem * Clean up the logger * Fix LOGFLAGS and make LongFile work correctly * Parallelize the logger backend * More logger cleanup * Initialize and close the logger backend wherever it's needed * Move the location where the backend is closed, also print the log if it panics while writing * Add TestMain to reachability manager tests to preserve the same log level * Fix review comments Co-authored-by: Svarog <feanorr@gmail.com>
192 lines
5.7 KiB
Go
192 lines
5.7 KiB
Go
package logger
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/jrick/logrotate/rotator"
|
|
"github.com/pkg/errors"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime/debug"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
)
|
|
|
|
const normalLogSize = 512
|
|
|
|
// defaultFlags specifies changes to the default logger behavior. It is set
|
|
// during package init and configured using the LOGFLAGS environment variable.
|
|
// New logger backends can override these default flags using WithFlags.
|
|
// We're using this instead of `init()` function because variables are initialized before init functions,
|
|
// and this variable is used inside other variable intializations, so runs before them
|
|
var defaultFlags = getDefaultFlags()
|
|
|
|
// Flags to modify Backend's behavior.
|
|
const (
|
|
// LogFlagLongFile modifies the logger output to include full path and line number
|
|
// of the logging callsite, e.g. /a/b/c/main.go:123.
|
|
LogFlagLongFile uint32 = 1 << iota
|
|
|
|
// LogFlagShortFile modifies the logger output to include filename and line number
|
|
// of the logging callsite, e.g. main.go:123. takes precedence over LogFlagLongFile.
|
|
LogFlagShortFile
|
|
)
|
|
|
|
// Read logger flags from the LOGFLAGS environment variable. Multiple flags can
|
|
// be set at once, separated by commas.
|
|
func getDefaultFlags() (flags uint32) {
|
|
for _, f := range strings.Split(os.Getenv("LOGFLAGS"), ",") {
|
|
switch f {
|
|
case "longfile":
|
|
flags |= LogFlagLongFile
|
|
case "shortfile":
|
|
flags |= LogFlagShortFile
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
const logsBuffer = 0
|
|
|
|
// Backend is a logging backend. Subsystems created from the backend write to
|
|
// the backend's Writer. Backend provides atomic writes to the Writer from all
|
|
// subsystems.
|
|
type Backend struct {
|
|
flag uint32
|
|
isRunning uint32
|
|
writers []logWriter
|
|
writeChan chan logEntry
|
|
syncClose sync.Mutex // used to sync that the logger finished writing everything
|
|
}
|
|
|
|
// NewBackendWithFlags configures a Backend to use the specified flags rather than using
|
|
// the package's defaults as determined through the LOGFLAGS environment
|
|
// variable.
|
|
func NewBackendWithFlags(flags uint32) *Backend {
|
|
return &Backend{flag: flags, writeChan: make(chan logEntry, logsBuffer)}
|
|
}
|
|
|
|
// NewBackend creates a new logger backend.
|
|
func NewBackend() *Backend {
|
|
return NewBackendWithFlags(defaultFlags)
|
|
}
|
|
|
|
const (
|
|
defaultThresholdKB = 100 * 1000 // 100 MB logs by default.
|
|
defaultMaxRolls = 8 // keep 8 last logs by default.
|
|
)
|
|
|
|
type logWriter interface {
|
|
io.WriteCloser
|
|
LogLevel() Level
|
|
}
|
|
|
|
type logWriterWrap struct {
|
|
io.WriteCloser
|
|
logLevel Level
|
|
}
|
|
|
|
func (lw logWriterWrap) LogLevel() Level {
|
|
return lw.logLevel
|
|
}
|
|
|
|
// AddLogFile adds a file which the log will write into on a certain
|
|
// log level with the default log rotation settings. It'll create the file if it doesn't exist.
|
|
func (b *Backend) AddLogFile(logFile string, logLevel Level) error {
|
|
return b.AddLogFileWithCustomRotator(logFile, logLevel, defaultThresholdKB, defaultMaxRolls)
|
|
}
|
|
|
|
// AddLogWriter adds a type implementing io.WriteCloser which the log will write into on a certain
|
|
// log level with the default log rotation settings. It'll create the file if it doesn't exist.
|
|
func (b *Backend) AddLogWriter(logWriter io.WriteCloser, logLevel Level) error {
|
|
if b.IsRunning() {
|
|
return errors.New("The logger is already running")
|
|
}
|
|
b.writers = append(b.writers, logWriterWrap{
|
|
WriteCloser: logWriter,
|
|
logLevel: logLevel,
|
|
})
|
|
return nil
|
|
}
|
|
|
|
// AddLogFileWithCustomRotator adds a file which the log will write into on a certain
|
|
// log level, with the specified log rotation settings.
|
|
// It'll create the file if it doesn't exist.
|
|
func (b *Backend) AddLogFileWithCustomRotator(logFile string, logLevel Level, thresholdKB int64, maxRolls int) error {
|
|
if b.IsRunning() {
|
|
return errors.New("The logger is already running")
|
|
}
|
|
logDir, _ := filepath.Split(logFile)
|
|
// if the logDir is empty then `logFile` is in the cwd and there's no need to create any directory.
|
|
if logDir != "" {
|
|
err := os.MkdirAll(logDir, 0700)
|
|
if err != nil {
|
|
return errors.Errorf("failed to create log directory: %+v", err)
|
|
}
|
|
}
|
|
r, err := rotator.New(logFile, thresholdKB, false, maxRolls)
|
|
if err != nil {
|
|
return errors.Errorf("failed to create file rotator: %s", err)
|
|
}
|
|
b.writers = append(b.writers, logWriterWrap{
|
|
WriteCloser: r,
|
|
logLevel: logLevel,
|
|
})
|
|
return nil
|
|
}
|
|
|
|
// Run launches the logger backend in a separate go-routine. should only be called once.
|
|
func (b *Backend) Run() error {
|
|
if !atomic.CompareAndSwapUint32(&b.isRunning, 0, 1) {
|
|
return errors.New("The logger is already running")
|
|
}
|
|
go func() {
|
|
defer func() {
|
|
if err := recover(); err != nil {
|
|
_, _ = fmt.Fprintf(os.Stderr, "Fatal error in logger.Backend goroutine: %+v\n", err)
|
|
_, _ = fmt.Fprintf(os.Stderr, "Goroutine stacktrace: %s\n", debug.Stack())
|
|
}
|
|
}()
|
|
b.runBlocking()
|
|
}()
|
|
return nil
|
|
}
|
|
|
|
func (b *Backend) runBlocking() {
|
|
defer atomic.StoreUint32(&b.isRunning, 0)
|
|
b.syncClose.Lock()
|
|
defer b.syncClose.Unlock()
|
|
|
|
for log := range b.writeChan {
|
|
for _, writer := range b.writers {
|
|
if log.level >= writer.LogLevel() {
|
|
_, _ = writer.Write(log.log)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// IsRunning returns true if backend.Run() has been called and false if it hasn't.
|
|
func (b *Backend) IsRunning() bool {
|
|
return atomic.LoadUint32(&b.isRunning) != 0
|
|
}
|
|
|
|
// Close finalizes all log rotators for this backend
|
|
func (b *Backend) Close() {
|
|
close(b.writeChan)
|
|
// Wait for it to finish writing using the syncClose mutex.
|
|
b.syncClose.Lock()
|
|
defer b.syncClose.Unlock()
|
|
for _, writer := range b.writers {
|
|
_ = writer.Close()
|
|
}
|
|
}
|
|
|
|
// Logger returns a new logger for a particular subsystem that writes to the
|
|
// Backend b. A tag describes the subsystem and is included in all log
|
|
// messages. The logger uses the info verbosity level by default.
|
|
func (b *Backend) Logger(subsystemTag string) *Logger {
|
|
return &Logger{LevelOff, subsystemTag, b, b.writeChan}
|
|
}
|