zlog/log.go

377 lines
9.8 KiB
Go
Raw Normal View History

2017-05-12 05:24:39 +00:00
// 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)
2017-05-12 05:24:39 +00:00
//
// 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:
//
2017-09-02 03:07:47 +00:00
// sampled := log.Sample(&zerolog.BasicSampler{N: 10})
2017-05-12 05:24:39 +00:00
// sampled.Info().Msg("will be logged every 10 messages")
//
2017-12-01 17:52:37 +00:00
// 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"}
//
2017-05-12 05:24:39 +00:00
package zerolog
import (
2017-09-02 02:56:35 +00:00
"fmt"
2017-05-12 05:24:39 +00:00
"io"
2017-05-19 16:56:31 +00:00
"io/ioutil"
2017-05-12 05:24:39 +00:00
"os"
"strconv"
2017-07-26 03:51:49 +00:00
"github.com/rs/zerolog/internal/json"
2017-05-12 05:24:39 +00:00
)
// 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
2017-12-01 17:52:37 +00:00
// NoLevel defines an absent log level.
NoLevel
2017-05-12 05:24:39 +00:00
// Disabled disables the logger.
Disabled
)
func (l Level) String() string {
switch l {
case DebugLevel:
return "debug"
case InfoLevel:
return "info"
case WarnLevel:
return "warn"
2017-05-12 05:24:39 +00:00
case ErrorLevel:
return "error"
case FatalLevel:
return "fatal"
case PanicLevel:
return "panic"
2017-12-01 17:52:37 +00:00
case NoLevel:
return ""
2017-05-12 05:24:39 +00:00
}
return ""
}
// 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
2017-08-29 01:52:15 +00:00
sampler Sampler
context []byte
2017-12-01 17:52:37 +00:00
hooks []Hook
2017-05-12 05:24:39 +00:00
}
// 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.
2017-05-12 05:24:39 +00:00
func New(w io.Writer) Logger {
if w == nil {
w = ioutil.Discard
2017-05-12 05:24:39 +00:00
}
lw, ok := w.(LevelWriter)
if !ok {
lw = levelWriterAdapter{w}
}
return Logger{w: lw}
2017-05-12 05:24:39 +00:00
}
2017-06-02 07:24:52 +00:00
// 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
2017-08-29 01:52:15 +00:00
l2.sampler = l.sampler
2017-12-01 17:52:37 +00:00
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
}
2017-05-12 05:24:39 +00:00
// 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...)
} else {
// first byte of context is presence of timestamp or not
l.context = append(l.context, 0)
}
2017-05-12 05:24:39 +00:00
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, 1, 500) // first byte is timestamp flag
}
c := update(Context{*l})
l.context = c.l.context
}
2017-06-05 19:59:25 +00:00
// Level creates a child logger with the minimum accepted level set to level.
2017-05-12 05:24:39 +00:00
func (l Logger) Level(lvl Level) Logger {
2017-09-03 18:01:28 +00:00
l.level = lvl
return l
2017-05-12 05:24:39 +00:00
}
2017-08-29 01:52:15 +00:00
// Sample returns a logger with the s sampler.
func (l Logger) Sample(s Sampler) Logger {
2017-09-03 18:01:28 +00:00
l.sampler = s
return l
2017-05-12 05:24:39 +00:00
}
2017-12-01 17:52:37 +00:00
// Hook returns a logger with the h Hook.
func (l Logger) Hook(h Hook) Logger {
l.hooks = append(l.hooks, h)
return l
}
2017-05-12 05:24:39 +00:00
// 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 {
2017-12-01 17:52:37 +00:00
return l.newEvent(DebugLevel, nil)
2017-05-12 05:24:39 +00:00
}
// 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 {
2017-12-01 17:52:37 +00:00
return l.newEvent(InfoLevel, nil)
2017-05-12 05:24:39 +00:00
}
// 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 {
2017-12-01 17:52:37 +00:00
return l.newEvent(WarnLevel, nil)
2017-05-12 05:24:39 +00:00
}
// 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 {
2017-12-01 17:52:37 +00:00
return l.newEvent(ErrorLevel, nil)
2017-05-12 05:24:39 +00:00
}
// 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 {
2017-12-01 17:52:37 +00:00
return l.newEvent(FatalLevel, func(msg string) { os.Exit(1) })
2017-05-12 05:24:39 +00:00
}
// 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 {
2017-12-01 17:52:37 +00:00
return l.newEvent(PanicLevel, func(msg string) { panic(msg) })
2017-05-12 05:24:39 +00:00
}
// 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()
2017-12-01 17:52:37 +00:00
case NoLevel:
return l.Log()
case Disabled:
return nil
default:
panic("zerolog: WithLevel(): invalid level: " + strconv.Itoa(int(level)))
}
}
2017-05-12 05:24:39 +00:00
// 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 {
2017-12-01 17:52:37 +00:00
return l.newEvent(NoLevel, nil)
2017-05-12 05:24:39 +00:00
}
2017-09-02 02:56:35 +00:00
// 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{}) {
2017-09-02 02:56:35 +00:00
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{}) {
2017-09-02 02:56:35 +00:00
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
}
2017-12-01 17:52:37 +00:00
func (l *Logger) newEvent(level Level, done func(string)) *Event {
2017-05-19 16:56:31 +00:00
enabled := l.should(level)
if !enabled {
return nil
2017-05-19 16:56:31 +00:00
}
2017-12-01 17:52:37 +00:00
e := newEvent(l.w, level, true)
e.done = done
if l.context != nil && len(l.context) > 0 && l.context[0] > 0 {
// first byte of context is ts flag
2017-07-26 03:51:49 +00:00
e.buf = json.AppendTime(json.AppendKey(e.buf, TimestampFieldName), TimestampFunc(), TimeFieldFormat)
}
2017-12-01 17:52:37 +00:00
if level != NoLevel {
2017-05-12 05:24:39 +00:00
e.Str(LevelFieldName, level.String())
}
if l.context != nil && len(l.context) > 1 {
if len(e.buf) > 1 {
e.buf = append(e.buf, ',')
}
e.buf = append(e.buf, l.context[1:]...)
}
2017-12-01 17:52:37 +00:00
if len(l.hooks) > 0 {
e.h = append(e.h, l.hooks...)
}
2017-05-12 05:24:39 +00:00
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() {
2017-05-12 05:24:39 +00:00
return false
}
2017-08-29 01:52:15 +00:00
if l.sampler != nil && !samplingDisabled() {
return l.sampler.Sample(lvl)
2017-05-12 05:24:39 +00:00
}
return true
}