github.com/openimsdk/tools@v0.0.49/log/zap.go (about)

     1  // Copyright © 2023 OpenIM. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package log
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"os"
    21  	"path/filepath"
    22  	"time"
    23  
    24  	rotatelogs "github.com/openimsdk/tools/log/file-rotatelogs"
    25  	"github.com/openimsdk/tools/utils/stringutil"
    26  
    27  	"go.uber.org/zap"
    28  	"go.uber.org/zap/zapcore"
    29  
    30  	"github.com/openimsdk/protocol/constant"
    31  	"github.com/openimsdk/tools/mcontext"
    32  )
    33  
    34  type LogFormatter interface {
    35  	Format() any
    36  }
    37  
    38  var (
    39  	pkgLogger   Logger
    40  	osStdout    Logger
    41  	sp          = string(filepath.Separator)
    42  	logLevelMap = map[int]zapcore.Level{
    43  		6: zapcore.DebugLevel,
    44  		5: zapcore.DebugLevel,
    45  		4: zapcore.InfoLevel,
    46  		3: zapcore.WarnLevel,
    47  		2: zapcore.ErrorLevel,
    48  		1: zapcore.FatalLevel,
    49  		0: zapcore.PanicLevel,
    50  	}
    51  )
    52  
    53  const callDepth = 2
    54  const hoursPerDay = 24
    55  
    56  // InitFromConfig initializes a Zap-based logger.
    57  func InitFromConfig(
    58  	loggerPrefixName, moduleName string,
    59  	logLevel int,
    60  	isStdout bool,
    61  	isJson bool,
    62  	logLocation string,
    63  	rotateCount uint,
    64  	rotationTime uint,
    65  	moduleVersion string,
    66  	isSimplify bool,
    67  ) error {
    68  	l, err := NewZapLogger(loggerPrefixName, moduleName, logLevel, isStdout, isJson, logLocation,
    69  		rotateCount, rotationTime, moduleVersion, isSimplify)
    70  	if err != nil {
    71  		return err
    72  	}
    73  	pkgLogger = l.WithCallDepth(callDepth)
    74  	if isJson {
    75  		pkgLogger = pkgLogger.WithName(moduleName)
    76  	}
    77  	return nil
    78  }
    79  
    80  // InitConsoleLogger init osStdout and osStderr.
    81  func InitConsoleLogger(moduleName string,
    82  	logLevel int,
    83  	isJson bool, moduleVersion string) error {
    84  	l, err := NewConsoleZapLogger(moduleName, logLevel, isJson, moduleVersion, os.Stdout)
    85  	if err != nil {
    86  		return err
    87  	}
    88  	osStdout = l.WithCallDepth(callDepth)
    89  	if isJson {
    90  		osStdout = osStdout.WithName(moduleName)
    91  	}
    92  	return nil
    93  
    94  }
    95  
    96  func ZDebug(ctx context.Context, msg string, keysAndValues ...any) {
    97  	if pkgLogger == nil {
    98  		return
    99  	}
   100  	pkgLogger.Debug(ctx, msg, keysAndValues...)
   101  }
   102  
   103  func ZInfo(ctx context.Context, msg string, keysAndValues ...any) {
   104  	if pkgLogger == nil {
   105  		return
   106  	}
   107  	pkgLogger.Info(ctx, msg, keysAndValues...)
   108  }
   109  
   110  func ZWarn(ctx context.Context, msg string, err error, keysAndValues ...any) {
   111  	if pkgLogger == nil {
   112  		return
   113  	}
   114  	pkgLogger.Warn(ctx, msg, err, keysAndValues...)
   115  }
   116  
   117  func ZError(ctx context.Context, msg string, err error, keysAndValues ...any) {
   118  	if pkgLogger == nil {
   119  		return
   120  	}
   121  	pkgLogger.Error(ctx, msg, err, keysAndValues...)
   122  }
   123  
   124  func CInfo(ctx context.Context, msg string, keysAndValues ...any) {
   125  	if osStdout == nil {
   126  		return
   127  	}
   128  	osStdout.Info(ctx, msg, keysAndValues...)
   129  }
   130  
   131  type ZapLogger struct {
   132  	zap              *zap.SugaredLogger
   133  	level            zapcore.Level
   134  	moduleName       string
   135  	moduleVersion    string
   136  	loggerPrefixName string
   137  	rotationTime     time.Duration
   138  	isSimplify       bool
   139  }
   140  
   141  func NewZapLogger(
   142  	loggerPrefixName, moduleName string,
   143  	logLevel int,
   144  	isStdout bool,
   145  	isJson bool,
   146  	logLocation string,
   147  	rotateCount uint,
   148  	rotationTime uint,
   149  	moduleVersion string,
   150  	isSimplify bool,
   151  ) (*ZapLogger, error) {
   152  	zapConfig := zap.Config{
   153  		Level:             zap.NewAtomicLevelAt(logLevelMap[logLevel]),
   154  		DisableStacktrace: true,
   155  	}
   156  	if isJson {
   157  		zapConfig.Encoding = "json"
   158  	} else {
   159  		zapConfig.Encoding = "console"
   160  	}
   161  	zl := &ZapLogger{level: logLevelMap[logLevel], moduleName: moduleName, loggerPrefixName: loggerPrefixName,
   162  		rotationTime: time.Duration(rotationTime) * time.Hour, moduleVersion: moduleVersion, isSimplify: isSimplify}
   163  	opts, err := zl.cores(isStdout, isJson, logLocation, rotateCount)
   164  	if err != nil {
   165  		return nil, err
   166  	}
   167  	l, err := zapConfig.Build(opts)
   168  	if err != nil {
   169  		return nil, err
   170  	}
   171  	zl.zap = l.Sugar()
   172  	return zl, nil
   173  }
   174  
   175  func NewConsoleZapLogger(
   176  	moduleName string,
   177  	logLevel int,
   178  	isJson bool,
   179  	moduleVersion string,
   180  	outPut *os.File) (*ZapLogger, error) {
   181  	zapConfig := zap.Config{
   182  		Level:             zap.NewAtomicLevelAt(logLevelMap[logLevel]),
   183  		DisableStacktrace: true,
   184  	}
   185  	if isJson {
   186  		zapConfig.Encoding = "json"
   187  	} else {
   188  		zapConfig.Encoding = "console"
   189  	}
   190  	zl := &ZapLogger{level: logLevelMap[logLevel], moduleName: moduleName, moduleVersion: moduleVersion}
   191  	opts, err := zl.consoleCores(outPut, isJson)
   192  	if err != nil {
   193  		return nil, err
   194  	}
   195  	l, err := zapConfig.Build(opts)
   196  	if err != nil {
   197  		return nil, err
   198  	}
   199  	zl.zap = l.Sugar()
   200  	return zl, nil
   201  }
   202  func (l *ZapLogger) cores(isStdout bool, isJson bool, logLocation string, rotateCount uint) (zap.Option, error) {
   203  	c := zap.NewProductionEncoderConfig()
   204  	c.EncodeTime = l.timeEncoder
   205  	c.EncodeDuration = zapcore.SecondsDurationEncoder
   206  	c.MessageKey = "msg"
   207  	c.LevelKey = "level"
   208  	c.TimeKey = "time"
   209  	c.CallerKey = "caller"
   210  	c.NameKey = "logger"
   211  	var fileEncoder zapcore.Encoder
   212  	if isJson {
   213  		c.EncodeLevel = zapcore.CapitalLevelEncoder
   214  		fileEncoder = zapcore.NewJSONEncoder(c)
   215  		fileEncoder.AddInt("PID", os.Getpid())
   216  		fileEncoder.AddString("version", l.moduleVersion)
   217  	} else {
   218  		c.EncodeLevel = l.capitalColorLevelEncoder
   219  		c.EncodeCaller = l.customCallerEncoder
   220  		fileEncoder = zapcore.NewConsoleEncoder(c)
   221  	}
   222  	fileEncoder = &alignEncoder{Encoder: fileEncoder}
   223  	writer, err := l.getWriter(logLocation, rotateCount)
   224  	if err != nil {
   225  		return nil, err
   226  	}
   227  	var cores []zapcore.Core
   228  	if logLocation != "" {
   229  		cores = []zapcore.Core{
   230  			zapcore.NewCore(fileEncoder, writer, zap.NewAtomicLevelAt(l.level)),
   231  		}
   232  	}
   233  	if isStdout {
   234  		cores = append(cores, zapcore.NewCore(fileEncoder, zapcore.Lock(os.Stdout), zap.NewAtomicLevelAt(l.level)))
   235  		// cores = append(cores, zapcore.NewCore(fileEncoder, zapcore.Lock(os.Stderr), zap.NewAtomicLevelAt(l.level)))
   236  	}
   237  	return zap.WrapCore(func(c zapcore.Core) zapcore.Core {
   238  		return zapcore.NewTee(cores...)
   239  	}), nil
   240  }
   241  
   242  func (l *ZapLogger) consoleCores(outPut *os.File, isJson bool) (zap.Option, error) {
   243  	c := zap.NewProductionEncoderConfig()
   244  	c.EncodeTime = l.timeEncoder
   245  	c.EncodeDuration = zapcore.SecondsDurationEncoder
   246  	c.MessageKey = "msg"
   247  	c.LevelKey = "level"
   248  	c.TimeKey = "time"
   249  	c.CallerKey = "caller"
   250  	c.NameKey = "logger"
   251  	var fileEncoder zapcore.Encoder
   252  	if isJson {
   253  		c.EncodeLevel = zapcore.CapitalLevelEncoder
   254  		fileEncoder = zapcore.NewJSONEncoder(c)
   255  		fileEncoder.AddInt("PID", os.Getpid())
   256  		fileEncoder.AddString("version", l.moduleVersion)
   257  	} else {
   258  		c.EncodeLevel = l.capitalColorLevelEncoder
   259  		c.EncodeCaller = l.customCallerEncoder
   260  		fileEncoder = zapcore.NewConsoleEncoder(c)
   261  	}
   262  	var cores []zapcore.Core
   263  	cores = append(cores, zapcore.NewCore(fileEncoder, zapcore.Lock(outPut), zap.NewAtomicLevelAt(l.level)))
   264  
   265  	return zap.WrapCore(func(c zapcore.Core) zapcore.Core {
   266  		return zapcore.NewTee(cores...)
   267  	}), nil
   268  }
   269  
   270  func (l *ZapLogger) customCallerEncoder(caller zapcore.EntryCaller, enc zapcore.PrimitiveArrayEncoder) {
   271  	fixedLength := 50
   272  	trimmedPath := caller.TrimmedPath()
   273  	trimmedPath = "[" + trimmedPath + "]"
   274  	s := stringutil.FormatString(trimmedPath, fixedLength, true)
   275  	enc.AppendString(s)
   276  }
   277  
   278  func (l *ZapLogger) timeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
   279  	layout := "2006-01-02 15:04:05.000"
   280  	type appendTimeEncoder interface {
   281  		AppendTimeLayout(time.Time, string)
   282  	}
   283  	if enc, ok := enc.(appendTimeEncoder); ok {
   284  		enc.AppendTimeLayout(t, layout)
   285  		return
   286  	}
   287  	enc.AppendString(t.Format(layout))
   288  }
   289  
   290  func (l *ZapLogger) getWriter(logLocation string, rorateCount uint) (zapcore.WriteSyncer, error) {
   291  	var path string
   292  	if l.rotationTime%(time.Hour*time.Duration(hoursPerDay)) == 0 {
   293  		path = logLocation + sp + l.loggerPrefixName + ".%Y-%m-%d"
   294  	} else if l.rotationTime%time.Hour == 0 {
   295  		path = logLocation + sp + l.loggerPrefixName + ".%Y-%m-%d_%H"
   296  	} else {
   297  		path = logLocation + sp + l.loggerPrefixName + ".%Y-%m-%d_%H_%M_%S"
   298  	}
   299  	logf, err := rotatelogs.New(path,
   300  		rotatelogs.WithRotationCount(rorateCount),
   301  		rotatelogs.WithRotationTime(l.rotationTime),
   302  	)
   303  	if err != nil {
   304  		return nil, err
   305  	}
   306  	return zapcore.AddSync(logf), nil
   307  }
   308  
   309  func (l *ZapLogger) capitalColorLevelEncoder(level zapcore.Level, enc zapcore.PrimitiveArrayEncoder) {
   310  	s, ok := _levelToCapitalColorString[level]
   311  	if !ok {
   312  		s = _unknownLevelColor[zapcore.ErrorLevel]
   313  	}
   314  	pid := stringutil.FormatString(fmt.Sprintf("["+"PID:"+"%d"+"]", os.Getpid()), 15, true)
   315  	color := _levelToColor[level]
   316  	enc.AppendString(s)
   317  	enc.AppendString(color.Add(pid))
   318  	if l.moduleName != "" {
   319  		moduleName := stringutil.FormatString(l.moduleName, 25, true)
   320  		enc.AppendString(color.Add(moduleName))
   321  	}
   322  	if l.moduleVersion != "" {
   323  		moduleVersion := stringutil.FormatString(fmt.Sprintf("["+"version:"+"%s"+"]", l.moduleVersion), 17, true)
   324  		enc.AppendString(moduleVersion)
   325  	}
   326  }
   327  
   328  func (l *ZapLogger) ToZap() *zap.SugaredLogger {
   329  	return l.zap
   330  }
   331  
   332  func (l *ZapLogger) Debug(ctx context.Context, msg string, keysAndValues ...any) {
   333  	if l.level > zapcore.DebugLevel {
   334  		return
   335  	}
   336  	keysAndValues = l.kvAppend(ctx, keysAndValues)
   337  	l.zap.Debugw(msg, keysAndValues...)
   338  }
   339  
   340  func (l *ZapLogger) Info(ctx context.Context, msg string, keysAndValues ...any) {
   341  	if l.level > zapcore.InfoLevel {
   342  		return
   343  	}
   344  	keysAndValues = l.kvAppend(ctx, keysAndValues)
   345  	l.zap.Infow(msg, keysAndValues...)
   346  }
   347  
   348  func (l *ZapLogger) Warn(ctx context.Context, msg string, err error, keysAndValues ...any) {
   349  	if l.level > zapcore.WarnLevel {
   350  		return
   351  	}
   352  	if err != nil {
   353  		keysAndValues = append(keysAndValues, "error", err.Error())
   354  	}
   355  	keysAndValues = l.kvAppend(ctx, keysAndValues)
   356  	l.zap.Warnw(msg, keysAndValues...)
   357  }
   358  
   359  func (l *ZapLogger) Error(ctx context.Context, msg string, err error, keysAndValues ...any) {
   360  	if l.level > zapcore.ErrorLevel {
   361  		return
   362  	}
   363  	if err != nil {
   364  		keysAndValues = append(keysAndValues, "error", err.Error())
   365  	}
   366  	keysAndValues = l.kvAppend(ctx, keysAndValues)
   367  	l.zap.Errorw(msg, keysAndValues...)
   368  }
   369  
   370  func (l *ZapLogger) kvAppend(ctx context.Context, keysAndValues []any) []any {
   371  	if ctx == nil {
   372  		return keysAndValues
   373  	}
   374  	operationID := mcontext.GetOperationID(ctx)
   375  	opUserID := mcontext.GetOpUserID(ctx)
   376  	connID := mcontext.GetConnID(ctx)
   377  	triggerID := mcontext.GetTriggerID(ctx)
   378  	opUserPlatform := mcontext.GetOpUserPlatform(ctx)
   379  	remoteAddr := mcontext.GetRemoteAddr(ctx)
   380  
   381  	if l.isSimplify {
   382  		if len(keysAndValues)%2 == 0 {
   383  			for i := 1; i < len(keysAndValues); i += 2 {
   384  
   385  				if val, ok := keysAndValues[i].(LogFormatter); ok && val != nil {
   386  					keysAndValues[i] = val.Format()
   387  				}
   388  			}
   389  		} else {
   390  			ZError(ctx, "keysAndValues length is not even", nil)
   391  		}
   392  	}
   393  
   394  	for i := 1; i < len(keysAndValues); i += 2 {
   395  		if s, ok := keysAndValues[i].(interface{ String() string }); ok {
   396  			keysAndValues[i] = s.String()
   397  		}
   398  	}
   399  
   400  	if opUserID != "" {
   401  		keysAndValues = append([]any{constant.OpUserID, opUserID}, keysAndValues...)
   402  	}
   403  	if operationID != "" {
   404  		keysAndValues = append([]any{constant.OperationID, operationID}, keysAndValues...)
   405  	}
   406  	if connID != "" {
   407  		keysAndValues = append([]any{constant.ConnID, connID}, keysAndValues...)
   408  	}
   409  	if triggerID != "" {
   410  		keysAndValues = append([]any{constant.TriggerID, triggerID}, keysAndValues...)
   411  	}
   412  	if opUserPlatform != "" {
   413  		keysAndValues = append([]any{constant.OpUserPlatform, opUserPlatform}, keysAndValues...)
   414  	}
   415  	if remoteAddr != "" {
   416  		keysAndValues = append([]any{constant.RemoteAddr, remoteAddr}, keysAndValues...)
   417  	}
   418  	return keysAndValues
   419  }
   420  
   421  func (l *ZapLogger) WithValues(keysAndValues ...any) Logger {
   422  	dup := *l
   423  	dup.zap = l.zap.With(keysAndValues...)
   424  	return &dup
   425  }
   426  
   427  func (l *ZapLogger) WithName(name string) Logger {
   428  	dup := *l
   429  	dup.zap = l.zap.Named(name)
   430  	return &dup
   431  }
   432  
   433  func (l *ZapLogger) WithCallDepth(depth int) Logger {
   434  	dup := *l
   435  	dup.zap = l.zap.WithOptions(zap.AddCallerSkip(depth))
   436  	return &dup
   437  }