...

Source file src/go.uber.org/zap/zapcore/entry.go

Documentation: go.uber.org/zap/zapcore

     1  // Copyright (c) 2016 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package zapcore
    22  
    23  import (
    24  	"fmt"
    25  	"runtime"
    26  	"strings"
    27  	"time"
    28  
    29  	"go.uber.org/multierr"
    30  	"go.uber.org/zap/internal/bufferpool"
    31  	"go.uber.org/zap/internal/exit"
    32  	"go.uber.org/zap/internal/pool"
    33  )
    34  
    35  var _cePool = pool.New(func() *CheckedEntry {
    36  	// Pre-allocate some space for cores.
    37  	return &CheckedEntry{
    38  		cores: make([]Core, 4),
    39  	}
    40  })
    41  
    42  func getCheckedEntry() *CheckedEntry {
    43  	ce := _cePool.Get()
    44  	ce.reset()
    45  	return ce
    46  }
    47  
    48  func putCheckedEntry(ce *CheckedEntry) {
    49  	if ce == nil {
    50  		return
    51  	}
    52  	_cePool.Put(ce)
    53  }
    54  
    55  // NewEntryCaller makes an EntryCaller from the return signature of
    56  // runtime.Caller.
    57  func NewEntryCaller(pc uintptr, file string, line int, ok bool) EntryCaller {
    58  	if !ok {
    59  		return EntryCaller{}
    60  	}
    61  	return EntryCaller{
    62  		PC:      pc,
    63  		File:    file,
    64  		Line:    line,
    65  		Defined: true,
    66  	}
    67  }
    68  
    69  // EntryCaller represents the caller of a logging function.
    70  type EntryCaller struct {
    71  	Defined  bool
    72  	PC       uintptr
    73  	File     string
    74  	Line     int
    75  	Function string
    76  }
    77  
    78  // String returns the full path and line number of the caller.
    79  func (ec EntryCaller) String() string {
    80  	return ec.FullPath()
    81  }
    82  
    83  // FullPath returns a /full/path/to/package/file:line description of the
    84  // caller.
    85  func (ec EntryCaller) FullPath() string {
    86  	if !ec.Defined {
    87  		return "undefined"
    88  	}
    89  	buf := bufferpool.Get()
    90  	buf.AppendString(ec.File)
    91  	buf.AppendByte(':')
    92  	buf.AppendInt(int64(ec.Line))
    93  	caller := buf.String()
    94  	buf.Free()
    95  	return caller
    96  }
    97  
    98  // TrimmedPath returns a package/file:line description of the caller,
    99  // preserving only the leaf directory name and file name.
   100  func (ec EntryCaller) TrimmedPath() string {
   101  	if !ec.Defined {
   102  		return "undefined"
   103  	}
   104  	// nb. To make sure we trim the path correctly on Windows too, we
   105  	// counter-intuitively need to use '/' and *not* os.PathSeparator here,
   106  	// because the path given originates from Go stdlib, specifically
   107  	// runtime.Caller() which (as of Mar/17) returns forward slashes even on
   108  	// Windows.
   109  	//
   110  	// See https://github.com/golang/go/issues/3335
   111  	// and https://github.com/golang/go/issues/18151
   112  	//
   113  	// for discussion on the issue on Go side.
   114  	//
   115  	// Find the last separator.
   116  	//
   117  	idx := strings.LastIndexByte(ec.File, '/')
   118  	if idx == -1 {
   119  		return ec.FullPath()
   120  	}
   121  	// Find the penultimate separator.
   122  	idx = strings.LastIndexByte(ec.File[:idx], '/')
   123  	if idx == -1 {
   124  		return ec.FullPath()
   125  	}
   126  	buf := bufferpool.Get()
   127  	// Keep everything after the penultimate separator.
   128  	buf.AppendString(ec.File[idx+1:])
   129  	buf.AppendByte(':')
   130  	buf.AppendInt(int64(ec.Line))
   131  	caller := buf.String()
   132  	buf.Free()
   133  	return caller
   134  }
   135  
   136  // An Entry represents a complete log message. The entry's structured context
   137  // is already serialized, but the log level, time, message, and call site
   138  // information are available for inspection and modification. Any fields left
   139  // empty will be omitted when encoding.
   140  //
   141  // Entries are pooled, so any functions that accept them MUST be careful not to
   142  // retain references to them.
   143  type Entry struct {
   144  	Level      Level
   145  	Time       time.Time
   146  	LoggerName string
   147  	Message    string
   148  	Caller     EntryCaller
   149  	Stack      string
   150  }
   151  
   152  // CheckWriteHook is a custom action that may be executed after an entry is
   153  // written.
   154  //
   155  // Register one on a CheckedEntry with the After method.
   156  //
   157  //	if ce := logger.Check(...); ce != nil {
   158  //	  ce = ce.After(hook)
   159  //	  ce.Write(...)
   160  //	}
   161  //
   162  // You can configure the hook for Fatal log statements at the logger level with
   163  // the zap.WithFatalHook option.
   164  type CheckWriteHook interface {
   165  	// OnWrite is invoked with the CheckedEntry that was written and a list
   166  	// of fields added with that entry.
   167  	//
   168  	// The list of fields DOES NOT include fields that were already added
   169  	// to the logger with the With method.
   170  	OnWrite(*CheckedEntry, []Field)
   171  }
   172  
   173  // CheckWriteAction indicates what action to take after a log entry is
   174  // processed. Actions are ordered in increasing severity.
   175  type CheckWriteAction uint8
   176  
   177  const (
   178  	// WriteThenNoop indicates that nothing special needs to be done. It's the
   179  	// default behavior.
   180  	WriteThenNoop CheckWriteAction = iota
   181  	// WriteThenGoexit runs runtime.Goexit after Write.
   182  	WriteThenGoexit
   183  	// WriteThenPanic causes a panic after Write.
   184  	WriteThenPanic
   185  	// WriteThenFatal causes an os.Exit(1) after Write.
   186  	WriteThenFatal
   187  )
   188  
   189  // OnWrite implements the OnWrite method to keep CheckWriteAction compatible
   190  // with the new CheckWriteHook interface which deprecates CheckWriteAction.
   191  func (a CheckWriteAction) OnWrite(ce *CheckedEntry, _ []Field) {
   192  	switch a {
   193  	case WriteThenGoexit:
   194  		runtime.Goexit()
   195  	case WriteThenPanic:
   196  		panic(ce.Message)
   197  	case WriteThenFatal:
   198  		exit.With(1)
   199  	}
   200  }
   201  
   202  var _ CheckWriteHook = CheckWriteAction(0)
   203  
   204  // CheckedEntry is an Entry together with a collection of Cores that have
   205  // already agreed to log it.
   206  //
   207  // CheckedEntry references should be created by calling AddCore or After on a
   208  // nil *CheckedEntry. References are returned to a pool after Write, and MUST
   209  // NOT be retained after calling their Write method.
   210  type CheckedEntry struct {
   211  	Entry
   212  	ErrorOutput WriteSyncer
   213  	dirty       bool // best-effort detection of pool misuse
   214  	after       CheckWriteHook
   215  	cores       []Core
   216  }
   217  
   218  func (ce *CheckedEntry) reset() {
   219  	ce.Entry = Entry{}
   220  	ce.ErrorOutput = nil
   221  	ce.dirty = false
   222  	ce.after = nil
   223  	for i := range ce.cores {
   224  		// don't keep references to cores
   225  		ce.cores[i] = nil
   226  	}
   227  	ce.cores = ce.cores[:0]
   228  }
   229  
   230  // Write writes the entry to the stored Cores, returns any errors, and returns
   231  // the CheckedEntry reference to a pool for immediate re-use. Finally, it
   232  // executes any required CheckWriteAction.
   233  func (ce *CheckedEntry) Write(fields ...Field) {
   234  	if ce == nil {
   235  		return
   236  	}
   237  
   238  	if ce.dirty {
   239  		if ce.ErrorOutput != nil {
   240  			// Make a best effort to detect unsafe re-use of this CheckedEntry.
   241  			// If the entry is dirty, log an internal error; because the
   242  			// CheckedEntry is being used after it was returned to the pool,
   243  			// the message may be an amalgamation from multiple call sites.
   244  			fmt.Fprintf(ce.ErrorOutput, "%v Unsafe CheckedEntry re-use near Entry %+v.\n", ce.Time, ce.Entry)
   245  			_ = ce.ErrorOutput.Sync() // ignore error
   246  		}
   247  		return
   248  	}
   249  	ce.dirty = true
   250  
   251  	var err error
   252  	for i := range ce.cores {
   253  		err = multierr.Append(err, ce.cores[i].Write(ce.Entry, fields))
   254  	}
   255  	if err != nil && ce.ErrorOutput != nil {
   256  		fmt.Fprintf(ce.ErrorOutput, "%v write error: %v\n", ce.Time, err)
   257  		_ = ce.ErrorOutput.Sync() // ignore error
   258  	}
   259  
   260  	hook := ce.after
   261  	if hook != nil {
   262  		hook.OnWrite(ce, fields)
   263  	}
   264  	putCheckedEntry(ce)
   265  }
   266  
   267  // AddCore adds a Core that has agreed to log this CheckedEntry. It's intended to be
   268  // used by Core.Check implementations, and is safe to call on nil CheckedEntry
   269  // references.
   270  func (ce *CheckedEntry) AddCore(ent Entry, core Core) *CheckedEntry {
   271  	if ce == nil {
   272  		ce = getCheckedEntry()
   273  		ce.Entry = ent
   274  	}
   275  	ce.cores = append(ce.cores, core)
   276  	return ce
   277  }
   278  
   279  // Should sets this CheckedEntry's CheckWriteAction, which controls whether a
   280  // Core will panic or fatal after writing this log entry. Like AddCore, it's
   281  // safe to call on nil CheckedEntry references.
   282  //
   283  // Deprecated: Use [CheckedEntry.After] instead.
   284  func (ce *CheckedEntry) Should(ent Entry, should CheckWriteAction) *CheckedEntry {
   285  	return ce.After(ent, should)
   286  }
   287  
   288  // After sets this CheckEntry's CheckWriteHook, which will be called after this
   289  // log entry has been written. It's safe to call this on nil CheckedEntry
   290  // references.
   291  func (ce *CheckedEntry) After(ent Entry, hook CheckWriteHook) *CheckedEntry {
   292  	if ce == nil {
   293  		ce = getCheckedEntry()
   294  		ce.Entry = ent
   295  	}
   296  	ce.after = hook
   297  	return ce
   298  }
   299  

View as plain text