Path: blob/main/vendor/go.uber.org/zap/internal/stacktrace/stack.go
2880 views
// Copyright (c) 2023 Uber Technologies, Inc.1//2// Permission is hereby granted, free of charge, to any person obtaining a copy3// of this software and associated documentation files (the "Software"), to deal4// in the Software without restriction, including without limitation the rights5// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell6// copies of the Software, and to permit persons to whom the Software is7// furnished to do so, subject to the following conditions:8//9// The above copyright notice and this permission notice shall be included in10// all copies or substantial portions of the Software.11//12// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR13// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,14// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE15// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER16// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,17// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN18// THE SOFTWARE.1920// Package stacktrace provides support for gathering stack traces21// efficiently.22package stacktrace2324import (25"runtime"2627"go.uber.org/zap/buffer"28"go.uber.org/zap/internal/bufferpool"29"go.uber.org/zap/internal/pool"30)3132var _stackPool = pool.New(func() *Stack {33return &Stack{34storage: make([]uintptr, 64),35}36})3738// Stack is a captured stack trace.39type Stack struct {40pcs []uintptr // program counters; always a subslice of storage41frames *runtime.Frames4243// The size of pcs varies depending on requirements:44// it will be one if the only the first frame was requested,45// and otherwise it will reflect the depth of the call stack.46//47// storage decouples the slice we need (pcs) from the slice we pool.48// We will always allocate a reasonably large storage, but we'll use49// only as much of it as we need.50storage []uintptr51}5253// Depth specifies how deep of a stack trace should be captured.54type Depth int5556const (57// First captures only the first frame.58First Depth = iota5960// Full captures the entire call stack, allocating more61// storage for it if needed.62Full63)6465// Capture captures a stack trace of the specified depth, skipping66// the provided number of frames. skip=0 identifies the caller of67// Capture.68//69// The caller must call Free on the returned stacktrace after using it.70func Capture(skip int, depth Depth) *Stack {71stack := _stackPool.Get()7273switch depth {74case First:75stack.pcs = stack.storage[:1]76case Full:77stack.pcs = stack.storage78}7980// Unlike other "skip"-based APIs, skip=0 identifies runtime.Callers81// itself. +2 to skip captureStacktrace and runtime.Callers.82numFrames := runtime.Callers(83skip+2,84stack.pcs,85)8687// runtime.Callers truncates the recorded stacktrace if there is no88// room in the provided slice. For the full stack trace, keep expanding89// storage until there are fewer frames than there is room.90if depth == Full {91pcs := stack.pcs92for numFrames == len(pcs) {93pcs = make([]uintptr, len(pcs)*2)94numFrames = runtime.Callers(skip+2, pcs)95}9697// Discard old storage instead of returning it to the pool.98// This will adjust the pool size over time if stack traces are99// consistently very deep.100stack.storage = pcs101stack.pcs = pcs[:numFrames]102} else {103stack.pcs = stack.pcs[:numFrames]104}105106stack.frames = runtime.CallersFrames(stack.pcs)107return stack108}109110// Free releases resources associated with this stacktrace111// and returns it back to the pool.112func (st *Stack) Free() {113st.frames = nil114st.pcs = nil115_stackPool.Put(st)116}117118// Count reports the total number of frames in this stacktrace.119// Count DOES NOT change as Next is called.120func (st *Stack) Count() int {121return len(st.pcs)122}123124// Next returns the next frame in the stack trace,125// and a boolean indicating whether there are more after it.126func (st *Stack) Next() (_ runtime.Frame, more bool) {127return st.frames.Next()128}129130// Take returns a string representation of the current stacktrace.131//132// skip is the number of frames to skip before recording the stack trace.133// skip=0 identifies the caller of Take.134func Take(skip int) string {135stack := Capture(skip+1, Full)136defer stack.Free()137138buffer := bufferpool.Get()139defer buffer.Free()140141stackfmt := NewFormatter(buffer)142stackfmt.FormatStack(stack)143return buffer.String()144}145146// Formatter formats a stack trace into a readable string representation.147type Formatter struct {148b *buffer.Buffer149nonEmpty bool // whehther we've written at least one frame already150}151152// NewFormatter builds a new Formatter.153func NewFormatter(b *buffer.Buffer) Formatter {154return Formatter{b: b}155}156157// FormatStack formats all remaining frames in the provided stacktrace -- minus158// the final runtime.main/runtime.goexit frame.159func (sf *Formatter) FormatStack(stack *Stack) {160// Note: On the last iteration, frames.Next() returns false, with a valid161// frame, but we ignore this frame. The last frame is a runtime frame which162// adds noise, since it's only either runtime.main or runtime.goexit.163for frame, more := stack.Next(); more; frame, more = stack.Next() {164sf.FormatFrame(frame)165}166}167168// FormatFrame formats the given frame.169func (sf *Formatter) FormatFrame(frame runtime.Frame) {170if sf.nonEmpty {171sf.b.AppendByte('\n')172}173sf.nonEmpty = true174sf.b.AppendString(frame.Function)175sf.b.AppendByte('\n')176sf.b.AppendByte('\t')177sf.b.AppendString(frame.File)178sf.b.AppendByte(':')179sf.b.AppendInt(int64(frame.Line))180}181182183