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 }