logp/logger.go (173 lines of code) (raw):
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package logp
import (
"bytes"
"fmt"
"io"
"go.elastic.co/ecszap"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
// LogOption configures a Logger.
type LogOption = zap.Option
// Logger logs messages to the configured output.
type Logger struct {
logger *zap.Logger
sugar *zap.SugaredLogger
}
func newLogger(rootLogger *zap.Logger, selector string, options ...LogOption) *Logger {
log := rootLogger.
WithOptions(zap.AddCallerSkip(1)).
WithOptions(options...).
Named(selector)
return &Logger{log, log.Sugar()}
}
// NewLogger returns a new Logger labeled with the name of the selector. This
// should never be used from any global contexts, otherwise you will receive a
// no-op Logger. This is because the logp package needs to be initialized first.
// Instead create new Logger instance that your object reuses. Or if you need to
// log from a static context then you may use logp.L().Infow(), for example.
func NewLogger(selector string, options ...LogOption) *Logger {
return newLogger(loadLogger().rootLogger, selector, options...)
}
// NewProductionLogger returns a production suitable logp.Logger
func NewProductionLogger(selector string, options ...LogOption) (*Logger, error) {
log, err := zap.NewProduction(options...)
log = log.Named(selector)
if err != nil {
return nil, err
}
return &Logger{log, log.Sugar()}, nil
}
// NewDevelopmentLogger returns a development suitable logp.Logger
func NewDevelopmentLogger(selector string, options ...LogOption) (*Logger, error) {
log, err := zap.NewDevelopment(options...)
log = log.Named(selector)
if err != nil {
return nil, err
}
return &Logger{log, log.Sugar()}, nil
}
// NewInMemory returns a new in-memory logger along with the buffer to which it
// logs. It's goroutine safe, but operating directly on the returned buffer is not.
// This logger is primary intended for short and simple use-cases such as printing
// the full logs only when an operation fails.
// encCfg configures the log format, use logp.ConsoleEncoderConfig for console
// format, logp.JSONEncoderConfig for JSON or any other valid zapcore.EncoderConfig.
//
// Deprecated: Prefer using localized loggers. Use logp.NewInMemoryLocal.
func NewInMemory(selector string, encCfg zapcore.EncoderConfig) (*Logger, *bytes.Buffer) {
buff := bytes.Buffer{}
encoderConfig := ecszap.ECSCompatibleEncoderConfig(encCfg)
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
encoder := zapcore.NewConsoleEncoder(encoderConfig)
core := zapcore.NewCore(
encoder,
zapcore.Lock(zapcore.AddSync(&buff)),
zap.NewAtomicLevelAt(zap.DebugLevel))
ecszap.ECSCompatibleEncoderConfig(ConsoleEncoderConfig())
logger := NewLogger(
selector,
zap.WrapCore(func(in zapcore.Core) zapcore.Core {
return core
}))
return logger, &buff
}
// NewInMemory returns a new in-memory logger along with the buffer to which it
// logs. It's goroutine safe, but operating directly on the returned buffer is not.
// This logger is primary intended for short and simple use-cases such as printing
// the full logs only when an operation fails.
// encCfg configures the log format, use logp.ConsoleEncoderConfig for console
// format, logp.JSONEncoderConfig for JSON or any other valid zapcore.EncoderConfig.
func NewInMemoryLocal(selector string, encCfg zapcore.EncoderConfig) (*Logger, *bytes.Buffer) {
buff := bytes.Buffer{}
encoderConfig := ecszap.ECSCompatibleEncoderConfig(encCfg)
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
encoder := zapcore.NewConsoleEncoder(encoderConfig)
core := zapcore.NewCore(
encoder,
zapcore.Lock(zapcore.AddSync(&buff)),
zap.NewAtomicLevelAt(zap.DebugLevel))
ecszap.ECSCompatibleEncoderConfig(ConsoleEncoderConfig())
logger, _ := NewDevelopmentLogger(
selector,
zap.WrapCore(func(in zapcore.Core) zapcore.Core {
return core
}))
return logger, &buff
}
// WithOptions returns a clone of l with options applied.
func (l *Logger) WithOptions(options ...LogOption) *Logger {
cloned := l.logger.WithOptions(options...)
return &Logger{cloned, cloned.Sugar()}
}
// With creates a child logger and adds structured context to it. Fields added
// to the child don't affect the parent, and vice versa.
func (l *Logger) With(args ...interface{}) *Logger {
sugar := l.sugar.With(args...)
return &Logger{sugar.Desugar(), sugar}
}
// Named adds a new path segment to the logger's name. Segments are joined by
// periods.
func (l *Logger) Named(name string) *Logger {
logger := l.logger.Named(name)
return &Logger{logger, logger.Sugar()}
}
// Sprint
// Debug uses fmt.Sprint to construct and log a message.
func (l *Logger) Debug(args ...interface{}) {
l.sugar.Debug(args...)
}
// Info uses fmt.Sprint to construct and log a message.
func (l *Logger) Info(args ...interface{}) {
l.sugar.Info(args...)
}
// Warn uses fmt.Sprint to construct and log a message.
func (l *Logger) Warn(args ...interface{}) {
l.sugar.Warn(args...)
}
// Error uses fmt.Sprint to construct and log a message.
func (l *Logger) Error(args ...interface{}) {
l.sugar.Error(args...)
}
// Fatal uses fmt.Sprint to construct and log a message, then calls os.Exit(1).
func (l *Logger) Fatal(args ...interface{}) {
l.sugar.Fatal(args...)
}
// Panic uses fmt.Sprint to construct and log a message, then panics.
func (l *Logger) Panic(args ...interface{}) {
l.sugar.Panic(args...)
}
// DPanic uses fmt.Sprint to construct and log a message. In development, the
// logger then panics.
func (l *Logger) DPanic(args ...interface{}) {
l.sugar.DPanic(args...)
}
// IsDebug checks to see if the given logger is Debug enabled.
func (l *Logger) IsDebug() bool {
return l.logger.Check(zapcore.DebugLevel, "") != nil
}
// Sprintf
// Debugf uses fmt.Sprintf to construct and log a message.
func (l *Logger) Debugf(format string, args ...interface{}) {
l.sugar.Debugf(format, args...)
}
// Infof uses fmt.Sprintf to log a templated message.
func (l *Logger) Infof(format string, args ...interface{}) {
l.sugar.Infof(format, args...)
}
// Warnf uses fmt.Sprintf to log a templated message.
func (l *Logger) Warnf(format string, args ...interface{}) {
l.sugar.Warnf(format, args...)
}
// Errorf uses fmt.Sprintf to log a templated message.
func (l *Logger) Errorf(format string, args ...interface{}) {
l.sugar.Errorf(format, args...)
}
// Fatalf uses fmt.Sprintf to log a templated message, then calls os.Exit(1).
func (l *Logger) Fatalf(format string, args ...interface{}) {
l.sugar.Fatalf(format, args...)
}
// Panicf uses fmt.Sprintf to log a templated message, then panics.
func (l *Logger) Panicf(format string, args ...interface{}) {
l.sugar.Panicf(format, args...)
}
// DPanicf uses fmt.Sprintf to log a templated message. In development, the
// logger then panics.
func (l *Logger) DPanicf(format string, args ...interface{}) {
l.sugar.DPanicf(format, args...)
}
// With context (reflection based)
// Debugw logs a message with some additional context. The additional context
// is added in the form of key-value pairs. The optimal way to write the value
// to the log message will be inferred by the value's type. To explicitly
// specify a type you can pass a Field such as logp.Stringer.
func (l *Logger) Debugw(msg string, keysAndValues ...interface{}) {
l.sugar.Debugw(msg, keysAndValues...)
}
// Infow logs a message with some additional context. The additional context
// is added in the form of key-value pairs. The optimal way to write the value
// to the log message will be inferred by the value's type. To explicitly
// specify a type you can pass a Field such as logp.Stringer.
func (l *Logger) Infow(msg string, keysAndValues ...interface{}) {
l.sugar.Infow(msg, keysAndValues...)
}
// Warnw logs a message with some additional context. The additional context
// is added in the form of key-value pairs. The optimal way to write the value
// to the log message will be inferred by the value's type. To explicitly
// specify a type you can pass a Field such as logp.Stringer.
func (l *Logger) Warnw(msg string, keysAndValues ...interface{}) {
l.sugar.Warnw(msg, keysAndValues...)
}
// Errorw logs a message with some additional context. The additional context
// is added in the form of key-value pairs. The optimal way to write the value
// to the log message will be inferred by the value's type. To explicitly
// specify a type you can pass a Field such as logp.Stringer.
func (l *Logger) Errorw(msg string, keysAndValues ...interface{}) {
l.sugar.Errorw(msg, keysAndValues...)
}
// Fatalw logs a message with some additional context, then calls os.Exit(1).
// The additional context is added in the form of key-value pairs. The optimal
// way to write the value to the log message will be inferred by the value's
// type. To explicitly specify a type you can pass a Field such as
// logp.Stringer.
func (l *Logger) Fatalw(msg string, keysAndValues ...interface{}) {
l.sugar.Fatalw(msg, keysAndValues...)
}
// Panicw logs a message with some additional context, then panics. The
// additional context is added in the form of key-value pairs. The optimal way
// to write the value to the log message will be inferred by the value's type.
// To explicitly specify a type you can pass a Field such as logp.Stringer.
func (l *Logger) Panicw(msg string, keysAndValues ...interface{}) {
l.sugar.Panicw(msg, keysAndValues...)
}
// DPanicw logs a message with some additional context. The logger panics only
// in Development mode. The additional context is added in the form of
// key-value pairs. The optimal way to write the value to the log message will
// be inferred by the value's type. To explicitly specify a type you can pass a
// Field such as logp.Stringer.
func (l *Logger) DPanicw(msg string, keysAndValues ...interface{}) {
l.sugar.DPanicw(msg, keysAndValues...)
}
// Recover stops a panicking goroutine and logs an Error.
func (l *Logger) Recover(msg string) {
if r := recover(); r != nil {
msg := fmt.Sprintf("%s. Recovering, but please report this.", msg)
l.Error(msg, zap.Any("panic", r), zap.Stack("stack"))
}
}
// Sync syncs the logger.
func (l *Logger) Sync() error {
return l.logger.Sync()
}
// Core returns the backend zapcore.Core for the logger.
func (l *Logger) Core() zapcore.Core {
return l.logger.Core()
}
// Close closes the underlying logger core/writer.
func (l *Logger) Close() error {
if closer, ok := l.logger.Core().(io.Closer); ok {
return closer.Close()
}
return nil
}
// L returns an unnamed global logger.
func L() *Logger {
return loadLogger().logger
}