github.com/polarismesh/polaris@v1.17.8/common/log/config.go (about)

     1  /**
     2   * Tencent is pleased to support the open source community by making Polaris available.
     3   *
     4   * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
     5   *
     6   * Licensed under the BSD 3-Clause License (the "License");
     7   * you may not use this file except in compliance with the License.
     8   * You may obtain a copy of the License at
     9   *
    10   * https://opensource.org/licenses/BSD-3-Clause
    11   *
    12   * Unless required by applicable law or agreed to in writing, software distributed
    13   * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
    14   * CONDITIONS OF ANY KIND, either express or implied. See the License for the
    15   * specific language governing permissions and limitations under the License.
    16   */
    17  
    18  // Package log
    19  // Once configured, this package intercepts the output of the standard golang "log" package as well as anything
    20  // sent to the global zap logger (zap.L()).
    21  package log
    22  
    23  import (
    24  	"fmt"
    25  	"net/url"
    26  	"os"
    27  	"path/filepath"
    28  	"time"
    29  
    30  	"github.com/hashicorp/go-multierror"
    31  	"github.com/natefinch/lumberjack"
    32  	"go.uber.org/zap"
    33  	"go.uber.org/zap/zapcore"
    34  	"go.uber.org/zap/zapgrpc"
    35  	"google.golang.org/grpc/grpclog"
    36  
    37  	"github.com/polarismesh/polaris/common/timewheel"
    38  )
    39  
    40  // none is used to disable logging output as well as to disable stack tracing.
    41  const none zapcore.Level = 100
    42  
    43  var levelToZap = map[Level]zapcore.Level{
    44  	DebugLevel: zapcore.DebugLevel,
    45  	InfoLevel:  zapcore.InfoLevel,
    46  	WarnLevel:  zapcore.WarnLevel,
    47  	ErrorLevel: zapcore.ErrorLevel,
    48  	FatalLevel: zapcore.FatalLevel,
    49  	NoneLevel:  none,
    50  }
    51  
    52  // functions that can be replaced in a test setting
    53  type patchTable struct {
    54  	write       func(ent zapcore.Entry, fields []zapcore.Field) error
    55  	sync        func() error
    56  	exitProcess func(code int)
    57  	errorSink   zapcore.WriteSyncer
    58  }
    59  
    60  func init() {
    61  	// use our defaults for starters so that logging works even before everything is fully configured
    62  	_ = Configure(DefaultOptions())
    63  }
    64  
    65  // prepZap is a utility function used by the Configure function.
    66  func prepZap(options *Options) ([]zapcore.Core, zapcore.Core, zapcore.WriteSyncer, *lumberjack.Logger, error) {
    67  	encCfg := zapcore.EncoderConfig{
    68  		TimeKey:        "time",
    69  		LevelKey:       "level",
    70  		NameKey:        "scope",
    71  		CallerKey:      "caller",
    72  		MessageKey:     "msg",
    73  		StacktraceKey:  "stack",
    74  		LineEnding:     zapcore.DefaultLineEnding,
    75  		EncodeLevel:    zapcore.LowercaseLevelEncoder,
    76  		EncodeCaller:   zapcore.ShortCallerEncoder,
    77  		EncodeDuration: zapcore.StringDurationEncoder,
    78  		EncodeTime:     formatDate,
    79  	}
    80  
    81  	if options.OnlyContent {
    82  		encCfg.EncodeLevel = func(l zapcore.Level, pae zapcore.PrimitiveArrayEncoder) {}
    83  		encCfg.EncodeCaller = func(ec zapcore.EntryCaller, pae zapcore.PrimitiveArrayEncoder) {}
    84  		encCfg.EncodeName = func(s string, pae zapcore.PrimitiveArrayEncoder) {}
    85  		encCfg.EncodeTime = func(t time.Time, pae zapcore.PrimitiveArrayEncoder) {}
    86  	}
    87  
    88  	var enc zapcore.Encoder
    89  	if options.JSONEncoding {
    90  		enc = zapcore.NewJSONEncoder(encCfg)
    91  	} else {
    92  		enc = zapcore.NewConsoleEncoder(encCfg)
    93  	}
    94  
    95  	var rotateSink zapcore.WriteSyncer
    96  	var l *lumberjack.Logger
    97  	if len(options.RotateOutputPath) > 0 {
    98  		l = &lumberjack.Logger{
    99  			Filename:   options.RotateOutputPath,
   100  			MaxSize:    options.RotationMaxSize,
   101  			MaxBackups: options.RotationMaxBackups,
   102  			MaxAge:     options.RotationMaxAge,
   103  			Compress:   options.Compress,
   104  		}
   105  		rotateSink = zapcore.AddSync(l)
   106  	}
   107  
   108  	err := createPathIfNotExist(options.ErrorOutputPaths...)
   109  	if err != nil {
   110  		return nil, nil, nil, nil, err
   111  	}
   112  	errSink, closeErrorSink, err := zap.Open(options.ErrorOutputPaths...)
   113  	if err != nil {
   114  		return nil, nil, nil, nil, err
   115  	}
   116  
   117  	var outputSink zapcore.WriteSyncer
   118  	if len(options.OutputPaths) > 0 {
   119  		err := createPathIfNotExist(options.OutputPaths...)
   120  		if err != nil {
   121  			return nil, nil, nil, nil, err
   122  		}
   123  		outputSink, _, err = zap.Open(options.OutputPaths...)
   124  		if err != nil {
   125  			closeErrorSink()
   126  			return nil, nil, nil, nil, err
   127  		}
   128  	}
   129  
   130  	var sink zapcore.WriteSyncer
   131  	if rotateSink != nil && outputSink != nil {
   132  		sink = zapcore.NewMultiWriteSyncer(outputSink, rotateSink)
   133  	} else if rotateSink != nil {
   134  		sink = rotateSink
   135  	} else if outputSink != nil {
   136  		sink = outputSink
   137  	} else {
   138  		sink = zapcore.AddSync(os.Stdout)
   139  	}
   140  
   141  	var enabler zap.LevelEnablerFunc = func(lvl zapcore.Level) bool {
   142  		switch lvl {
   143  		case zapcore.FatalLevel:
   144  			return defaultScope.FatalEnabled()
   145  		case zapcore.ErrorLevel:
   146  			return defaultScope.ErrorEnabled()
   147  		case zapcore.WarnLevel:
   148  			return defaultScope.WarnEnabled()
   149  		case zapcore.InfoLevel:
   150  			return defaultScope.InfoEnabled()
   151  		}
   152  		return defaultScope.DebugEnabled()
   153  	}
   154  
   155  	var errCore zapcore.Core
   156  	if len(options.ErrorRotateOutputPath) > 0 {
   157  		errRotateSink := zapcore.AddSync(&lumberjack.Logger{
   158  			Filename:   options.ErrorRotateOutputPath,
   159  			MaxSize:    options.RotationMaxSize,
   160  			MaxBackups: options.RotationMaxBackups,
   161  			MaxAge:     options.RotationMaxAge,
   162  		})
   163  		errCore = zapcore.NewCore(enc, errRotateSink, zap.NewAtomicLevelAt(zapcore.ErrorLevel))
   164  	}
   165  
   166  	cores := make([]zapcore.Core, 0)
   167  	cores = append(cores, zapcore.NewCore(enc, sink, zap.NewAtomicLevelAt(zapcore.DebugLevel)))
   168  	if errCore != nil {
   169  		cores = append(cores, errCore)
   170  	}
   171  	return cores, zapcore.NewCore(enc, sink, enabler), errSink, l, nil
   172  }
   173  
   174  func formatDate(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
   175  	// t = t.UTC() 不用utc时间
   176  	year, month, day := t.Date()
   177  	hour, minute, second := t.Clock()
   178  	micros := t.Nanosecond() / 1000
   179  
   180  	buf := make([]byte, 27)
   181  
   182  	buf[0] = byte((year/1000)%10) + '0'
   183  	buf[1] = byte((year/100)%10) + '0'
   184  	buf[2] = byte((year/10)%10) + '0'
   185  	buf[3] = byte(year%10) + '0'
   186  	buf[4] = '-'
   187  	buf[5] = byte((month)/10) + '0'
   188  	buf[6] = byte((month)%10) + '0'
   189  	buf[7] = '-'
   190  	buf[8] = byte((day)/10) + '0'
   191  	buf[9] = byte((day)%10) + '0'
   192  	buf[10] = 'T'
   193  	buf[11] = byte((hour)/10) + '0'
   194  	buf[12] = byte((hour)%10) + '0'
   195  	buf[13] = ':'
   196  	buf[14] = byte((minute)/10) + '0'
   197  	buf[15] = byte((minute)%10) + '0'
   198  	buf[16] = ':'
   199  	buf[17] = byte((second)/10) + '0'
   200  	buf[18] = byte((second)%10) + '0'
   201  	buf[19] = '.'
   202  	buf[20] = byte((micros/100000)%10) + '0'
   203  	buf[21] = byte((micros/10000)%10) + '0'
   204  	buf[22] = byte((micros/1000)%10) + '0'
   205  	buf[23] = byte((micros/100)%10) + '0'
   206  	buf[24] = byte((micros/10)%10) + '0'
   207  	buf[25] = byte((micros)%10) + '0'
   208  	buf[26] = 'Z'
   209  
   210  	enc.AppendString(string(buf))
   211  }
   212  
   213  func updateScopes(typeName string, options *Options, cores []zapcore.Core, errSink zapcore.WriteSyncer) error {
   214  	scope := FindScope(typeName)
   215  	if scope == nil {
   216  		// 对还没注册的日志配置进行注册
   217  		scope = RegisterScope(typeName, fmt.Sprintf("%s logging messages.", typeName), 0)
   218  	}
   219  
   220  	// update the output levels of all listed scopes
   221  	outPutLevel, ok := stringToLevel[options.OutputLevel]
   222  	if !ok {
   223  		return fmt.Errorf("unknown outPutLevel '%s' specified", options.OutputLevel)
   224  	}
   225  
   226  	// update the stack tracing levels of all listed scopes
   227  	stackTraceLevel, ok := stringToLevel[options.StackTraceLevel]
   228  	if !ok {
   229  		return fmt.Errorf("unknown stackTraceLevel '%s' specified", options.StackTraceLevel)
   230  	}
   231  
   232  	lock.Lock()
   233  	defer lock.Unlock()
   234  
   235  	scope.SetOutputLevel(outPutLevel)
   236  	scope.SetStackTraceLevel(stackTraceLevel)
   237  
   238  	// update patchTable
   239  	pt := &patchTable{
   240  		write: func(ent zapcore.Entry, fields []zapcore.Field) error {
   241  			var errs error
   242  			for _, core := range cores {
   243  				if core.Enabled(ent.Level) {
   244  					if err := core.Write(ent, fields); err != nil {
   245  						errs = multierror.Append(errs, err)
   246  					}
   247  				}
   248  			}
   249  			if ent.Level == zapcore.FatalLevel {
   250  				scope.getPathTable().exitProcess(1)
   251  			}
   252  
   253  			return errs
   254  		},
   255  		sync: func() error {
   256  			var errs error
   257  			for _, core := range cores {
   258  				if err := core.Sync(); err != nil {
   259  					errs = multierror.Append(errs, err)
   260  				}
   261  			}
   262  			return errs
   263  		},
   264  		exitProcess: os.Exit,
   265  		errorSink:   errSink,
   266  	}
   267  
   268  	scope.pt = pt
   269  	// update the caller location setting of all listed scopes
   270  	scope.SetDisableLogCaller(options.DisableLogCaller)
   271  
   272  	return nil
   273  }
   274  
   275  func logRotationSyncCallback(tw *timewheel.TimeWheel, rotationMaxDurationForHour int64, log *lumberjack.Logger) {
   276  	tw.AddTask(uint32(rotationMaxDurationForHour*time.Hour.Milliseconds()), nil, func(i interface{}) {
   277  		if err := log.Rotate(); err != nil {
   278  			return
   279  		}
   280  		logRotationSyncCallback(tw, rotationMaxDurationForHour, log)
   281  	})
   282  }
   283  
   284  // Configure .
   285  // You typically call this once at process startup.
   286  // Configure Once this call returns, the logging system is ready to accept data.
   287  func Configure(optionsMap map[string]*Options) error {
   288  	tw := timewheel.New(time.Minute, 5, "log rotation sync")
   289  	tw.Start()
   290  	for typeName, options := range optionsMap {
   291  		setDefaultOption(options)
   292  		cores, captureCore, errSink, log, err := prepZap(options)
   293  		if err != nil {
   294  			return err
   295  		}
   296  		if options.RotationMaxDurationForHour != 0 {
   297  			logRotationSyncCallback(tw, int64(options.RotationMaxDurationForHour), log)
   298  		}
   299  		if err = updateScopes(typeName, options, cores, errSink); err != nil {
   300  			return err
   301  		}
   302  
   303  		targetScope := scopes[typeName]
   304  
   305  		opts := []zap.Option{
   306  			zap.ErrorOutput(errSink),
   307  		}
   308  
   309  		if !targetScope.GetDisableLogCaller() {
   310  			opts = append(opts,
   311  				zap.AddCallerSkip(1),
   312  				zap.AddCaller(),
   313  			)
   314  		}
   315  
   316  		l := targetScope.GetStackTraceLevel()
   317  		if l != NoneLevel {
   318  			opts = append(opts, zap.AddStacktrace(levelToZap[l]))
   319  		}
   320  
   321  		captureLogger := zap.New(captureCore, opts...)
   322  
   323  		if typeName == DefaultLoggerName {
   324  			// capture global zap logging and force it through our logger
   325  			_ = zap.ReplaceGlobals(captureLogger)
   326  			// capture standard golang "log" package output and force it through our logger
   327  			_ = zap.RedirectStdLog(captureLogger)
   328  		}
   329  
   330  		// capture gRPC logging
   331  		if options.LogGrpc {
   332  			grpclog.SetLogger(zapgrpc.NewLogger(captureLogger.WithOptions(zap.AddCallerSkip(2))))
   333  		}
   334  
   335  		// if typeName == DefaultLoggerName {
   336  		// 	opts := []zap.Option{
   337  		// 		zap.ErrorOutput(errSink),
   338  		// 		zap.AddCallerSkip(1),
   339  		// 	}
   340  
   341  		// 	if defaultScope.GetLogCallers() {
   342  		// 		opts = append(opts, zap.AddCaller())
   343  		// 	}
   344  
   345  		// 	l := defaultScope.GetStackTraceLevel()
   346  		// 	if l != NoneLevel {
   347  		// 		opts = append(opts, zap.AddStacktrace(levelToZap[l]))
   348  		// 	}
   349  
   350  		// 	captureLogger := zap.New(captureCore, opts...)
   351  
   352  		// 	// capture global zap logging and force it through our logger
   353  		// 	_ = zap.ReplaceGlobals(captureLogger)
   354  
   355  		// 	// capture standard golang "log" package output and force it through our logger
   356  		// 	_ = zap.RedirectStdLog(captureLogger)
   357  
   358  		// 	// capture gRPC logging
   359  		// 	if options.LogGrpc {
   360  		// 		grpclog.SetLogger(zapgrpc.NewLogger(captureLogger.WithOptions(zap.AddCallerSkip(2))))
   361  		// 	}
   362  		// }
   363  	}
   364  	return nil
   365  }
   366  
   367  // setDefaultOption 设置日志配置的默认值
   368  func setDefaultOption(options *Options) {
   369  	if options.RotationMaxSize == 0 {
   370  		options.RotationMaxSize = defaultRotationMaxSize
   371  	}
   372  	if options.RotationMaxAge == 0 {
   373  		options.RotationMaxAge = defaultRotationMaxAge
   374  	}
   375  	if options.RotationMaxBackups == 0 {
   376  		options.RotationMaxBackups = defaultRotationMaxBackups
   377  	}
   378  	if options.OutputLevel == "" {
   379  		options.OutputLevel = levelToString[defaultOutputLevel]
   380  	}
   381  	if options.StackTraceLevel == "" {
   382  		options.StackTraceLevel = levelToString[defaultStackTraceLevel]
   383  	}
   384  }
   385  
   386  // Sync flushes any buffered log entries.
   387  // Processes should normally take care to call Sync before exiting.
   388  func Sync() error {
   389  	return defaultScope.getPathTable().sync()
   390  }
   391  
   392  // createPathIfNotExist 如果判断为本地文件,检查目录是否存在,不存在创建父级目录
   393  func createPathIfNotExist(paths ...string) error {
   394  	for _, path := range paths {
   395  		u, err := url.Parse(path)
   396  		if err != nil {
   397  			return fmt.Errorf("can't parse %q as a URL: %v", path, err)
   398  		}
   399  		if (u.Scheme == "" || u.Scheme == "file") && u.Path != "stdout" && u.Path != "stderr" {
   400  			dir := filepath.Dir(u.Path)
   401  			err := os.MkdirAll(dir, os.ModePerm)
   402  			if err != nil {
   403  				return fmt.Errorf("can't create %q directory: %v", dir, err)
   404  			}
   405  		}
   406  	}
   407  	return nil
   408  }