| // Package zerolog provides a lightweight logging library dedicated to JSON logging. |
| // |
| // A global Logger can be use for simple logging: |
| // |
| // import "github.com/rs/zerolog/log" |
| // |
| // log.Info().Msg("hello world") |
| // // Output: {"time":1494567715,"level":"info","message":"hello world"} |
| // |
| // NOTE: To import the global logger, import the "log" subpackage "github.com/rs/zerolog/log". |
| // |
| // Fields can be added to log messages: |
| // |
| // log.Info().Str("foo", "bar").Msg("hello world") |
| // // Output: {"time":1494567715,"level":"info","message":"hello world","foo":"bar"} |
| // |
| // Create logger instance to manage different outputs: |
| // |
| // logger := zerolog.New(os.Stderr).With().Timestamp().Logger() |
| // logger.Info(). |
| // Str("foo", "bar"). |
| // Msg("hello world") |
| // // Output: {"time":1494567715,"level":"info","message":"hello world","foo":"bar"} |
| // |
| // Sub-loggers let you chain loggers with additional context: |
| // |
| // sublogger := log.With().Str("component": "foo").Logger() |
| // sublogger.Info().Msg("hello world") |
| // // Output: {"time":1494567715,"level":"info","message":"hello world","component":"foo"} |
| // |
| // Level logging |
| // |
| // zerolog.SetGlobalLevel(zerolog.InfoLevel) |
| // |
| // log.Debug().Msg("filtered out message") |
| // log.Info().Msg("routed message") |
| // |
| // if e := log.Debug(); e.Enabled() { |
| // // Compute log output only if enabled. |
| // value := compute() |
| // e.Str("foo": value).Msg("some debug message") |
| // } |
| // // Output: {"level":"info","time":1494567715,"routed message"} |
| // |
| // Customize automatic field names: |
| // |
| // log.TimestampFieldName = "t" |
| // log.LevelFieldName = "p" |
| // log.MessageFieldName = "m" |
| // |
| // log.Info().Msg("hello world") |
| // // Output: {"t":1494567715,"p":"info","m":"hello world"} |
| // |
| // Log with no level and message: |
| // |
| // log.Log().Str("foo","bar").Msg("") |
| // // Output: {"time":1494567715,"foo":"bar"} |
| // |
| // Add contextual fields to global Logger: |
| // |
| // log.Logger = log.With().Str("foo", "bar").Logger() |
| // |
| // Sample logs: |
| // |
| // sampled := log.Sample(&zerolog.BasicSampler{N: 10}) |
| // sampled.Info().Msg("will be logged every 10 messages") |
| // |
| // Log with contextual hooks: |
| // |
| // // Create the hook: |
| // type SeverityHook struct{} |
| // |
| // func (h SeverityHook) Run(e *zerolog.Event, level zerolog.Level, msg string) { |
| // if level != zerolog.NoLevel { |
| // e.Str("severity", level.String()) |
| // } |
| // } |
| // |
| // // And use it: |
| // var h SeverityHook |
| // log := zerolog.New(os.Stdout).Hook(h) |
| // log.Warn().Msg("") |
| // // Output: {"level":"warn","severity":"warn"} |
| // |
| // |
| // Caveats |
| // |
| // There is no fields deduplication out-of-the-box. |
| // Using the same key multiple times creates new key in final JSON each time. |
| // |
| // logger := zerolog.New(os.Stderr).With().Timestamp().Logger() |
| // logger.Info(). |
| // Timestamp(). |
| // Msg("dup") |
| // // Output: {"level":"info","time":1494567715,"time":1494567715,"message":"dup"} |
| // |
| // However, it’s not a big deal though as JSON accepts dup keys, |
| // the last one prevails. |
| package zerolog |
| |
| import ( |
| "fmt" |
| "io" |
| "io/ioutil" |
| "os" |
| "strconv" |
| ) |
| |
| // Level defines log levels. |
| type Level uint8 |
| |
| const ( |
| // DebugLevel defines debug log level. |
| DebugLevel Level = iota |
| // InfoLevel defines info log level. |
| InfoLevel |
| // WarnLevel defines warn log level. |
| WarnLevel |
| // ErrorLevel defines error log level. |
| ErrorLevel |
| // FatalLevel defines fatal log level. |
| FatalLevel |
| // PanicLevel defines panic log level. |
| PanicLevel |
| // NoLevel defines an absent log level. |
| NoLevel |
| // Disabled disables the logger. |
| Disabled |
| ) |
| |
| func (l Level) String() string { |
| switch l { |
| case DebugLevel: |
| return "debug" |
| case InfoLevel: |
| return "info" |
| case WarnLevel: |
| return "warn" |
| case ErrorLevel: |
| return "error" |
| case FatalLevel: |
| return "fatal" |
| case PanicLevel: |
| return "panic" |
| case NoLevel: |
| return "" |
| } |
| return "" |
| } |
| |
| // ParseLevel converts a level string into a zerolog Level value. |
| // returns an error if the input string does not match known values. |
| func ParseLevel(levelStr string) (Level, error) { |
| switch levelStr { |
| case DebugLevel.String(): |
| return DebugLevel, nil |
| case InfoLevel.String(): |
| return InfoLevel, nil |
| case WarnLevel.String(): |
| return WarnLevel, nil |
| case ErrorLevel.String(): |
| return ErrorLevel, nil |
| case FatalLevel.String(): |
| return FatalLevel, nil |
| case PanicLevel.String(): |
| return PanicLevel, nil |
| case NoLevel.String(): |
| return NoLevel, nil |
| } |
| return NoLevel, fmt.Errorf("Unknown Level String: '%s', defaulting to NoLevel", levelStr) |
| } |
| |
| // A Logger represents an active logging object that generates lines |
| // of JSON output to an io.Writer. Each logging operation makes a single |
| // call to the Writer's Write method. There is no guaranty on access |
| // serialization to the Writer. If your Writer is not thread safe, |
| // you may consider a sync wrapper. |
| type Logger struct { |
| w LevelWriter |
| level Level |
| sampler Sampler |
| context []byte |
| hooks []Hook |
| } |
| |
| // New creates a root logger with given output writer. If the output writer implements |
| // the LevelWriter interface, the WriteLevel method will be called instead of the Write |
| // one. |
| // |
| // Each logging operation makes a single call to the Writer's Write method. There is no |
| // guaranty on access serialization to the Writer. If your Writer is not thread safe, |
| // you may consider using sync wrapper. |
| func New(w io.Writer) Logger { |
| if w == nil { |
| w = ioutil.Discard |
| } |
| lw, ok := w.(LevelWriter) |
| if !ok { |
| lw = levelWriterAdapter{w} |
| } |
| return Logger{w: lw} |
| } |
| |
| // Nop returns a disabled logger for which all operation are no-op. |
| func Nop() Logger { |
| return New(nil).Level(Disabled) |
| } |
| |
| // Output duplicates the current logger and sets w as its output. |
| func (l Logger) Output(w io.Writer) Logger { |
| l2 := New(w) |
| l2.level = l.level |
| l2.sampler = l.sampler |
| if len(l.hooks) > 0 { |
| l2.hooks = append(l2.hooks, l.hooks...) |
| } |
| if l.context != nil { |
| l2.context = make([]byte, len(l.context), cap(l.context)) |
| copy(l2.context, l.context) |
| } |
| return l2 |
| } |
| |
| // With creates a child logger with the field added to its context. |
| func (l Logger) With() Context { |
| context := l.context |
| l.context = make([]byte, 0, 500) |
| if context != nil { |
| l.context = append(l.context, context...) |
| } |
| return Context{l} |
| } |
| |
| // UpdateContext updates the internal logger's context. |
| // |
| // Use this method with caution. If unsure, prefer the With method. |
| func (l *Logger) UpdateContext(update func(c Context) Context) { |
| if l == disabledLogger { |
| return |
| } |
| if cap(l.context) == 0 { |
| l.context = make([]byte, 0, 500) |
| } |
| c := update(Context{*l}) |
| l.context = c.l.context |
| } |
| |
| // Level creates a child logger with the minimum accepted level set to level. |
| func (l Logger) Level(lvl Level) Logger { |
| l.level = lvl |
| return l |
| } |
| |
| // Sample returns a logger with the s sampler. |
| func (l Logger) Sample(s Sampler) Logger { |
| l.sampler = s |
| return l |
| } |
| |
| // Hook returns a logger with the h Hook. |
| func (l Logger) Hook(h Hook) Logger { |
| l.hooks = append(l.hooks, h) |
| return l |
| } |
| |
| // Debug starts a new message with debug level. |
| // |
| // You must call Msg on the returned event in order to send the event. |
| func (l *Logger) Debug() *Event { |
| return l.newEvent(DebugLevel, nil) |
| } |
| |
| // Info starts a new message with info level. |
| // |
| // You must call Msg on the returned event in order to send the event. |
| func (l *Logger) Info() *Event { |
| return l.newEvent(InfoLevel, nil) |
| } |
| |
| // Warn starts a new message with warn level. |
| // |
| // You must call Msg on the returned event in order to send the event. |
| func (l *Logger) Warn() *Event { |
| return l.newEvent(WarnLevel, nil) |
| } |
| |
| // Error starts a new message with error level. |
| // |
| // You must call Msg on the returned event in order to send the event. |
| func (l *Logger) Error() *Event { |
| return l.newEvent(ErrorLevel, nil) |
| } |
| |
| // Fatal starts a new message with fatal level. The os.Exit(1) function |
| // is called by the Msg method, which terminates the program immediately. |
| // |
| // You must call Msg on the returned event in order to send the event. |
| func (l *Logger) Fatal() *Event { |
| return l.newEvent(FatalLevel, func(msg string) { os.Exit(1) }) |
| } |
| |
| // Panic starts a new message with panic level. The panic() function |
| // is called by the Msg method, which stops the ordinary flow of a goroutine. |
| // |
| // You must call Msg on the returned event in order to send the event. |
| func (l *Logger) Panic() *Event { |
| return l.newEvent(PanicLevel, func(msg string) { panic(msg) }) |
| } |
| |
| // WithLevel starts a new message with level. Unlike Fatal and Panic |
| // methods, WithLevel does not terminate the program or stop the ordinary |
| // flow of a gourotine when used with their respective levels. |
| // |
| // You must call Msg on the returned event in order to send the event. |
| func (l *Logger) WithLevel(level Level) *Event { |
| switch level { |
| case DebugLevel: |
| return l.Debug() |
| case InfoLevel: |
| return l.Info() |
| case WarnLevel: |
| return l.Warn() |
| case ErrorLevel: |
| return l.Error() |
| case FatalLevel: |
| return l.newEvent(FatalLevel, nil) |
| case PanicLevel: |
| return l.newEvent(PanicLevel, nil) |
| case NoLevel: |
| return l.Log() |
| case Disabled: |
| return nil |
| default: |
| panic("zerolog: WithLevel(): invalid level: " + strconv.Itoa(int(level))) |
| } |
| } |
| |
| // Log starts a new message with no level. Setting GlobalLevel to Disabled |
| // will still disable events produced by this method. |
| // |
| // You must call Msg on the returned event in order to send the event. |
| func (l *Logger) Log() *Event { |
| return l.newEvent(NoLevel, nil) |
| } |
| |
| // Print sends a log event using debug level and no extra field. |
| // Arguments are handled in the manner of fmt.Print. |
| func (l *Logger) Print(v ...interface{}) { |
| if e := l.Debug(); e.Enabled() { |
| e.Msg(fmt.Sprint(v...)) |
| } |
| } |
| |
| // Printf sends a log event using debug level and no extra field. |
| // Arguments are handled in the manner of fmt.Printf. |
| func (l *Logger) Printf(format string, v ...interface{}) { |
| if e := l.Debug(); e.Enabled() { |
| e.Msg(fmt.Sprintf(format, v...)) |
| } |
| } |
| |
| // Write implements the io.Writer interface. This is useful to set as a writer |
| // for the standard library log. |
| func (l Logger) Write(p []byte) (n int, err error) { |
| n = len(p) |
| if n > 0 && p[n-1] == '\n' { |
| // Trim CR added by stdlog. |
| p = p[0 : n-1] |
| } |
| l.Log().Msg(string(p)) |
| return |
| } |
| |
| func (l *Logger) newEvent(level Level, done func(string)) *Event { |
| enabled := l.should(level) |
| if !enabled { |
| return nil |
| } |
| e := newEvent(l.w, level) |
| e.done = done |
| e.ch = l.hooks |
| if level != NoLevel { |
| e.Str(LevelFieldName, level.String()) |
| } |
| if l.context != nil && len(l.context) > 0 { |
| e.buf = enc.AppendObjectData(e.buf, l.context) |
| } |
| return e |
| } |
| |
| // should returns true if the log event should be logged. |
| func (l *Logger) should(lvl Level) bool { |
| if lvl < l.level || lvl < GlobalLevel() { |
| return false |
| } |
| if l.sampler != nil && !samplingDisabled() { |
| return l.sampler.Sample(lvl) |
| } |
| return true |
| } |