399 lines
10 KiB
Go
399 lines
10 KiB
Go
// 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.
|
||
//
|
||
// 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 message is also sent
|
||
// to the panic function.
|
||
//
|
||
// 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.
|
||
//
|
||
// 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.Fatal()
|
||
case PanicLevel:
|
||
return l.Panic()
|
||
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
|
||
}
|