Files
protocol/logger/logger.go
2025-02-07 14:33:56 -08:00

486 lines
13 KiB
Go

// Copyright 2023 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package logger
import (
"log/slog"
"os"
"slices"
"strings"
"sync"
"time"
"github.com/go-logr/logr"
"github.com/puzpuzpuz/xsync/v3"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"github.com/livekit/protocol/logger/zaputil"
)
var (
discardLogger = logr.Discard()
defaultLogger Logger = LogRLogger(discardLogger)
pkgLogger Logger = LogRLogger(discardLogger)
)
// InitFromConfig initializes a Zap-based logger
func InitFromConfig(conf *Config, name string) {
l, err := NewZapLogger(conf)
if err == nil {
SetLogger(l, name)
slog.SetDefault(slog.New(ToSlogHandler(l)))
}
}
// GetLogger returns the logger that was set with SetLogger with an extra depth of 1
func GetLogger() Logger {
return defaultLogger
}
// SetLogger lets you use a custom logger. Pass in a logr.Logger with default depth
func SetLogger(l Logger, name string) {
defaultLogger = l.WithName(name)
// pkg wrapper needs to drop two levels of depth
pkgLogger = l.WithCallDepth(1).WithName(name)
}
func Debugw(msg string, keysAndValues ...any) {
pkgLogger.Debugw(msg, keysAndValues...)
}
func Infow(msg string, keysAndValues ...any) {
pkgLogger.Infow(msg, keysAndValues...)
}
func Warnw(msg string, err error, keysAndValues ...any) {
pkgLogger.Warnw(msg, err, keysAndValues...)
}
func Errorw(msg string, err error, keysAndValues ...any) {
pkgLogger.Errorw(msg, err, keysAndValues...)
}
func ParseZapLevel(level string) zapcore.Level {
lvl := zapcore.InfoLevel
if level != "" {
_ = lvl.UnmarshalText([]byte(level))
}
return lvl
}
type DeferredFieldResolver = zaputil.DeferredFieldResolver
type Logger interface {
Debugw(msg string, keysAndValues ...any)
Infow(msg string, keysAndValues ...any)
Warnw(msg string, err error, keysAndValues ...any)
Errorw(msg string, err error, keysAndValues ...any)
WithValues(keysAndValues ...any) Logger
WithUnlikelyValues(keysAndValues ...any) UnlikelyLogger
WithName(name string) Logger
// WithComponent creates a new logger with name as "<name>.<component>", and uses a log level as specified
WithComponent(component string) Logger
WithCallDepth(depth int) Logger
WithItemSampler() Logger
// WithoutSampler returns the original logger without sampling
WithoutSampler() Logger
WithDeferredValues() (Logger, DeferredFieldResolver)
}
type UnlikelyLogger struct {
logger Logger
keysAndValues []any
}
func (l UnlikelyLogger) makeLogger() Logger {
return l.logger.WithCallDepth(1)
}
func (l UnlikelyLogger) Debugw(msg string, keysAndValues ...any) {
l.makeLogger().Debugw(msg, slices.Concat(l.keysAndValues, keysAndValues)...)
}
func (l UnlikelyLogger) Infow(msg string, keysAndValues ...any) {
l.makeLogger().Infow(msg, slices.Concat(l.keysAndValues, keysAndValues)...)
}
func (l UnlikelyLogger) Warnw(msg string, err error, keysAndValues ...any) {
l.makeLogger().Warnw(msg, err, slices.Concat(l.keysAndValues, keysAndValues)...)
}
func (l UnlikelyLogger) Errorw(msg string, err error, keysAndValues ...any) {
l.makeLogger().Errorw(msg, err, slices.Concat(l.keysAndValues, keysAndValues)...)
}
func (l UnlikelyLogger) WithValues(keysAndValues ...any) UnlikelyLogger {
return UnlikelyLogger{l.logger, slices.Concat(l.keysAndValues, keysAndValues)}
}
type sharedConfig struct {
level zap.AtomicLevel
mu sync.Mutex
componentLevels map[string]zap.AtomicLevel
config *Config
}
func newSharedConfig(conf *Config) *sharedConfig {
sc := &sharedConfig{
level: zap.NewAtomicLevelAt(ParseZapLevel(conf.Level)),
config: conf,
componentLevels: make(map[string]zap.AtomicLevel),
}
conf.AddUpdateObserver(sc.onConfigUpdate)
_ = sc.onConfigUpdate(conf)
return sc
}
func (c *sharedConfig) onConfigUpdate(conf *Config) error {
// update log levels
c.level.SetLevel(ParseZapLevel(conf.Level))
// we have to update alla existing component levels
c.mu.Lock()
c.config = conf
for component, atomicLevel := range c.componentLevels {
effectiveLevel := c.level.Level()
parts := strings.Split(component, ".")
confSearch:
for len(parts) > 0 {
search := strings.Join(parts, ".")
if compLevel, ok := conf.ComponentLevels[search]; ok {
effectiveLevel = ParseZapLevel(compLevel)
break confSearch
}
parts = parts[:len(parts)-1]
}
atomicLevel.SetLevel(effectiveLevel)
}
c.mu.Unlock()
return nil
}
// ensure we have an atomic level in the map representing the full component path
// this makes it possible to update the log level after the fact
func (c *sharedConfig) ComponentLevel(component string) zap.AtomicLevel {
c.mu.Lock()
defer c.mu.Unlock()
if compLevel, ok := c.componentLevels[component]; ok {
return compLevel
}
// search up the hierarchy to find the first level that is set
atomicLevel := zap.NewAtomicLevelAt(c.level.Level())
c.componentLevels[component] = atomicLevel
parts := strings.Split(component, ".")
for len(parts) > 0 {
search := strings.Join(parts, ".")
if compLevel, ok := c.config.ComponentLevels[search]; ok {
atomicLevel.SetLevel(ParseZapLevel(compLevel))
return atomicLevel
}
parts = parts[:len(parts)-1]
}
return atomicLevel
}
type zapConfig struct {
conf *Config
sc *sharedConfig
writeEnablers *xsync.MapOf[string, *zaputil.WriteEnabler]
levelEnablers *xsync.MapOf[string, *zaputil.OrLevelEnabler]
tap *zaputil.WriteEnabler
}
type ZapLoggerOption func(*zapConfig)
func WithTap(tap *zaputil.WriteEnabler) ZapLoggerOption {
return func(zc *zapConfig) {
zc.tap = tap
}
}
type ZapComponentLeveler interface {
ComponentLevel(component string) zapcore.LevelEnabler
}
type ZapLogger interface {
Logger
ToZap() *zap.SugaredLogger
ComponentLeveler() ZapComponentLeveler
WithMinLevel(lvl zapcore.LevelEnabler) Logger
}
type zapLogger[T zaputil.Encoder[T]] struct {
zap *zap.SugaredLogger
*zapConfig
enc T
component string
deferred []*zaputil.Deferrer
sampler *zaputil.Sampler
minLevel zapcore.LevelEnabler
}
func FromZapLogger(log *zap.Logger, conf *Config, opts ...ZapLoggerOption) (ZapLogger, error) {
if log == nil {
log = zap.New(nil).WithOptions(zap.AddCaller(), zap.AddStacktrace(zap.ErrorLevel))
}
zap := log.WithOptions(zap.AddCallerSkip(1)).Sugar()
zc := &zapConfig{
conf: conf,
sc: newSharedConfig(conf),
writeEnablers: xsync.NewMapOf[string, *zaputil.WriteEnabler](),
levelEnablers: xsync.NewMapOf[string, *zaputil.OrLevelEnabler](),
tap: zaputil.NewDiscardWriteEnabler(),
}
for _, opt := range opts {
opt(zc)
}
var sampler *zaputil.Sampler
if conf.Sample {
var initial = 20
var interval = 100
if conf.ItemSampleInitial != 0 {
initial = conf.ItemSampleInitial
}
if conf.ItemSampleInterval != 0 {
interval = conf.ItemSampleInterval
}
sampler = zaputil.NewSampler(time.Second, initial, interval)
}
if conf.JSON {
return newZapLogger(zap, zc, zaputil.NewProductionEncoder(), sampler), nil
} else {
return newZapLogger(zap, zc, zaputil.NewDevelopmentEncoder(), sampler), nil
}
}
func NewZapLogger(conf *Config, opts ...ZapLoggerOption) (ZapLogger, error) {
return FromZapLogger(nil, conf, opts...)
}
func newZapLogger[T zaputil.Encoder[T]](zap *zap.SugaredLogger, zc *zapConfig, enc T, sampler *zaputil.Sampler) ZapLogger {
l := &zapLogger[T]{
zap: zap,
zapConfig: zc,
enc: enc,
sampler: sampler,
}
l.zap = l.makeZap()
return l
}
func (l *zapLogger[T]) makeZap() *zap.SugaredLogger {
var console *zaputil.WriteEnabler
if l.minLevel == nil {
console, _ = l.writeEnablers.LoadOrCompute(l.component, func() *zaputil.WriteEnabler {
return zaputil.NewWriteEnabler(os.Stderr, l.sc.ComponentLevel(l.component))
})
} else {
enab := zaputil.OrLevelEnabler{l.minLevel, l.sc.ComponentLevel(l.component)}
console = zaputil.NewWriteEnabler(os.Stderr, enab)
}
c := l.enc.Core(console, l.tap)
for i := range l.deferred {
c = zaputil.NewDeferredValueCore(c, l.deferred[i])
}
if l.sampler != nil {
c = zaputil.NewSamplerCore(c, l.sampler)
}
return l.zap.WithOptions(zap.WrapCore(func(zapcore.Core) zapcore.Core { return c }))
}
func (l *zapLogger[T]) ToZap() *zap.SugaredLogger {
return l.zap.WithOptions(zap.AddCallerSkip(-1))
}
type zapLoggerComponentLeveler[T zaputil.Encoder[T]] struct {
zl *zapLogger[T]
}
func (l zapLoggerComponentLeveler[T]) ComponentLevel(component string) zapcore.LevelEnabler {
if l.zl.component != "" {
component = l.zl.component + "." + component
}
enab, _ := l.zl.levelEnablers.LoadOrCompute(component, func() *zaputil.OrLevelEnabler {
return &zaputil.OrLevelEnabler{l.zl.sc.ComponentLevel(component), l.zl.tap}
})
return enab
}
func (l *zapLogger[T]) ComponentLeveler() ZapComponentLeveler {
return zapLoggerComponentLeveler[T]{l}
}
func (l *zapLogger[T]) Debugw(msg string, keysAndValues ...any) {
l.zap.Debugw(msg, keysAndValues...)
}
func (l *zapLogger[T]) WithMinLevel(lvl zapcore.LevelEnabler) Logger {
dup := *l
dup.minLevel = lvl
dup.zap = dup.makeZap()
return &dup
}
func (l *zapLogger[T]) Infow(msg string, keysAndValues ...any) {
l.zap.Infow(msg, keysAndValues...)
}
func (l *zapLogger[T]) Warnw(msg string, err error, keysAndValues ...any) {
if err != nil {
keysAndValues = append(keysAndValues, "error", err)
}
l.zap.Warnw(msg, keysAndValues...)
}
func (l *zapLogger[T]) Errorw(msg string, err error, keysAndValues ...any) {
if err != nil {
keysAndValues = append(keysAndValues, "error", err)
}
l.zap.Errorw(msg, keysAndValues...)
}
func (l *zapLogger[T]) WithValues(keysAndValues ...any) Logger {
dup := *l
dup.enc = dup.enc.WithValues(keysAndValues...)
dup.zap = dup.makeZap()
return &dup
}
func (l *zapLogger[T]) WithUnlikelyValues(keysAndValues ...any) UnlikelyLogger {
return UnlikelyLogger{l, keysAndValues}
}
func (l *zapLogger[T]) WithName(name string) Logger {
dup := *l
dup.zap = dup.zap.Named(name)
return &dup
}
func (l *zapLogger[T]) WithComponent(component string) Logger {
dup := *l
dup.zap = dup.zap.Named(component)
if dup.component == "" {
dup.component = component
} else {
dup.component = dup.component + "." + component
}
dup.zap = dup.makeZap()
return &dup
}
func (l *zapLogger[T]) WithCallDepth(depth int) Logger {
dup := *l
dup.zap = dup.zap.WithOptions(zap.AddCallerSkip(depth))
return &dup
}
func (l *zapLogger[T]) WithItemSampler() Logger {
if l.conf.ItemSampleSeconds == 0 {
return l
}
dup := *l
dup.sampler = zaputil.NewSampler(
time.Duration(l.conf.ItemSampleSeconds)*time.Second,
l.conf.ItemSampleInitial,
l.conf.ItemSampleInterval,
)
dup.zap = dup.makeZap()
return &dup
}
func (l *zapLogger[T]) WithoutSampler() Logger {
dup := *l
dup.sampler = nil
dup.zap = dup.makeZap()
return &dup
}
func (l *zapLogger[T]) WithDeferredValues() (Logger, DeferredFieldResolver) {
dup := *l
def, resolve := zaputil.NewDeferrer()
dup.deferred = append(dup.deferred[0:len(dup.deferred):len(dup.deferred)], def)
dup.zap = dup.makeZap()
return &dup, resolve
}
type LogRLogger logr.Logger
func (l LogRLogger) toLogr() logr.Logger {
if logr.Logger(l).GetSink() == nil {
return discardLogger
}
return logr.Logger(l)
}
func (l LogRLogger) Debugw(msg string, keysAndValues ...any) {
l.toLogr().V(1).Info(msg, keysAndValues...)
}
func (l LogRLogger) Infow(msg string, keysAndValues ...any) {
l.toLogr().Info(msg, keysAndValues...)
}
func (l LogRLogger) Warnw(msg string, err error, keysAndValues ...any) {
if err != nil {
keysAndValues = append(keysAndValues, "error", err)
}
l.toLogr().Info(msg, keysAndValues...)
}
func (l LogRLogger) Errorw(msg string, err error, keysAndValues ...any) {
l.toLogr().Error(err, msg, keysAndValues...)
}
func (l LogRLogger) WithValues(keysAndValues ...any) Logger {
return LogRLogger(l.toLogr().WithValues(keysAndValues...))
}
func (l LogRLogger) WithUnlikelyValues(keysAndValues ...any) UnlikelyLogger {
return UnlikelyLogger{l, keysAndValues}
}
func (l LogRLogger) WithName(name string) Logger {
return LogRLogger(l.toLogr().WithName(name))
}
func (l LogRLogger) WithComponent(component string) Logger {
return LogRLogger(l.toLogr().WithName(component))
}
func (l LogRLogger) WithCallDepth(depth int) Logger {
return LogRLogger(l.toLogr().WithCallDepth(depth))
}
func (l LogRLogger) WithItemSampler() Logger {
// logr does not support sampling
return l
}
func (l LogRLogger) WithoutSampler() Logger {
return l
}
func (l LogRLogger) WithDeferredValues() (Logger, DeferredFieldResolver) {
return l, func(args ...any) {}
}