gecko/utils/logging/log.go

303 lines
6.2 KiB
Go

// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package logging
import (
"bufio"
"fmt"
"os"
"path"
"runtime"
"strings"
"sync"
"time"
)
// Log ...
type Log struct {
config Config
messages []string
size int
wg sync.WaitGroup
flushLock, writeLock, configLock sync.Mutex
needsFlush *sync.Cond
w *bufio.Writer
closed bool
}
// New ...
func New(config Config) (*Log, error) {
if err := os.MkdirAll(config.Directory, os.ModePerm); err != nil {
return nil, err
}
l := &Log{config: config}
l.needsFlush = sync.NewCond(&l.flushLock)
l.wg.Add(1)
go l.RecoverAndPanic(l.run)
return l, nil
}
func (l *Log) run() {
defer l.wg.Done()
l.writeLock.Lock()
defer l.writeLock.Unlock()
fileIndex := 0
filename := path.Join(l.config.Directory, fmt.Sprintf("%d.log", fileIndex))
f, err := os.Create(filename)
if err != nil {
panic(err)
}
l.w = bufio.NewWriter(f)
closed := false
nextRotation := time.Now().Add(l.config.RotationInterval)
currentSize := 0
for !closed {
l.writeLock.Unlock()
l.flushLock.Lock()
for l.size < l.config.FlushSize && !l.closed {
l.needsFlush.Wait()
}
closed = l.closed
prevMessages := l.messages
l.messages = nil
l.size = 0
l.flushLock.Unlock()
l.writeLock.Lock()
for _, msg := range prevMessages {
n, _ := l.w.WriteString(msg)
currentSize += n
}
if !l.config.DisableFlushOnWrite {
l.w.Flush()
}
if now := time.Now(); nextRotation.Before(now) || currentSize > l.config.FileSize {
nextRotation = now.Add(l.config.RotationInterval)
currentSize = 0
l.w.Flush()
f.Close()
fileIndex = (fileIndex + 1) % l.config.RotationSize
filename := path.Join(l.config.Directory, fmt.Sprintf("%d.log", fileIndex))
f, err = os.Create(filename)
if err != nil {
panic(err)
}
l.w = bufio.NewWriter(f)
}
}
l.w.Flush()
f.Close()
}
func (l *Log) Write(p []byte) (int, error) {
l.writeLock.Lock()
defer l.writeLock.Unlock()
return l.w.Write(p)
}
// Stop ...
func (l *Log) Stop() {
l.flushLock.Lock()
l.closed = true
l.needsFlush.Signal()
l.flushLock.Unlock()
l.wg.Wait()
}
// Should only be called from [Level] functions.
func (l *Log) log(level Level, format string, args ...interface{}) {
if l == nil {
return
}
l.configLock.Lock()
defer l.configLock.Unlock()
shouldLog := !l.config.DisableLogging && level <= l.config.LogLevel
shouldDisplay := (!l.config.DisableDisplaying && level <= l.config.DisplayLevel) || level == Fatal
if !shouldLog && !shouldDisplay {
return
}
output := l.format(level, format, args...)
if shouldLog {
l.flushLock.Lock()
l.messages = append(l.messages, output)
l.size += len(output)
l.needsFlush.Signal()
l.flushLock.Unlock()
}
if shouldDisplay {
if l.config.DisableContextualDisplaying {
fmt.Println(fmt.Sprintf(format, args...))
} else {
fmt.Print(level.Color().Wrap(output))
}
}
}
func (l *Log) format(level Level, format string, args ...interface{}) string {
loc := "?"
if _, file, no, ok := runtime.Caller(3); ok {
loc = fmt.Sprintf("%s#%d", file, no)
}
if i := strings.Index(loc, "gecko/"); i != -1 {
loc = loc[i+5:]
}
text := fmt.Sprintf("%s: %s", loc, fmt.Sprintf(format, args...))
prefix := ""
if l.config.MsgPrefix != "" {
prefix = fmt.Sprintf(" <%s>", l.config.MsgPrefix)
}
return fmt.Sprintf("%s[%s]%s %s\n",
level,
time.Now().Format("01-02|15:04:05"),
prefix,
text)
}
// Fatal ...
func (l *Log) Fatal(format string, args ...interface{}) { l.log(Fatal, format, args...) }
// Error ...
func (l *Log) Error(format string, args ...interface{}) { l.log(Error, format, args...) }
// Warn ...
func (l *Log) Warn(format string, args ...interface{}) { l.log(Warn, format, args...) }
// Info ...
func (l *Log) Info(format string, args ...interface{}) { l.log(Info, format, args...) }
// Debug ...
func (l *Log) Debug(format string, args ...interface{}) { l.log(Debug, format, args...) }
// Verbo ...
func (l *Log) Verbo(format string, args ...interface{}) { l.log(Verbo, format, args...) }
// AssertNoError ...
func (l *Log) AssertNoError(err error) {
if err != nil {
l.log(Fatal, "%s", err)
}
if l.config.Assertions && err != nil {
l.Stop()
panic(err)
}
}
// AssertTrue ...
func (l *Log) AssertTrue(b bool, format string, args ...interface{}) {
if !b {
l.log(Fatal, format, args...)
}
if l.config.Assertions && !b {
l.Stop()
panic(fmt.Sprintf(format, args...))
}
}
// AssertDeferredTrue ...
func (l *Log) AssertDeferredTrue(f func() bool, format string, args ...interface{}) {
// Note, the logger will only be notified here if assertions are enabled
if l.config.Assertions && !f() {
err := fmt.Sprintf(format, args...)
l.log(Fatal, err)
l.Stop()
panic(err)
}
}
// AssertDeferredNoError ...
func (l *Log) AssertDeferredNoError(f func() error) {
if l.config.Assertions {
err := f()
if err != nil {
l.log(Fatal, "%s", err)
}
if l.config.Assertions && err != nil {
l.Stop()
panic(err)
}
}
}
// StopOnPanic ...
func (l *Log) StopOnPanic() {
if r := recover(); r != nil {
l.Fatal("Panicing due to:\n%s\nFrom:\n%s", r, Stacktrace{})
l.Stop()
panic(r)
}
}
// RecoverAndPanic ...
func (l *Log) RecoverAndPanic(f func()) { defer l.StopOnPanic(); f() }
// SetLogLevel ...
func (l *Log) SetLogLevel(lvl Level) {
l.configLock.Lock()
defer l.configLock.Unlock()
l.config.LogLevel = lvl
}
// SetDisplayLevel ...
func (l *Log) SetDisplayLevel(lvl Level) {
l.configLock.Lock()
defer l.configLock.Unlock()
l.config.DisplayLevel = lvl
}
// SetPrefix ...
func (l *Log) SetPrefix(prefix string) {
l.configLock.Lock()
defer l.configLock.Unlock()
l.config.MsgPrefix = prefix
}
// SetLoggingEnabled ...
func (l *Log) SetLoggingEnabled(enabled bool) {
l.configLock.Lock()
defer l.configLock.Unlock()
l.config.DisableLogging = !enabled
}
// SetDisplayingEnabled ...
func (l *Log) SetDisplayingEnabled(enabled bool) {
l.configLock.Lock()
defer l.configLock.Unlock()
l.config.DisableDisplaying = !enabled
}
// SetContextualDisplayingEnabled ...
func (l *Log) SetContextualDisplayingEnabled(enabled bool) {
l.configLock.Lock()
defer l.configLock.Unlock()
l.config.DisableContextualDisplaying = !enabled
}