Files
mochi/pkg/log/log.go
Lawrence, Rendall da7de52813 change httprouter with fasthttp and simple handler
* add http benchmark
* move HTTP query parameters parsing to http subpackage
* update dependencies
2023-03-18 00:58:35 +03:00

303 lines
8.1 KiB
Go

// Package log adds a thin wrapper around zerolog to improve logging performance.
//
// Root logger (called by log.Info, log.Warn etc.) uses global zerolog.Logger instance
// until ConfigureLogger called. Any child logger created with NewLogger will not
// produce any events until logger configured, so any function which uses child
// logger will come stuck because of root initialization synchronization.
package log
import (
"io"
"os"
"strings"
"sync"
// needs for async file logging
_ "code.cloudfoundry.org/go-diodes"
"github.com/rs/zerolog"
"github.com/rs/zerolog/diode"
zl "github.com/rs/zerolog/log"
)
var (
root = zl.Logger
rootMu = sync.Mutex{}
customOut io.WriteCloser
customOutMu = sync.Mutex{}
)
// ConfigureLogger initializes root and all child loggers.
// NOTE: this function MUST be called before any child log call
//
// otherwise any goroutine, which uses logger will wait logger initialization
func ConfigureLogger(output, level string, formatted, colored bool) (err error) {
lvl := zerolog.WarnLevel
output = strings.ToLower(output)
var w io.Writer
var stdAny bool
switch output {
case "stderr", "":
w, stdAny = os.Stderr, true
case "stdout":
w, stdAny = os.Stdout, true
default:
if w, err = os.OpenFile(output, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0o600); err == nil {
customOutMu.Lock()
defer customOutMu.Unlock()
customOut = diode.NewWriter(w, 1000, 0, func(missed int) {
zl.Warn().Int("count", missed).Msg("Logger dropped messages")
})
w = customOut
} else {
return err
}
}
if stdAny && formatted {
w = zerolog.ConsoleWriter{
Out: w,
NoColor: !colored,
TimeFormat: "2006-01-02 15:04:05.999",
}
}
if len(level) > 0 {
if logLevel, err := zerolog.ParseLevel(strings.ToLower(level)); err == nil {
lvl = logLevel
} else {
return err
}
}
rootMu.Lock()
defer rootMu.Unlock()
root = zerolog.New(w).With().Timestamp().Logger()
zerolog.SetGlobalLevel(lvl)
return nil
}
// Logger is the holder for zerolog.Logger which
// waits until root logger initialized to prevent
// mixed logging format and output
type Logger struct {
comp string
zlOnce sync.Once
zerolog.Logger
}
func (l *Logger) init() {
l.zlOnce.Do(func() {
l.Logger = root.With().Str("component", l.comp).Logger()
})
}
// ==== copied from zerolog ====
// Trace starts a new message with trace level.
//
// You must call Msg on the returned event in order to send the event.
func (l *Logger) Trace() *zerolog.Event {
l.init()
return l.Logger.Trace()
}
// Debug starts a new message with debug level.
//
// You must call Msg on the returned event in order to send the event.
func (l *Logger) Debug() *zerolog.Event {
l.init()
return l.Logger.Debug()
}
// Info starts a new message with info level.
//
// You must call Msg on the returned event in order to send the event.
func (l *Logger) Info() *zerolog.Event {
l.init()
return l.Logger.Info()
}
// Warn starts a new message with warn level.
//
// You must call Msg on the returned event in order to send the event.
func (l *Logger) Warn() *zerolog.Event {
l.init()
return l.Logger.Warn()
}
// Error starts a new message with error level.
//
// You must call Msg on the returned event in order to send the event.
func (l *Logger) Error() *zerolog.Event {
l.init()
return l.Logger.Error()
}
// Err starts a new message with error level with err as a field if not nil or
// with info level if err is nil.
//
// You must call Msg on the returned event in order to send the event.
func (l *Logger) Err(err error) *zerolog.Event {
l.init()
return l.Logger.Err(err)
}
// Fatal starts a new message with fatal level. The os.Exit(1) function
// is called by the Msg method, which terminates the program immediately.
//
// You must call Msg on the returned event in order to send the event.
func (l *Logger) Fatal() *zerolog.Event {
l.init()
return l.Logger.Fatal()
}
// Panic starts a new message with panic level. The panic() function
// is called by the Msg method, which stops the ordinary flow of a goroutine.
//
// You must call Msg on the returned event in order to send the event.
func (l *Logger) Panic() *zerolog.Event {
l.init()
return l.Logger.Panic()
}
// WithLevel starts a new message with level. Unlike Fatal and Panic
// methods, WithLevel does not terminate the program or stop the ordinary
// flow of a gourotine when used with their respective levels.
//
// You must call Msg on the returned event in order to send the event.
func (l *Logger) WithLevel(level zerolog.Level) *zerolog.Event {
l.init()
return l.Logger.WithLevel(level)
}
// Log starts a new message with no level. Setting GlobalLevel to Disabled
// will still disable events produced by this method.
//
// You must call Msg on the returned event in order to send the event.
func (l *Logger) Log() *zerolog.Event {
l.init()
return l.Logger.Log()
}
// Print sends a log event using debug level and no extra field.
// Arguments are handled in the manner of fmt.Print.
func (l *Logger) Print(v ...any) {
l.init()
l.Logger.Print(v...)
}
// Printf sends a log event using debug level and no extra field.
// Arguments are handled in the manner of fmt.Printf.
func (l *Logger) Printf(format string, v ...any) {
l.init()
l.Logger.Printf(format, v...)
}
// Write implements the io.Writer interface. This is useful to set as a writer
// for the standard library log.
func (l *Logger) Write(p []byte) (n int, err error) {
l.init()
return l.Logger.Write(p)
}
// Err starts a new message with error level with err as a field if not nil or
// with info level if err is nil.
//
// You must call Msg on the returned event in order to send the event.
func Err(err error) *zerolog.Event {
return root.Err(err)
}
// Trace starts a new message with trace level.
//
// You must call Msg on the returned event in order to send the event.
func Trace() *zerolog.Event {
return root.Trace()
}
// Debug starts a new message with debug level.
//
// You must call Msg on the returned event in order to send the event.
func Debug() *zerolog.Event {
return root.Debug()
}
// Info starts a new message with info level.
//
// You must call Msg on the returned event in order to send the event.
func Info() *zerolog.Event {
return root.Info()
}
// Warn starts a new message with warn level.
//
// You must call Msg on the returned event in order to send the event.
func Warn() *zerolog.Event {
return root.Warn()
}
// Error starts a new message with error level.
//
// You must call Msg on the returned event in order to send the event.
func Error() *zerolog.Event {
return root.Error()
}
// Fatal starts a new message with fatal level. The os.Exit(1) function
// is called by the Msg method.
//
// You must call Msg on the returned event in order to send the event.
func Fatal() *zerolog.Event {
return root.Fatal()
}
// Panic starts a new message with panic level. The message is also sent
// to the panic function.
//
// You must call Msg on the returned event in order to send the event.
func Panic() *zerolog.Event {
return root.Panic()
}
// WithLevel starts a new message with level.
//
// You must call Msg on the returned event in order to send the event.
func WithLevel(level zerolog.Level) *zerolog.Event {
return root.WithLevel(level)
}
// Log starts a new message with no level. Setting zerolog.GlobalLevel to
// zerolog.Disabled will still disable events produced by this method.
//
// You must call Msg on the returned event in order to send the event.
func Log() *zerolog.Event {
return root.Log()
}
// Print sends a log event using debug level and no extra field.
// Arguments are handled in the manner of fmt.Print.
func Print(v ...any) {
root.Print(v...)
}
// Printf sends a log event using debug level and no extra field.
// Arguments are handled in the manner of fmt.Printf.
func Printf(format string, v ...any) {
root.Printf(format, v...)
}
// Close closes custom output writer if it configured
func Close() {
customOutMu.Lock()
defer customOutMu.Unlock()
if customOut != nil {
_ = customOut.Close()
customOut = nil
}
}
// NewLogger creates child logger with specified component name
// NOTE: root logger MUST be initialized with ConfigureLogger
//
// before any logger call
func NewLogger(component string) *Logger {
return &Logger{comp: component}
}