blob: 2d815feb84218d04dc31ded9be3b5eebf7997a38 [file] [log] [blame]
Don Newton7577f072020-01-06 12:41:11 -05001// 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
21package zapcore
22
23import (
24 "fmt"
David K. Bainbridgee05cf0c2021-08-19 03:16:50 +000025 "runtime"
Don Newton7577f072020-01-06 12:41:11 -050026 "strings"
27 "sync"
28 "time"
29
30 "go.uber.org/zap/internal/bufferpool"
31 "go.uber.org/zap/internal/exit"
32
33 "go.uber.org/multierr"
34)
35
36var (
37 _cePool = sync.Pool{New: func() interface{} {
38 // Pre-allocate some space for cores.
39 return &CheckedEntry{
40 cores: make([]Core, 4),
41 }
42 }}
43)
44
45func getCheckedEntry() *CheckedEntry {
46 ce := _cePool.Get().(*CheckedEntry)
47 ce.reset()
48 return ce
49}
50
51func putCheckedEntry(ce *CheckedEntry) {
52 if ce == nil {
53 return
54 }
55 _cePool.Put(ce)
56}
57
58// NewEntryCaller makes an EntryCaller from the return signature of
59// runtime.Caller.
60func NewEntryCaller(pc uintptr, file string, line int, ok bool) EntryCaller {
61 if !ok {
62 return EntryCaller{}
63 }
64 return EntryCaller{
65 PC: pc,
66 File: file,
67 Line: line,
68 Defined: true,
69 }
70}
71
72// EntryCaller represents the caller of a logging function.
73type EntryCaller struct {
David K. Bainbridgee05cf0c2021-08-19 03:16:50 +000074 Defined bool
75 PC uintptr
76 File string
77 Line int
78 Function string
Don Newton7577f072020-01-06 12:41:11 -050079}
80
81// String returns the full path and line number of the caller.
82func (ec EntryCaller) String() string {
83 return ec.FullPath()
84}
85
86// FullPath returns a /full/path/to/package/file:line description of the
87// caller.
88func (ec EntryCaller) FullPath() string {
89 if !ec.Defined {
90 return "undefined"
91 }
92 buf := bufferpool.Get()
93 buf.AppendString(ec.File)
94 buf.AppendByte(':')
95 buf.AppendInt(int64(ec.Line))
96 caller := buf.String()
97 buf.Free()
98 return caller
99}
100
101// TrimmedPath returns a package/file:line description of the caller,
102// preserving only the leaf directory name and file name.
103func (ec EntryCaller) TrimmedPath() string {
104 if !ec.Defined {
105 return "undefined"
106 }
107 // nb. To make sure we trim the path correctly on Windows too, we
108 // counter-intuitively need to use '/' and *not* os.PathSeparator here,
109 // because the path given originates from Go stdlib, specifically
110 // runtime.Caller() which (as of Mar/17) returns forward slashes even on
111 // Windows.
112 //
113 // See https://github.com/golang/go/issues/3335
114 // and https://github.com/golang/go/issues/18151
115 //
116 // for discussion on the issue on Go side.
117 //
118 // Find the last separator.
119 //
120 idx := strings.LastIndexByte(ec.File, '/')
121 if idx == -1 {
122 return ec.FullPath()
123 }
124 // Find the penultimate separator.
125 idx = strings.LastIndexByte(ec.File[:idx], '/')
126 if idx == -1 {
127 return ec.FullPath()
128 }
129 buf := bufferpool.Get()
130 // Keep everything after the penultimate separator.
131 buf.AppendString(ec.File[idx+1:])
132 buf.AppendByte(':')
133 buf.AppendInt(int64(ec.Line))
134 caller := buf.String()
135 buf.Free()
136 return caller
137}
138
139// An Entry represents a complete log message. The entry's structured context
140// is already serialized, but the log level, time, message, and call site
David K. Bainbridgee05cf0c2021-08-19 03:16:50 +0000141// information are available for inspection and modification. Any fields left
142// empty will be omitted when encoding.
Don Newton7577f072020-01-06 12:41:11 -0500143//
144// Entries are pooled, so any functions that accept them MUST be careful not to
145// retain references to them.
146type Entry struct {
147 Level Level
148 Time time.Time
149 LoggerName string
150 Message string
151 Caller EntryCaller
152 Stack string
153}
154
155// CheckWriteAction indicates what action to take after a log entry is
156// processed. Actions are ordered in increasing severity.
157type CheckWriteAction uint8
158
159const (
160 // WriteThenNoop indicates that nothing special needs to be done. It's the
161 // default behavior.
162 WriteThenNoop CheckWriteAction = iota
David K. Bainbridgee05cf0c2021-08-19 03:16:50 +0000163 // WriteThenGoexit runs runtime.Goexit after Write.
164 WriteThenGoexit
Don Newton7577f072020-01-06 12:41:11 -0500165 // WriteThenPanic causes a panic after Write.
166 WriteThenPanic
167 // WriteThenFatal causes a fatal os.Exit after Write.
168 WriteThenFatal
169)
170
171// CheckedEntry is an Entry together with a collection of Cores that have
172// already agreed to log it.
173//
174// CheckedEntry references should be created by calling AddCore or Should on a
175// nil *CheckedEntry. References are returned to a pool after Write, and MUST
176// NOT be retained after calling their Write method.
177type CheckedEntry struct {
178 Entry
179 ErrorOutput WriteSyncer
180 dirty bool // best-effort detection of pool misuse
181 should CheckWriteAction
182 cores []Core
183}
184
185func (ce *CheckedEntry) reset() {
186 ce.Entry = Entry{}
187 ce.ErrorOutput = nil
188 ce.dirty = false
189 ce.should = WriteThenNoop
190 for i := range ce.cores {
191 // don't keep references to cores
192 ce.cores[i] = nil
193 }
194 ce.cores = ce.cores[:0]
195}
196
197// Write writes the entry to the stored Cores, returns any errors, and returns
198// the CheckedEntry reference to a pool for immediate re-use. Finally, it
199// executes any required CheckWriteAction.
200func (ce *CheckedEntry) Write(fields ...Field) {
201 if ce == nil {
202 return
203 }
204
205 if ce.dirty {
206 if ce.ErrorOutput != nil {
207 // Make a best effort to detect unsafe re-use of this CheckedEntry.
208 // If the entry is dirty, log an internal error; because the
209 // CheckedEntry is being used after it was returned to the pool,
210 // the message may be an amalgamation from multiple call sites.
David K. Bainbridgee05cf0c2021-08-19 03:16:50 +0000211 fmt.Fprintf(ce.ErrorOutput, "%v Unsafe CheckedEntry re-use near Entry %+v.\n", ce.Time, ce.Entry)
Don Newton7577f072020-01-06 12:41:11 -0500212 ce.ErrorOutput.Sync()
213 }
214 return
215 }
216 ce.dirty = true
217
218 var err error
219 for i := range ce.cores {
220 err = multierr.Append(err, ce.cores[i].Write(ce.Entry, fields))
221 }
222 if ce.ErrorOutput != nil {
223 if err != nil {
David K. Bainbridgee05cf0c2021-08-19 03:16:50 +0000224 fmt.Fprintf(ce.ErrorOutput, "%v write error: %v\n", ce.Time, err)
Don Newton7577f072020-01-06 12:41:11 -0500225 ce.ErrorOutput.Sync()
226 }
227 }
228
229 should, msg := ce.should, ce.Message
230 putCheckedEntry(ce)
231
232 switch should {
233 case WriteThenPanic:
234 panic(msg)
235 case WriteThenFatal:
236 exit.Exit()
David K. Bainbridgee05cf0c2021-08-19 03:16:50 +0000237 case WriteThenGoexit:
238 runtime.Goexit()
Don Newton7577f072020-01-06 12:41:11 -0500239 }
240}
241
242// AddCore adds a Core that has agreed to log this CheckedEntry. It's intended to be
243// used by Core.Check implementations, and is safe to call on nil CheckedEntry
244// references.
245func (ce *CheckedEntry) AddCore(ent Entry, core Core) *CheckedEntry {
246 if ce == nil {
247 ce = getCheckedEntry()
248 ce.Entry = ent
249 }
250 ce.cores = append(ce.cores, core)
251 return ce
252}
253
254// Should sets this CheckedEntry's CheckWriteAction, which controls whether a
255// Core will panic or fatal after writing this log entry. Like AddCore, it's
256// safe to call on nil CheckedEntry references.
257func (ce *CheckedEntry) Should(ent Entry, should CheckWriteAction) *CheckedEntry {
258 if ce == nil {
259 ce = getCheckedEntry()
260 ce.Entry = ent
261 }
262 ce.should = should
263 return ce
264}