khenaidoo | ac63710 | 2019-01-14 15:44:34 -0500 | [diff] [blame^] | 1 | // Package zerolog provides a lightweight logging library dedicated to JSON logging. |
| 2 | // |
| 3 | // A global Logger can be use for simple logging: |
| 4 | // |
| 5 | // import "github.com/rs/zerolog/log" |
| 6 | // |
| 7 | // log.Info().Msg("hello world") |
| 8 | // // Output: {"time":1494567715,"level":"info","message":"hello world"} |
| 9 | // |
| 10 | // NOTE: To import the global logger, import the "log" subpackage "github.com/rs/zerolog/log". |
| 11 | // |
| 12 | // Fields can be added to log messages: |
| 13 | // |
| 14 | // log.Info().Str("foo", "bar").Msg("hello world") |
| 15 | // // Output: {"time":1494567715,"level":"info","message":"hello world","foo":"bar"} |
| 16 | // |
| 17 | // Create logger instance to manage different outputs: |
| 18 | // |
| 19 | // logger := zerolog.New(os.Stderr).With().Timestamp().Logger() |
| 20 | // logger.Info(). |
| 21 | // Str("foo", "bar"). |
| 22 | // Msg("hello world") |
| 23 | // // Output: {"time":1494567715,"level":"info","message":"hello world","foo":"bar"} |
| 24 | // |
| 25 | // Sub-loggers let you chain loggers with additional context: |
| 26 | // |
| 27 | // sublogger := log.With().Str("component": "foo").Logger() |
| 28 | // sublogger.Info().Msg("hello world") |
| 29 | // // Output: {"time":1494567715,"level":"info","message":"hello world","component":"foo"} |
| 30 | // |
| 31 | // Level logging |
| 32 | // |
| 33 | // zerolog.SetGlobalLevel(zerolog.InfoLevel) |
| 34 | // |
| 35 | // log.Debug().Msg("filtered out message") |
| 36 | // log.Info().Msg("routed message") |
| 37 | // |
| 38 | // if e := log.Debug(); e.Enabled() { |
| 39 | // // Compute log output only if enabled. |
| 40 | // value := compute() |
| 41 | // e.Str("foo": value).Msg("some debug message") |
| 42 | // } |
| 43 | // // Output: {"level":"info","time":1494567715,"routed message"} |
| 44 | // |
| 45 | // Customize automatic field names: |
| 46 | // |
| 47 | // log.TimestampFieldName = "t" |
| 48 | // log.LevelFieldName = "p" |
| 49 | // log.MessageFieldName = "m" |
| 50 | // |
| 51 | // log.Info().Msg("hello world") |
| 52 | // // Output: {"t":1494567715,"p":"info","m":"hello world"} |
| 53 | // |
| 54 | // Log with no level and message: |
| 55 | // |
| 56 | // log.Log().Str("foo","bar").Msg("") |
| 57 | // // Output: {"time":1494567715,"foo":"bar"} |
| 58 | // |
| 59 | // Add contextual fields to global Logger: |
| 60 | // |
| 61 | // log.Logger = log.With().Str("foo", "bar").Logger() |
| 62 | // |
| 63 | // Sample logs: |
| 64 | // |
| 65 | // sampled := log.Sample(&zerolog.BasicSampler{N: 10}) |
| 66 | // sampled.Info().Msg("will be logged every 10 messages") |
| 67 | // |
| 68 | // Log with contextual hooks: |
| 69 | // |
| 70 | // // Create the hook: |
| 71 | // type SeverityHook struct{} |
| 72 | // |
| 73 | // func (h SeverityHook) Run(e *zerolog.Event, level zerolog.Level, msg string) { |
| 74 | // if level != zerolog.NoLevel { |
| 75 | // e.Str("severity", level.String()) |
| 76 | // } |
| 77 | // } |
| 78 | // |
| 79 | // // And use it: |
| 80 | // var h SeverityHook |
| 81 | // log := zerolog.New(os.Stdout).Hook(h) |
| 82 | // log.Warn().Msg("") |
| 83 | // // Output: {"level":"warn","severity":"warn"} |
| 84 | // |
| 85 | // |
| 86 | // Caveats |
| 87 | // |
| 88 | // There is no fields deduplication out-of-the-box. |
| 89 | // Using the same key multiple times creates new key in final JSON each time. |
| 90 | // |
| 91 | // logger := zerolog.New(os.Stderr).With().Timestamp().Logger() |
| 92 | // logger.Info(). |
| 93 | // Timestamp(). |
| 94 | // Msg("dup") |
| 95 | // // Output: {"level":"info","time":1494567715,"time":1494567715,"message":"dup"} |
| 96 | // |
| 97 | // However, it’s not a big deal though as JSON accepts dup keys, |
| 98 | // the last one prevails. |
| 99 | package zerolog |
| 100 | |
| 101 | import ( |
| 102 | "fmt" |
| 103 | "io" |
| 104 | "io/ioutil" |
| 105 | "os" |
| 106 | "strconv" |
| 107 | ) |
| 108 | |
| 109 | // Level defines log levels. |
| 110 | type Level uint8 |
| 111 | |
| 112 | const ( |
| 113 | // DebugLevel defines debug log level. |
| 114 | DebugLevel Level = iota |
| 115 | // InfoLevel defines info log level. |
| 116 | InfoLevel |
| 117 | // WarnLevel defines warn log level. |
| 118 | WarnLevel |
| 119 | // ErrorLevel defines error log level. |
| 120 | ErrorLevel |
| 121 | // FatalLevel defines fatal log level. |
| 122 | FatalLevel |
| 123 | // PanicLevel defines panic log level. |
| 124 | PanicLevel |
| 125 | // NoLevel defines an absent log level. |
| 126 | NoLevel |
| 127 | // Disabled disables the logger. |
| 128 | Disabled |
| 129 | ) |
| 130 | |
| 131 | func (l Level) String() string { |
| 132 | switch l { |
| 133 | case DebugLevel: |
| 134 | return "debug" |
| 135 | case InfoLevel: |
| 136 | return "info" |
| 137 | case WarnLevel: |
| 138 | return "warn" |
| 139 | case ErrorLevel: |
| 140 | return "error" |
| 141 | case FatalLevel: |
| 142 | return "fatal" |
| 143 | case PanicLevel: |
| 144 | return "panic" |
| 145 | case NoLevel: |
| 146 | return "" |
| 147 | } |
| 148 | return "" |
| 149 | } |
| 150 | |
| 151 | // ParseLevel converts a level string into a zerolog Level value. |
| 152 | // returns an error if the input string does not match known values. |
| 153 | func ParseLevel(levelStr string) (Level, error) { |
| 154 | switch levelStr { |
| 155 | case DebugLevel.String(): |
| 156 | return DebugLevel, nil |
| 157 | case InfoLevel.String(): |
| 158 | return InfoLevel, nil |
| 159 | case WarnLevel.String(): |
| 160 | return WarnLevel, nil |
| 161 | case ErrorLevel.String(): |
| 162 | return ErrorLevel, nil |
| 163 | case FatalLevel.String(): |
| 164 | return FatalLevel, nil |
| 165 | case PanicLevel.String(): |
| 166 | return PanicLevel, nil |
| 167 | case NoLevel.String(): |
| 168 | return NoLevel, nil |
| 169 | } |
| 170 | return NoLevel, fmt.Errorf("Unknown Level String: '%s', defaulting to NoLevel", levelStr) |
| 171 | } |
| 172 | |
| 173 | // A Logger represents an active logging object that generates lines |
| 174 | // of JSON output to an io.Writer. Each logging operation makes a single |
| 175 | // call to the Writer's Write method. There is no guaranty on access |
| 176 | // serialization to the Writer. If your Writer is not thread safe, |
| 177 | // you may consider a sync wrapper. |
| 178 | type Logger struct { |
| 179 | w LevelWriter |
| 180 | level Level |
| 181 | sampler Sampler |
| 182 | context []byte |
| 183 | hooks []Hook |
| 184 | } |
| 185 | |
| 186 | // New creates a root logger with given output writer. If the output writer implements |
| 187 | // the LevelWriter interface, the WriteLevel method will be called instead of the Write |
| 188 | // one. |
| 189 | // |
| 190 | // Each logging operation makes a single call to the Writer's Write method. There is no |
| 191 | // guaranty on access serialization to the Writer. If your Writer is not thread safe, |
| 192 | // you may consider using sync wrapper. |
| 193 | func New(w io.Writer) Logger { |
| 194 | if w == nil { |
| 195 | w = ioutil.Discard |
| 196 | } |
| 197 | lw, ok := w.(LevelWriter) |
| 198 | if !ok { |
| 199 | lw = levelWriterAdapter{w} |
| 200 | } |
| 201 | return Logger{w: lw} |
| 202 | } |
| 203 | |
| 204 | // Nop returns a disabled logger for which all operation are no-op. |
| 205 | func Nop() Logger { |
| 206 | return New(nil).Level(Disabled) |
| 207 | } |
| 208 | |
| 209 | // Output duplicates the current logger and sets w as its output. |
| 210 | func (l Logger) Output(w io.Writer) Logger { |
| 211 | l2 := New(w) |
| 212 | l2.level = l.level |
| 213 | l2.sampler = l.sampler |
| 214 | if len(l.hooks) > 0 { |
| 215 | l2.hooks = append(l2.hooks, l.hooks...) |
| 216 | } |
| 217 | if l.context != nil { |
| 218 | l2.context = make([]byte, len(l.context), cap(l.context)) |
| 219 | copy(l2.context, l.context) |
| 220 | } |
| 221 | return l2 |
| 222 | } |
| 223 | |
| 224 | // With creates a child logger with the field added to its context. |
| 225 | func (l Logger) With() Context { |
| 226 | context := l.context |
| 227 | l.context = make([]byte, 0, 500) |
| 228 | if context != nil { |
| 229 | l.context = append(l.context, context...) |
| 230 | } |
| 231 | return Context{l} |
| 232 | } |
| 233 | |
| 234 | // UpdateContext updates the internal logger's context. |
| 235 | // |
| 236 | // Use this method with caution. If unsure, prefer the With method. |
| 237 | func (l *Logger) UpdateContext(update func(c Context) Context) { |
| 238 | if l == disabledLogger { |
| 239 | return |
| 240 | } |
| 241 | if cap(l.context) == 0 { |
| 242 | l.context = make([]byte, 0, 500) |
| 243 | } |
| 244 | c := update(Context{*l}) |
| 245 | l.context = c.l.context |
| 246 | } |
| 247 | |
| 248 | // Level creates a child logger with the minimum accepted level set to level. |
| 249 | func (l Logger) Level(lvl Level) Logger { |
| 250 | l.level = lvl |
| 251 | return l |
| 252 | } |
| 253 | |
| 254 | // Sample returns a logger with the s sampler. |
| 255 | func (l Logger) Sample(s Sampler) Logger { |
| 256 | l.sampler = s |
| 257 | return l |
| 258 | } |
| 259 | |
| 260 | // Hook returns a logger with the h Hook. |
| 261 | func (l Logger) Hook(h Hook) Logger { |
| 262 | l.hooks = append(l.hooks, h) |
| 263 | return l |
| 264 | } |
| 265 | |
| 266 | // Debug starts a new message with debug level. |
| 267 | // |
| 268 | // You must call Msg on the returned event in order to send the event. |
| 269 | func (l *Logger) Debug() *Event { |
| 270 | return l.newEvent(DebugLevel, nil) |
| 271 | } |
| 272 | |
| 273 | // Info starts a new message with info level. |
| 274 | // |
| 275 | // You must call Msg on the returned event in order to send the event. |
| 276 | func (l *Logger) Info() *Event { |
| 277 | return l.newEvent(InfoLevel, nil) |
| 278 | } |
| 279 | |
| 280 | // Warn starts a new message with warn level. |
| 281 | // |
| 282 | // You must call Msg on the returned event in order to send the event. |
| 283 | func (l *Logger) Warn() *Event { |
| 284 | return l.newEvent(WarnLevel, nil) |
| 285 | } |
| 286 | |
| 287 | // Error starts a new message with error level. |
| 288 | // |
| 289 | // You must call Msg on the returned event in order to send the event. |
| 290 | func (l *Logger) Error() *Event { |
| 291 | return l.newEvent(ErrorLevel, nil) |
| 292 | } |
| 293 | |
| 294 | // Fatal starts a new message with fatal level. The os.Exit(1) function |
| 295 | // is called by the Msg method, which terminates the program immediately. |
| 296 | // |
| 297 | // You must call Msg on the returned event in order to send the event. |
| 298 | func (l *Logger) Fatal() *Event { |
| 299 | return l.newEvent(FatalLevel, func(msg string) { os.Exit(1) }) |
| 300 | } |
| 301 | |
| 302 | // Panic starts a new message with panic level. The panic() function |
| 303 | // is called by the Msg method, which stops the ordinary flow of a goroutine. |
| 304 | // |
| 305 | // You must call Msg on the returned event in order to send the event. |
| 306 | func (l *Logger) Panic() *Event { |
| 307 | return l.newEvent(PanicLevel, func(msg string) { panic(msg) }) |
| 308 | } |
| 309 | |
| 310 | // WithLevel starts a new message with level. Unlike Fatal and Panic |
| 311 | // methods, WithLevel does not terminate the program or stop the ordinary |
| 312 | // flow of a gourotine when used with their respective levels. |
| 313 | // |
| 314 | // You must call Msg on the returned event in order to send the event. |
| 315 | func (l *Logger) WithLevel(level Level) *Event { |
| 316 | switch level { |
| 317 | case DebugLevel: |
| 318 | return l.Debug() |
| 319 | case InfoLevel: |
| 320 | return l.Info() |
| 321 | case WarnLevel: |
| 322 | return l.Warn() |
| 323 | case ErrorLevel: |
| 324 | return l.Error() |
| 325 | case FatalLevel: |
| 326 | return l.newEvent(FatalLevel, nil) |
| 327 | case PanicLevel: |
| 328 | return l.newEvent(PanicLevel, nil) |
| 329 | case NoLevel: |
| 330 | return l.Log() |
| 331 | case Disabled: |
| 332 | return nil |
| 333 | default: |
| 334 | panic("zerolog: WithLevel(): invalid level: " + strconv.Itoa(int(level))) |
| 335 | } |
| 336 | } |
| 337 | |
| 338 | // Log starts a new message with no level. Setting GlobalLevel to Disabled |
| 339 | // will still disable events produced by this method. |
| 340 | // |
| 341 | // You must call Msg on the returned event in order to send the event. |
| 342 | func (l *Logger) Log() *Event { |
| 343 | return l.newEvent(NoLevel, nil) |
| 344 | } |
| 345 | |
| 346 | // Print sends a log event using debug level and no extra field. |
| 347 | // Arguments are handled in the manner of fmt.Print. |
| 348 | func (l *Logger) Print(v ...interface{}) { |
| 349 | if e := l.Debug(); e.Enabled() { |
| 350 | e.Msg(fmt.Sprint(v...)) |
| 351 | } |
| 352 | } |
| 353 | |
| 354 | // Printf sends a log event using debug level and no extra field. |
| 355 | // Arguments are handled in the manner of fmt.Printf. |
| 356 | func (l *Logger) Printf(format string, v ...interface{}) { |
| 357 | if e := l.Debug(); e.Enabled() { |
| 358 | e.Msg(fmt.Sprintf(format, v...)) |
| 359 | } |
| 360 | } |
| 361 | |
| 362 | // Write implements the io.Writer interface. This is useful to set as a writer |
| 363 | // for the standard library log. |
| 364 | func (l Logger) Write(p []byte) (n int, err error) { |
| 365 | n = len(p) |
| 366 | if n > 0 && p[n-1] == '\n' { |
| 367 | // Trim CR added by stdlog. |
| 368 | p = p[0 : n-1] |
| 369 | } |
| 370 | l.Log().Msg(string(p)) |
| 371 | return |
| 372 | } |
| 373 | |
| 374 | func (l *Logger) newEvent(level Level, done func(string)) *Event { |
| 375 | enabled := l.should(level) |
| 376 | if !enabled { |
| 377 | return nil |
| 378 | } |
| 379 | e := newEvent(l.w, level) |
| 380 | e.done = done |
| 381 | e.ch = l.hooks |
| 382 | if level != NoLevel { |
| 383 | e.Str(LevelFieldName, level.String()) |
| 384 | } |
| 385 | if l.context != nil && len(l.context) > 0 { |
| 386 | e.buf = enc.AppendObjectData(e.buf, l.context) |
| 387 | } |
| 388 | return e |
| 389 | } |
| 390 | |
| 391 | // should returns true if the log event should be logged. |
| 392 | func (l *Logger) should(lvl Level) bool { |
| 393 | if lvl < l.level || lvl < GlobalLevel() { |
| 394 | return false |
| 395 | } |
| 396 | if l.sampler != nil && !samplingDisabled() { |
| 397 | return l.sampler.Sample(lvl) |
| 398 | } |
| 399 | return true |
| 400 | } |