Source File
logger.go
Belonging Package
go.uber.org/zap
// Copyright (c) 2016 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package zap
import (
)
// A Logger provides fast, leveled, structured logging. All methods are safe
// for concurrent use.
//
// The Logger is designed for contexts in which every microsecond and every
// allocation matters, so its API intentionally favors performance and type
// safety over brevity. For most applications, the SugaredLogger strikes a
// better balance between performance and ergonomics.
type Logger struct {
core zapcore.Core
development bool
addCaller bool
onFatal zapcore.CheckWriteHook // default is WriteThenFatal
name string
errorOutput zapcore.WriteSyncer
addStack zapcore.LevelEnabler
callerSkip int
clock zapcore.Clock
}
// New constructs a new Logger from the provided zapcore.Core and Options. If
// the passed zapcore.Core is nil, it falls back to using a no-op
// implementation.
//
// This is the most flexible way to construct a Logger, but also the most
// verbose. For typical use cases, the highly-opinionated presets
// (NewProduction, NewDevelopment, and NewExample) or the Config struct are
// more convenient.
//
// For sample code, see the package-level AdvancedConfiguration example.
func ( zapcore.Core, ...Option) *Logger {
if == nil {
return NewNop()
}
:= &Logger{
core: ,
errorOutput: zapcore.Lock(os.Stderr),
addStack: zapcore.FatalLevel + 1,
clock: zapcore.DefaultClock,
}
return .WithOptions(...)
}
// NewNop returns a no-op Logger. It never writes out logs or internal errors,
// and it never runs user-defined hooks.
//
// Using WithOptions to replace the Core or error output of a no-op Logger can
// re-enable logging.
func () *Logger {
return &Logger{
core: zapcore.NewNopCore(),
errorOutput: zapcore.AddSync(io.Discard),
addStack: zapcore.FatalLevel + 1,
clock: zapcore.DefaultClock,
}
}
// NewProduction builds a sensible production Logger that writes InfoLevel and
// above logs to standard error as JSON.
//
// It's a shortcut for NewProductionConfig().Build(...Option).
func ( ...Option) (*Logger, error) {
return NewProductionConfig().Build(...)
}
// NewDevelopment builds a development Logger that writes DebugLevel and above
// logs to standard error in a human-friendly format.
//
// It's a shortcut for NewDevelopmentConfig().Build(...Option).
func ( ...Option) (*Logger, error) {
return NewDevelopmentConfig().Build(...)
}
// Must is a helper that wraps a call to a function returning (*Logger, error)
// and panics if the error is non-nil. It is intended for use in variable
// initialization such as:
//
// var logger = zap.Must(zap.NewProduction())
func ( *Logger, error) *Logger {
if != nil {
panic()
}
return
}
// NewExample builds a Logger that's designed for use in zap's testable
// examples. It writes DebugLevel and above logs to standard out as JSON, but
// omits the timestamp and calling function to keep example output
// short and deterministic.
func ( ...Option) *Logger {
:= zapcore.EncoderConfig{
MessageKey: "msg",
LevelKey: "level",
NameKey: "logger",
EncodeLevel: zapcore.LowercaseLevelEncoder,
EncodeTime: zapcore.ISO8601TimeEncoder,
EncodeDuration: zapcore.StringDurationEncoder,
}
:= zapcore.NewCore(zapcore.NewJSONEncoder(), os.Stdout, DebugLevel)
return New().WithOptions(...)
}
// Sugar wraps the Logger to provide a more ergonomic, but slightly slower,
// API. Sugaring a Logger is quite inexpensive, so it's reasonable for a
// single application to use both Loggers and SugaredLoggers, converting
// between them on the boundaries of performance-sensitive code.
func ( *Logger) () *SugaredLogger {
:= .clone()
.callerSkip += 2
return &SugaredLogger{}
}
// Named adds a new path segment to the logger's name. Segments are joined by
// periods. By default, Loggers are unnamed.
func ( *Logger) ( string) *Logger {
if == "" {
return
}
:= .clone()
if .name == "" {
.name =
} else {
.name = strings.Join([]string{.name, }, ".")
}
return
}
// WithOptions clones the current Logger, applies the supplied Options, and
// returns the resulting Logger. It's safe to use concurrently.
func ( *Logger) ( ...Option) *Logger {
:= .clone()
for , := range {
.apply()
}
return
}
// With creates a child logger and adds structured context to it. Fields added
// to the child don't affect the parent, and vice versa. Any fields that
// require evaluation (such as Objects) are evaluated upon invocation of With.
func ( *Logger) ( ...Field) *Logger {
if len() == 0 {
return
}
:= .clone()
.core = .core.With()
return
}
// WithLazy creates a child logger and adds structured context to it lazily.
//
// The fields are evaluated only if the logger is further chained with [With]
// or is written to with any of the log level methods.
// Until that occurs, the logger may retain references to objects inside the fields,
// and logging will reflect the state of an object at the time of logging,
// not the time of WithLazy().
//
// WithLazy provides a worthwhile performance optimization for contextual loggers
// when the likelihood of using the child logger is low,
// such as error paths and rarely taken branches.
//
// Similar to [With], fields added to the child don't affect the parent, and vice versa.
func ( *Logger) ( ...Field) *Logger {
if len() == 0 {
return
}
return .WithOptions(WrapCore(func( zapcore.Core) zapcore.Core {
return zapcore.NewLazyWith(, )
}))
}
// Level reports the minimum enabled level for this logger.
//
// For NopLoggers, this is [zapcore.InvalidLevel].
func ( *Logger) () zapcore.Level {
return zapcore.LevelOf(.core)
}
// Check returns a CheckedEntry if logging a message at the specified level
// is enabled. It's a completely optional optimization; in high-performance
// applications, Check can help avoid allocating a slice to hold fields.
func ( *Logger) ( zapcore.Level, string) *zapcore.CheckedEntry {
return .check(, )
}
// Log logs a message at the specified level. The message includes any fields
// passed at the log site, as well as any fields accumulated on the logger.
// Any Fields that require evaluation (such as Objects) are evaluated upon
// invocation of Log.
func ( *Logger) ( zapcore.Level, string, ...Field) {
if := .check(, ); != nil {
.Write(...)
}
}
// Debug logs a message at DebugLevel. The message includes any fields passed
// at the log site, as well as any fields accumulated on the logger.
func ( *Logger) ( string, ...Field) {
if := .check(DebugLevel, ); != nil {
.Write(...)
}
}
// Info logs a message at InfoLevel. The message includes any fields passed
// at the log site, as well as any fields accumulated on the logger.
func ( *Logger) ( string, ...Field) {
if := .check(InfoLevel, ); != nil {
.Write(...)
}
}
// Warn logs a message at WarnLevel. The message includes any fields passed
// at the log site, as well as any fields accumulated on the logger.
func ( *Logger) ( string, ...Field) {
if := .check(WarnLevel, ); != nil {
.Write(...)
}
}
// Error logs a message at ErrorLevel. The message includes any fields passed
// at the log site, as well as any fields accumulated on the logger.
func ( *Logger) ( string, ...Field) {
if := .check(ErrorLevel, ); != nil {
.Write(...)
}
}
// DPanic logs a message at DPanicLevel. The message includes any fields
// passed at the log site, as well as any fields accumulated on the logger.
//
// If the logger is in development mode, it then panics (DPanic means
// "development panic"). This is useful for catching errors that are
// recoverable, but shouldn't ever happen.
func ( *Logger) ( string, ...Field) {
if := .check(DPanicLevel, ); != nil {
.Write(...)
}
}
// Panic logs a message at PanicLevel. The message includes any fields passed
// at the log site, as well as any fields accumulated on the logger.
//
// The logger then panics, even if logging at PanicLevel is disabled.
func ( *Logger) ( string, ...Field) {
if := .check(PanicLevel, ); != nil {
.Write(...)
}
}
// Fatal logs a message at FatalLevel. The message includes any fields passed
// at the log site, as well as any fields accumulated on the logger.
//
// The logger then calls os.Exit(1), even if logging at FatalLevel is
// disabled.
func ( *Logger) ( string, ...Field) {
if := .check(FatalLevel, ); != nil {
.Write(...)
}
}
// Sync calls the underlying Core's Sync method, flushing any buffered log
// entries. Applications should take care to call Sync before exiting.
func ( *Logger) () error {
return .core.Sync()
}
// Core returns the Logger's underlying zapcore.Core.
func ( *Logger) () zapcore.Core {
return .core
}
// Name returns the Logger's underlying name,
// or an empty string if the logger is unnamed.
func ( *Logger) () string {
return .name
}
func ( *Logger) () *Logger {
:= *
return &
}
func ( *Logger) ( zapcore.Level, string) *zapcore.CheckedEntry {
// Logger.check must always be called directly by a method in the
// Logger interface (e.g., Check, Info, Fatal).
// This skips Logger.check and the Info/Fatal/Check/etc. method that
// called it.
const = 2
// Check the level first to reduce the cost of disabled log calls.
// Since Panic and higher may exit, we skip the optimization for those levels.
if < zapcore.DPanicLevel && !.core.Enabled() {
return nil
}
// Create basic checked entry thru the core; this will be non-nil if the
// log message will actually be written somewhere.
:= zapcore.Entry{
LoggerName: .name,
Time: .clock.Now(),
Level: ,
Message: ,
}
:= .core.Check(, nil)
:= != nil
// Set up any required terminal behavior.
switch .Level {
case zapcore.PanicLevel:
= .After(, zapcore.WriteThenPanic)
case zapcore.FatalLevel:
:= .onFatal
// nil or WriteThenNoop will lead to continued execution after
// a Fatal log entry, which is unexpected. For example,
//
// f, err := os.Open(..)
// if err != nil {
// log.Fatal("cannot open", zap.Error(err))
// }
// fmt.Println(f.Name())
//
// The f.Name() will panic if we continue execution after the
// log.Fatal.
if == nil || == zapcore.WriteThenNoop {
= zapcore.WriteThenFatal
}
= .After(, )
case zapcore.DPanicLevel:
if .development {
= .After(, zapcore.WriteThenPanic)
}
}
// Only do further annotation if we're going to write this message; checked
// entries that exist only for terminal behavior don't benefit from
// annotation.
if ! {
return
}
// Thread the error output through to the CheckedEntry.
.ErrorOutput = .errorOutput
:= .addStack.Enabled(.Level)
if !.addCaller && ! {
return
}
// Adding the caller or stack trace requires capturing the callers of
// this function. We'll share information between these two.
:= stacktrace.First
if {
= stacktrace.Full
}
:= stacktrace.Capture(.callerSkip+, )
defer .Free()
if .Count() == 0 {
if .addCaller {
fmt.Fprintf(.errorOutput, "%v Logger.check error: failed to get caller\n", .Time.UTC())
_ = .errorOutput.Sync()
}
return
}
, := .Next()
if .addCaller {
.Caller = zapcore.EntryCaller{
Defined: .PC != 0,
PC: .PC,
File: .File,
Line: .Line,
Function: .Function,
}
}
if {
:= bufferpool.Get()
defer .Free()
:= stacktrace.NewFormatter()
// We've already extracted the first frame, so format that
// separately and defer to stackfmt for the rest.
.FormatFrame()
if {
.FormatStack()
}
.Stack = .String()
}
return
}
The pages are generated with Golds v0.6.7. (GOOS=linux GOARCH=amd64) Golds is a Go 101 project developed by Tapir Liu. PR and bug reports are welcome and can be submitted to the issue list. Please follow @Go100and1 (reachable from the left QR code) to get the latest news of Golds. |