github.com/benz9527/xboot@v0.0.0-20240504061247-c23f15593274/xlog/xlog.go (about)

     1  package xlog
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"sort"
     8  	"strings"
     9  	"sync"
    10  	"sync/atomic"
    11  
    12  	"go.uber.org/zap"
    13  	"go.uber.org/zap/zapcore"
    14  
    15  	"github.com/benz9527/xboot/lib/infra"
    16  	"github.com/benz9527/xboot/lib/kv"
    17  )
    18  
    19  var printBanner = sync.Once{}
    20  
    21  // XLogger is wrapper logger of Uber zap logger.
    22  type xLogger struct {
    23  	cancelFn            context.CancelFunc
    24  	logger              atomic.Pointer[zap.Logger]
    25  	ctxFields           kv.ThreadSafeStorer[string, string]
    26  	dynamicLevelEnabler zap.AtomicLevel
    27  	writer              logOutWriterType
    28  	encoder             logEncoderType
    29  }
    30  
    31  func (l *xLogger) zap() *zap.Logger {
    32  	return l.logger.Load()
    33  }
    34  
    35  // IncreaseLogLevel we can increase or decrease the log level concurrently.
    36  func (l *xLogger) IncreaseLogLevel(level zapcore.Level) {
    37  	l.dynamicLevelEnabler.SetLevel(level)
    38  }
    39  
    40  func (l *xLogger) Sync() error {
    41  	return l.logger.Load().Sync()
    42  }
    43  
    44  func (l *xLogger) Level() string {
    45  	return l.dynamicLevelEnabler.Level().String()
    46  }
    47  
    48  func (l *xLogger) Close() {
    49  	if l.cancelFn != nil {
    50  		l.cancelFn()
    51  	}
    52  }
    53  
    54  func (l *xLogger) Banner(banner Banner) {
    55  	printBanner.Do(func() {
    56  		var enc zapcore.Encoder
    57  		core := zapcore.EncoderConfig{
    58  			MessageKey:    "banner", // Required, but the plain text will be ignored.
    59  			LevelKey:      coreKeyIgnored,
    60  			EncodeLevel:   nil,
    61  			TimeKey:       coreKeyIgnored,
    62  			EncodeTime:    nil,
    63  			CallerKey:     coreKeyIgnored,
    64  			EncodeCaller:  nil,
    65  			StacktraceKey: coreKeyIgnored,
    66  		}
    67  		switch l.encoder {
    68  		case JSON:
    69  			enc = zapcore.NewJSONEncoder(core)
    70  		case PlainText:
    71  			enc = zapcore.NewConsoleEncoder(core)
    72  		}
    73  		ws := getOutWriterByType(l.writer)
    74  		lvlEnabler := zap.NewAtomicLevelAt(zapcore.InfoLevel)
    75  		_l := l.logger.Load().WithOptions(
    76  			zap.WrapCore(func(core zapcore.Core) zapcore.Core {
    77  				return zapcore.NewCore(enc, ws, lvlEnabler)
    78  			}),
    79  		)
    80  		switch l.encoder {
    81  		case JSON:
    82  			_l.Info(banner.JSON())
    83  		case PlainText:
    84  			_l.Info(banner.PlainText())
    85  		}
    86  	})
    87  }
    88  
    89  func (l *xLogger) Log(lvl zapcore.Level, msg string, fields ...zap.Field) {
    90  	l.logger.Load().Log(lvl, msg, fields...)
    91  }
    92  
    93  func (l *xLogger) Debug(msg string, fields ...zap.Field) {
    94  	l.logger.Load().Debug(msg, fields...)
    95  }
    96  
    97  func (l *xLogger) Info(msg string, fields ...zap.Field) {
    98  	l.logger.Load().Info(msg, fields...)
    99  }
   100  
   101  func (l *xLogger) Warn(msg string, fields ...zap.Field) {
   102  	l.logger.Load().Warn(msg, fields...)
   103  }
   104  
   105  func (l *xLogger) Error(err error, msg string, fields ...zap.Field) {
   106  	newFields := make([]zap.Field, 0, len(fields)+1)
   107  	if err != nil {
   108  		newFields = append(newFields, zap.String("error", err.Error()))
   109  	}
   110  	newFields = append(newFields, fields...)
   111  	l.logger.Load().Error(msg, newFields...)
   112  }
   113  
   114  func (l *xLogger) ErrorStack(err error, msg string, fields ...zap.Field) {
   115  	var newFields []zap.Field
   116  	if es, ok := err.(infra.ErrorStack); ok && es != nil {
   117  		newFields = []zap.Field{
   118  			zap.Inline(es),
   119  		}
   120  	}
   121  	newFields = append(newFields, fields...)
   122  	l.logger.Load().Error(msg, newFields...)
   123  }
   124  
   125  func (l *xLogger) DebugContext(ctx context.Context, msg string, fields ...zap.Field) {
   126  	newFields := extractFieldsFromContext(ctx, l.ctxFields)
   127  	newFields = append(newFields, fields...)
   128  	l.logger.Load().Debug(msg, newFields...)
   129  }
   130  
   131  func (l *xLogger) InfoContext(ctx context.Context, msg string, fields ...zap.Field) {
   132  	newFields := extractFieldsFromContext(ctx, l.ctxFields)
   133  	newFields = append(newFields, fields...)
   134  	l.logger.Load().Info(msg, newFields...)
   135  }
   136  
   137  func (l *xLogger) WarnContext(ctx context.Context, msg string, fields ...zap.Field) {
   138  	newFields := extractFieldsFromContext(ctx, l.ctxFields)
   139  	newFields = append(newFields, fields...)
   140  	l.logger.Load().Warn(msg, newFields...)
   141  }
   142  
   143  func (l *xLogger) ErrorContext(ctx context.Context, err error, msg string, fields ...zap.Field) {
   144  	newFields := extractFieldsFromContext(ctx, l.ctxFields)
   145  	if err != nil {
   146  		newFields = append(newFields, zap.String("error", err.Error()))
   147  	}
   148  	newFields = append(newFields, fields...)
   149  	l.logger.Load().Error(msg, newFields...)
   150  }
   151  
   152  func (l *xLogger) ErrorStackContext(ctx context.Context, err error, msg string, fields ...zap.Field) {
   153  	newFields := extractFieldsFromContext(ctx, l.ctxFields)
   154  	if es, ok := err.(infra.ErrorStack); ok && es != nil {
   155  		newFields = append(newFields, zap.Inline(es))
   156  	}
   157  	newFields = append(newFields, fields...)
   158  	l.logger.Load().Error(msg, newFields...)
   159  }
   160  
   161  func (l *xLogger) Logf(lvl zapcore.Level, format string, args ...any) {
   162  	l.logger.Load().Log(lvl, fmt.Sprintf(format, args...))
   163  }
   164  
   165  func (l *xLogger) ErrorStackf(err error, format string, args ...any) {
   166  	var newFields []zap.Field
   167  	if es, ok := err.(infra.ErrorStack); ok && es != nil {
   168  		newFields = []zap.Field{
   169  			zap.Inline(es),
   170  		}
   171  	}
   172  	l.logger.Load().Log(zap.ErrorLevel, fmt.Sprintf(format, args...), newFields...)
   173  }
   174  
   175  type loggerCfg struct {
   176  	ctx              context.Context
   177  	cancelFn         context.CancelFunc
   178  	ctxFields        kv.ThreadSafeStorer[string, string]
   179  	encoderType      *logEncoderType
   180  	lvlEncoder       zapcore.LevelEncoder
   181  	tsEncoder        zapcore.TimeEncoder
   182  	level            *zapcore.Level
   183  	coreConstructors []XLogCoreConstructor
   184  	cores            []xLogCore
   185  }
   186  
   187  func (cfg *loggerCfg) apply(l *xLogger) {
   188  	if cfg.encoderType != nil {
   189  		l.encoder = *cfg.encoderType
   190  	} else {
   191  		l.encoder = JSON
   192  	}
   193  
   194  	if cfg.level != nil {
   195  		l.dynamicLevelEnabler = zap.NewAtomicLevelAt(*cfg.level)
   196  	} else {
   197  		l.dynamicLevelEnabler = zap.NewAtomicLevelAt(getLogLevelOrDefault(os.Getenv("XLOG_LVL")))
   198  	}
   199  
   200  	l.ctxFields = cfg.ctxFields
   201  
   202  	if cfg.lvlEncoder == nil {
   203  		cfg.lvlEncoder = zapcore.CapitalLevelEncoder
   204  	}
   205  
   206  	if cfg.tsEncoder == nil {
   207  		cfg.tsEncoder = zapcore.ISO8601TimeEncoder
   208  	}
   209  
   210  	if cfg.coreConstructors == nil || len(cfg.coreConstructors) == 0 {
   211  		cfg.coreConstructors = []XLogCoreConstructor{
   212  			newConsoleCore,
   213  		}
   214  	}
   215  
   216  	if cfg.ctx == nil {
   217  		cfg.ctx, l.cancelFn = context.WithCancel(context.Background())
   218  	} else {
   219  		cfg.ctx, l.cancelFn = context.WithCancel(cfg.ctx)
   220  	}
   221  
   222  	cfg.cores = make([]xLogCore, 0, 16)
   223  	for _, cc := range cfg.coreConstructors {
   224  		cfg.cores = append(cfg.cores, cc(
   225  			cfg.ctx,
   226  			l.dynamicLevelEnabler,
   227  			l.encoder,
   228  			cfg.lvlEncoder,
   229  			cfg.tsEncoder,
   230  		))
   231  	}
   232  }
   233  
   234  type XLoggerOption func(*loggerCfg) error
   235  
   236  func NewXLogger(opts ...XLoggerOption) XLogger {
   237  	cfg := &loggerCfg{}
   238  	for _, o := range opts {
   239  		if o == nil {
   240  			continue
   241  		}
   242  		if err := o(cfg); err != nil {
   243  			panic(err)
   244  		}
   245  	}
   246  	xl := &xLogger{}
   247  	cfg.apply(xl)
   248  
   249  	// Disable zap logger error stack.
   250  	l := zap.New(
   251  		XLogTeeCore(cfg.cores...),
   252  		zap.AddCallerSkip(1), // Use caller filename as service
   253  		zap.AddCaller(),
   254  	)
   255  	xl.logger.Store(l)
   256  	return xl
   257  }
   258  
   259  func WithXLoggerContext(ctx context.Context) XLoggerOption {
   260  	return func(cfg *loggerCfg) error {
   261  		cfg.ctx = ctx
   262  		return nil
   263  	}
   264  }
   265  
   266  func WithXLoggerStdOutWriter() XLoggerOption {
   267  	return func(cfg *loggerCfg) error {
   268  		if cfg.coreConstructors == nil || len(cfg.coreConstructors) == 0 {
   269  			cfg.coreConstructors = make([]XLogCoreConstructor, 0, 8)
   270  		}
   271  		cfg.coreConstructors = append(cfg.coreConstructors, newConsoleCore)
   272  		return nil
   273  	}
   274  }
   275  
   276  func WithXLoggerFileWriter(coreCfg *FileCoreConfig) XLoggerOption {
   277  	return func(cfg *loggerCfg) error {
   278  		if cfg.coreConstructors == nil || len(cfg.coreConstructors) == 0 {
   279  			cfg.coreConstructors = make([]XLogCoreConstructor, 0, 8)
   280  		}
   281  		cfg.coreConstructors = append(cfg.coreConstructors, newFileCore(coreCfg))
   282  		return nil
   283  	}
   284  }
   285  
   286  func WithXLoggerEncoder(logEnc logEncoderType) XLoggerOption {
   287  	return func(cfg *loggerCfg) error {
   288  		if logEnc == _encMax {
   289  			return infra.NewErrorStack("unknown xlogger encoder")
   290  		}
   291  		cfg.encoderType = &logEnc
   292  		return nil
   293  	}
   294  }
   295  
   296  func WithXLoggerLevel(lvl logLevel) XLoggerOption {
   297  	return func(cfg *loggerCfg) error {
   298  		_lvl := lvl.zapLevel()
   299  		cfg.level = &_lvl
   300  		return nil
   301  	}
   302  }
   303  
   304  func WithXLoggerLevelEncoder(lvlEnc zapcore.LevelEncoder) XLoggerOption {
   305  	return func(cfg *loggerCfg) error {
   306  		if lvlEnc == nil {
   307  			lvlEnc = zapcore.CapitalColorLevelEncoder
   308  		}
   309  		cfg.lvlEncoder = lvlEnc
   310  		return nil
   311  	}
   312  }
   313  
   314  func WithXLoggerTimeEncoder(tsEnc zapcore.TimeEncoder) XLoggerOption {
   315  	return func(cfg *loggerCfg) error {
   316  		if tsEnc == nil {
   317  			tsEnc = zapcore.ISO8601TimeEncoder
   318  		}
   319  		cfg.tsEncoder = tsEnc
   320  		return nil
   321  	}
   322  }
   323  
   324  func WithXLoggerContextFieldExtract(field string, mapTo ...string) XLoggerOption {
   325  	return func(cfg *loggerCfg) error {
   326  		if len(field) == 0 {
   327  			return nil
   328  		}
   329  		if cfg.ctxFields == nil {
   330  			cfg.ctxFields = kv.NewThreadSafeMap[string, string]()
   331  		}
   332  		if len(mapTo) == 0 || mapTo[0] == ContextKeyMapToItself {
   333  			mapTo = []string{field}
   334  		}
   335  		return cfg.ctxFields.AddOrUpdate(field, mapTo[0])
   336  	}
   337  }
   338  
   339  func getLogLevelOrDefault(level string) zapcore.Level {
   340  	switch strings.ToUpper(level) {
   341  	case LogLevelInfo.String():
   342  		return zapcore.InfoLevel
   343  	case LogLevelWarn.String():
   344  		return zapcore.WarnLevel
   345  	case LogLevelError.String():
   346  		return zapcore.ErrorLevel
   347  	case LogLevelDebug.String():
   348  		fallthrough
   349  	default:
   350  	}
   351  	return zapcore.DebugLevel
   352  }
   353  
   354  func extractFieldsFromContext(
   355  	ctx context.Context,
   356  	targets kv.ThreadSafeStorer[string, string],
   357  ) []zap.Field {
   358  	if ctx == nil || targets == nil {
   359  		return []zap.Field{}
   360  	}
   361  
   362  	keys := targets.ListKeys()
   363  	sort.StringSlice(keys).Sort()
   364  	newFields := make([]zap.Field, 0, len(keys))
   365  	for _, key := range keys {
   366  		v := ctx.Value(key)
   367  		mapTo, _ := targets.Get(key)
   368  		if v == nil && mapTo != ContextKeyMapToOmitempty {
   369  			newFields = append(newFields, zap.String(mapTo, "nil"))
   370  		} else if v != nil && mapTo != ContextKeyMapToOmitempty {
   371  			newFields = append(newFields, zap.Any(mapTo, v))
   372  		}
   373  	}
   374  	return newFields
   375  }