// Copyright (c) 2016 Uber Technologies, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. package zap import ( "runtime" "sync" "go.uber.org/zap/buffer" "go.uber.org/zap/internal/bufferpool" ) var _stacktracePool = sync.Pool{ New: func() interface{} { return &stacktrace{ storage: make([]uintptr, 64), } }, } type stacktrace struct { pcs []uintptr // program counters; always a subslice of storage frames *runtime.Frames // The size of pcs varies depending on requirements: // it will be one if the only the first frame was requested, // and otherwise it will reflect the depth of the call stack. // // storage decouples the slice we need (pcs) from the slice we pool. // We will always allocate a reasonably large storage, but we'll use // only as much of it as we need. storage []uintptr } // stacktraceDepth specifies how deep of a stack trace should be captured. type stacktraceDepth int const ( // stacktraceFirst captures only the first frame. stacktraceFirst stacktraceDepth = iota // stacktraceFull captures the entire call stack, allocating more // storage for it if needed. stacktraceFull ) // captureStacktrace captures a stack trace of the specified depth, skipping // the provided number of frames. skip=0 identifies the caller of // captureStacktrace. // // The caller must call Free on the returned stacktrace after using it. func captureStacktrace(skip int, depth stacktraceDepth) *stacktrace { stack := _stacktracePool.Get().(*stacktrace) switch depth { case stacktraceFirst: stack.pcs = stack.storage[:1] case stacktraceFull: stack.pcs = stack.storage } // Unlike other "skip"-based APIs, skip=0 identifies runtime.Callers // itself. +2 to skip captureStacktrace and runtime.Callers. numFrames := runtime.Callers( skip+2, stack.pcs, ) // runtime.Callers truncates the recorded stacktrace if there is no // room in the provided slice. For the full stack trace, keep expanding // storage until there are fewer frames than there is room. if depth == stacktraceFull { pcs := stack.pcs for numFrames == len(pcs) { pcs = make([]uintptr, len(pcs)*2) numFrames = runtime.Callers(skip+2, pcs) } // Discard old storage instead of returning it to the pool. // This will adjust the pool size over time if stack traces are // consistently very deep. stack.storage = pcs stack.pcs = pcs[:numFrames] } else { stack.pcs = stack.pcs[:numFrames] } stack.frames = runtime.CallersFrames(stack.pcs) return stack } // Free releases resources associated with this stacktrace // and returns it back to the pool. func (st *stacktrace) Free() { st.frames = nil st.pcs = nil _stacktracePool.Put(st) } // Count reports the total number of frames in this stacktrace. // Count DOES NOT change as Next is called. func (st *stacktrace) Count() int { return len(st.pcs) } // Next returns the next frame in the stack trace, // and a boolean indicating whether there are more after it. func (st *stacktrace) Next() (_ runtime.Frame, more bool) { return st.frames.Next() } func takeStacktrace(skip int) string { stack := captureStacktrace(skip+1, stacktraceFull) defer stack.Free() buffer := bufferpool.Get() defer buffer.Free() stackfmt := newStackFormatter(buffer) stackfmt.FormatStack(stack) return buffer.String() } // stackFormatter formats a stack trace into a readable string representation. type stackFormatter struct { b *buffer.Buffer nonEmpty bool // whehther we've written at least one frame already } // newStackFormatter builds a new stackFormatter. func newStackFormatter(b *buffer.Buffer) stackFormatter { return stackFormatter{b: b} } // FormatStack formats all remaining frames in the provided stacktrace -- minus // the final runtime.main/runtime.goexit frame. func (sf *stackFormatter) FormatStack(stack *stacktrace) { // Note: On the last iteration, frames.Next() returns false, with a valid // frame, but we ignore this frame. The last frame is a a runtime frame which // adds noise, since it's only either runtime.main or runtime.goexit. for frame, more := stack.Next(); more; frame, more = stack.Next() { sf.FormatFrame(frame) } } // FormatFrame formats the given frame. func (sf *stackFormatter) FormatFrame(frame runtime.Frame) { if sf.nonEmpty { sf.b.AppendByte('\n') } sf.nonEmpty = true sf.b.AppendString(frame.Function) sf.b.AppendByte('\n') sf.b.AppendByte('\t') sf.b.AppendString(frame.File) sf.b.AppendByte(':') sf.b.AppendInt(int64(frame.Line)) }