go.temporal.io/server@v1.23.0/common/log/zap_logger.go (about)

     1  // The MIT License
     2  //
     3  // Copyright (c) 2020 Temporal Technologies Inc.  All rights reserved.
     4  //
     5  // Copyright (c) 2020 Uber Technologies, Inc.
     6  //
     7  // Permission is hereby granted, free of charge, to any person obtaining a copy
     8  // of this software and associated documentation files (the "Software"), to deal
     9  // in the Software without restriction, including without limitation the rights
    10  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    11  // copies of the Software, and to permit persons to whom the Software is
    12  // furnished to do so, subject to the following conditions:
    13  //
    14  // The above copyright notice and this permission notice shall be included in
    15  // all copies or substantial portions of the Software.
    16  //
    17  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    18  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    19  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    20  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    21  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    22  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    23  // THE SOFTWARE.
    24  
    25  package log
    26  
    27  import (
    28  	"os"
    29  	"path/filepath"
    30  	"runtime"
    31  	"strconv"
    32  	"strings"
    33  
    34  	"go.uber.org/zap"
    35  	"go.uber.org/zap/zapcore"
    36  
    37  	"go.temporal.io/server/common/log/tag"
    38  )
    39  
    40  const (
    41  	skipForZapLogger = 3
    42  	// we put a default message when it is empty so that the log can be searchable/filterable
    43  	defaultMsgForEmpty  = "none"
    44  	testLogFormatEnvVar = "TEMPORAL_TEST_LOG_FORMAT" // set to "json" for json logs in tests
    45  	testLogLevelEnvVar  = "TEMPORAL_TEST_LOG_LEVEL"  // set to "debug" for debug level logs in tests
    46  )
    47  
    48  type (
    49  	// zapLogger is logger backed up by zap.Logger.
    50  	zapLogger struct {
    51  		zl   *zap.Logger
    52  		skip int
    53  	}
    54  )
    55  
    56  var _ Logger = (*zapLogger)(nil)
    57  
    58  // NewTestLogger returns a logger for tests
    59  func NewTestLogger() *zapLogger {
    60  	format := os.Getenv(testLogFormatEnvVar)
    61  	if format == "" {
    62  		format = "console"
    63  	}
    64  
    65  	logger := BuildZapLogger(Config{
    66  		Level:       os.Getenv(testLogLevelEnvVar),
    67  		Format:      format,
    68  		Development: true,
    69  	})
    70  
    71  	// Don't include stack traces for warnings during tests. Only include them for logs with level error and above.
    72  	logger = logger.WithOptions(zap.AddStacktrace(zap.ErrorLevel))
    73  
    74  	return NewZapLogger(logger)
    75  }
    76  
    77  // NewCLILogger returns a logger at debug level and log into STDERR for logging from within CLI tools
    78  func NewCLILogger() *zapLogger {
    79  	return NewZapLogger(buildCLIZapLogger())
    80  }
    81  
    82  // NewZapLogger returns a new zap based logger from zap.Logger
    83  func NewZapLogger(zl *zap.Logger) *zapLogger {
    84  	return &zapLogger{
    85  		zl:   zl,
    86  		skip: skipForZapLogger,
    87  	}
    88  }
    89  
    90  // BuildZapLogger builds and returns a new zap.Logger for this logging configuration
    91  func BuildZapLogger(cfg Config) *zap.Logger {
    92  	return buildZapLogger(cfg, true)
    93  }
    94  
    95  func caller(skip int) string {
    96  	_, path, line, ok := runtime.Caller(skip)
    97  	if !ok {
    98  		return ""
    99  	}
   100  	return filepath.Base(path) + ":" + strconv.Itoa(line)
   101  }
   102  
   103  func (l *zapLogger) buildFieldsWithCallAt(tags []tag.Tag) []zap.Field {
   104  	fields := make([]zap.Field, len(tags)+1)
   105  	l.fillFields(tags, fields)
   106  	fields[len(fields)-1] = zap.String(tag.LoggingCallAtKey, caller(l.skip))
   107  	return fields
   108  }
   109  
   110  // fillFields fill fields parameter with fields read from tags. Optimized for performance.
   111  func (l *zapLogger) fillFields(tags []tag.Tag, fields []zap.Field) {
   112  	for i, t := range tags {
   113  		if zt, ok := t.(tag.ZapTag); ok {
   114  			fields[i] = zt.Field()
   115  		} else {
   116  			fields[i] = zap.Any(t.Key(), t.Value())
   117  		}
   118  	}
   119  }
   120  
   121  func setDefaultMsg(msg string) string {
   122  	if msg == "" {
   123  		return defaultMsgForEmpty
   124  	}
   125  	return msg
   126  }
   127  
   128  func (l *zapLogger) Debug(msg string, tags ...tag.Tag) {
   129  	if l.zl.Core().Enabled(zap.DebugLevel) {
   130  		msg = setDefaultMsg(msg)
   131  		fields := l.buildFieldsWithCallAt(tags)
   132  		l.zl.Debug(msg, fields...)
   133  	}
   134  }
   135  
   136  func (l *zapLogger) Info(msg string, tags ...tag.Tag) {
   137  	if l.zl.Core().Enabled(zap.InfoLevel) {
   138  		msg = setDefaultMsg(msg)
   139  		fields := l.buildFieldsWithCallAt(tags)
   140  		l.zl.Info(msg, fields...)
   141  	}
   142  }
   143  
   144  func (l *zapLogger) Warn(msg string, tags ...tag.Tag) {
   145  	if l.zl.Core().Enabled(zap.WarnLevel) {
   146  		msg = setDefaultMsg(msg)
   147  		fields := l.buildFieldsWithCallAt(tags)
   148  		l.zl.Warn(msg, fields...)
   149  	}
   150  }
   151  
   152  func (l *zapLogger) Error(msg string, tags ...tag.Tag) {
   153  	if l.zl.Core().Enabled(zap.ErrorLevel) {
   154  		msg = setDefaultMsg(msg)
   155  		fields := l.buildFieldsWithCallAt(tags)
   156  		l.zl.Error(msg, fields...)
   157  	}
   158  }
   159  
   160  func (l *zapLogger) DPanic(msg string, tags ...tag.Tag) {
   161  	if l.zl.Core().Enabled(zap.DPanicLevel) {
   162  		msg = setDefaultMsg(msg)
   163  		fields := l.buildFieldsWithCallAt(tags)
   164  		l.zl.DPanic(msg, fields...)
   165  	}
   166  }
   167  
   168  func (l *zapLogger) Panic(msg string, tags ...tag.Tag) {
   169  	if l.zl.Core().Enabled(zap.PanicLevel) {
   170  		msg = setDefaultMsg(msg)
   171  		fields := l.buildFieldsWithCallAt(tags)
   172  		l.zl.Panic(msg, fields...)
   173  	}
   174  }
   175  
   176  func (l *zapLogger) Fatal(msg string, tags ...tag.Tag) {
   177  	if l.zl.Core().Enabled(zap.FatalLevel) {
   178  		msg = setDefaultMsg(msg)
   179  		fields := l.buildFieldsWithCallAt(tags)
   180  		l.zl.Fatal(msg, fields...)
   181  	}
   182  }
   183  
   184  func (l *zapLogger) With(tags ...tag.Tag) Logger {
   185  	fields := make([]zap.Field, len(tags))
   186  	l.fillFields(tags, fields)
   187  	zl := l.zl.With(fields...)
   188  	return &zapLogger{
   189  		zl:   zl,
   190  		skip: l.skip,
   191  	}
   192  }
   193  
   194  func (l *zapLogger) Skip(extraSkip int) Logger {
   195  	return &zapLogger{
   196  		zl:   l.zl,
   197  		skip: l.skip + extraSkip,
   198  	}
   199  }
   200  
   201  func buildZapLogger(cfg Config, disableCaller bool) *zap.Logger {
   202  	encodeConfig := zapcore.EncoderConfig{
   203  		TimeKey:        "ts",
   204  		LevelKey:       "level",
   205  		NameKey:        "logger",
   206  		CallerKey:      zapcore.OmitKey, // we use our own caller
   207  		FunctionKey:    zapcore.OmitKey,
   208  		MessageKey:     "msg",
   209  		StacktraceKey:  "stacktrace",
   210  		LineEnding:     zapcore.DefaultLineEnding,
   211  		EncodeLevel:    zapcore.LowercaseLevelEncoder,
   212  		EncodeTime:     zapcore.ISO8601TimeEncoder,
   213  		EncodeDuration: zapcore.SecondsDurationEncoder,
   214  		EncodeCaller:   zapcore.ShortCallerEncoder,
   215  	}
   216  	if disableCaller {
   217  		encodeConfig.CallerKey = zapcore.OmitKey
   218  		encodeConfig.EncodeCaller = nil
   219  	}
   220  
   221  	outputPath := "stderr"
   222  	if len(cfg.OutputFile) > 0 {
   223  		outputPath = cfg.OutputFile
   224  	}
   225  	if cfg.Stdout {
   226  		outputPath = "stdout"
   227  	}
   228  	encoding := "json"
   229  	if cfg.Format == "console" {
   230  		encoding = "console"
   231  	}
   232  	config := zap.Config{
   233  		Level:            zap.NewAtomicLevelAt(parseZapLevel(cfg.Level)),
   234  		Development:      cfg.Development,
   235  		Sampling:         nil,
   236  		Encoding:         encoding,
   237  		EncoderConfig:    encodeConfig,
   238  		OutputPaths:      []string{outputPath},
   239  		ErrorOutputPaths: []string{outputPath},
   240  		DisableCaller:    disableCaller,
   241  	}
   242  	logger, _ := config.Build()
   243  	return logger
   244  }
   245  
   246  func buildCLIZapLogger() *zap.Logger {
   247  	encodeConfig := zapcore.EncoderConfig{
   248  		TimeKey:        "ts",
   249  		LevelKey:       "level",
   250  		NameKey:        "logger",
   251  		CallerKey:      zapcore.OmitKey, // we use our own caller
   252  		FunctionKey:    zapcore.OmitKey,
   253  		MessageKey:     "msg",
   254  		StacktraceKey:  "stacktrace",
   255  		LineEnding:     zapcore.DefaultLineEnding,
   256  		EncodeLevel:    zapcore.CapitalColorLevelEncoder,
   257  		EncodeTime:     zapcore.ISO8601TimeEncoder,
   258  		EncodeDuration: zapcore.SecondsDurationEncoder,
   259  		EncodeCaller:   nil,
   260  	}
   261  
   262  	config := zap.Config{
   263  		Level:             zap.NewAtomicLevelAt(zap.DebugLevel),
   264  		Development:       false,
   265  		DisableStacktrace: os.Getenv("TEMPORAL_CLI_SHOW_STACKS") == "",
   266  		Sampling:          nil,
   267  		Encoding:          "console",
   268  		EncoderConfig:     encodeConfig,
   269  		OutputPaths:       []string{"stderr"},
   270  		ErrorOutputPaths:  []string{"stderr"},
   271  		DisableCaller:     true,
   272  	}
   273  	logger, _ := config.Build()
   274  	return logger
   275  }
   276  
   277  func parseZapLevel(level string) zapcore.Level {
   278  	switch strings.ToLower(level) {
   279  	case "debug":
   280  		return zap.DebugLevel
   281  	case "info":
   282  		return zap.InfoLevel
   283  	case "warn":
   284  		return zap.WarnLevel
   285  	case "error":
   286  		return zap.ErrorLevel
   287  	case "dpanic":
   288  		return zap.DPanicLevel
   289  	case "panic":
   290  		return zap.PanicLevel
   291  	case "fatal":
   292  		return zap.FatalLevel
   293  	default:
   294  		return zap.InfoLevel
   295  	}
   296  }