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