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