frostfs-node/pkg/util/logger/logger.go
Dmitrii Stepanov 612b34d570
[#1437] logger: Add caller skip to log original caller position
Signed-off-by: Dmitrii Stepanov <d.stepanov@yadro.com>
2024-11-13 10:36:12 +03:00

196 lines
4.7 KiB
Go

package logger
import (
"fmt"
"git.frostfs.info/TrueCloudLab/zapjournald"
"github.com/ssgreg/journald"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
// Logger represents a component
// for writing messages to log.
type Logger struct {
z *zap.Logger
lvl zap.AtomicLevel
}
// Prm groups Logger's parameters.
// Successful passing non-nil parameters to the NewLogger (if returned
// error is nil) connects the parameters with the returned Logger.
// Parameters that have been connected to the Logger support its
// configuration changing.
//
// Passing Prm after a successful connection via the NewLogger, connects
// the Prm to a new instance of the Logger.
//
// See also Reload, SetLevelString.
type Prm struct {
// link to the created Logger
// instance; used for a runtime
// reconfiguration
_log *Logger
// support runtime rereading
level zapcore.Level
// SamplingHook hook for the zap.Logger
SamplingHook func(e zapcore.Entry, sd zapcore.SamplingDecision)
// do not support runtime rereading
dest string
// PrependTimestamp specifies whether to prepend a timestamp in the log
PrependTimestamp bool
}
const (
DestinationUndefined = ""
DestinationStdout = "stdout"
DestinationJournald = "journald"
)
// SetLevelString sets the minimum logging level. Default is
// "info".
//
// Returns an error if s is not a string representation of a
// supporting logging level.
//
// Supports runtime rereading.
func (p *Prm) SetLevelString(s string) error {
return p.level.UnmarshalText([]byte(s))
}
func (p *Prm) SetDestination(d string) error {
if d != DestinationStdout && d != DestinationJournald {
return fmt.Errorf("invalid logger destination %s", d)
}
if p != nil {
p.dest = d
}
return nil
}
// Reload reloads configuration of a connected instance of the Logger.
// Returns ErrLoggerNotConnected if no connection has been performed.
// Returns any reconfiguration error from the Logger directly.
func (p Prm) Reload() error {
if p._log == nil {
// incorrect logger usage
panic("parameters are not connected to any Logger")
}
return p._log.reload(p)
}
func defaultPrm() *Prm {
return new(Prm)
}
// NewLogger constructs a new zap logger instance. Constructing with nil
// parameters is safe: default values will be used then.
// Passing non-nil parameters after a successful creation (non-error) allows
// runtime reconfiguration.
//
// Logger is built from production logging configuration with:
// - parameterized level;
// - console encoding;
// - ISO8601 time encoding.
//
// Logger records a stack trace for all messages at or above fatal level.
func NewLogger(prm *Prm) (*Logger, error) {
if prm == nil {
prm = defaultPrm()
}
switch prm.dest {
case DestinationUndefined, DestinationStdout:
return newConsoleLogger(prm)
case DestinationJournald:
return newJournaldLogger(prm)
default:
return nil, fmt.Errorf("unknown destination %s", prm.dest)
}
}
func newConsoleLogger(prm *Prm) (*Logger, error) {
lvl := zap.NewAtomicLevelAt(prm.level)
c := zap.NewProductionConfig()
c.Level = lvl
c.Encoding = "console"
if prm.SamplingHook != nil {
c.Sampling.Hook = prm.SamplingHook
}
if prm.PrependTimestamp {
c.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
} else {
c.EncoderConfig.TimeKey = ""
}
lZap, err := c.Build(
zap.AddStacktrace(zap.NewAtomicLevelAt(zap.FatalLevel)),
zap.AddCallerSkip(1),
)
if err != nil {
return nil, err
}
l := &Logger{z: lZap, lvl: lvl}
prm._log = l
return l, nil
}
func newJournaldLogger(prm *Prm) (*Logger, error) {
lvl := zap.NewAtomicLevelAt(prm.level)
c := zap.NewProductionConfig()
c.Level = lvl
c.Encoding = "console"
if prm.SamplingHook != nil {
c.Sampling.Hook = prm.SamplingHook
}
if prm.PrependTimestamp {
c.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
} else {
c.EncoderConfig.TimeKey = ""
}
encoder := zapjournald.NewPartialEncoder(zapcore.NewConsoleEncoder(c.EncoderConfig), zapjournald.SyslogFields)
core := zapjournald.NewCore(lvl, encoder, &journald.Journal{}, zapjournald.SyslogFields)
coreWithContext := core.With([]zapcore.Field{
zapjournald.SyslogFacility(zapjournald.LogDaemon),
zapjournald.SyslogIdentifier(),
zapjournald.SyslogPid(),
})
lZap := zap.New(coreWithContext, zap.AddStacktrace(zap.NewAtomicLevelAt(zap.FatalLevel)), zap.AddCallerSkip(1))
l := &Logger{z: lZap, lvl: lvl}
prm._log = l
return l, nil
}
func (l *Logger) reload(prm Prm) error {
l.lvl.SetLevel(prm.level)
return nil
}
func (l *Logger) WithOptions(options ...zap.Option) {
l.z = l.z.WithOptions(options...)
}
func (l *Logger) With(fields ...zap.Field) *Logger {
return &Logger{z: l.z.With(fields...)}
}
func NewLoggerWrapper(z *zap.Logger) *Logger {
return &Logger{
z: z.WithOptions(zap.AddCallerSkip(1)),
}
}