entry.go raw

   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