...

Source file src/go.uber.org/zap/internal/stacktrace/stack.go

Documentation: go.uber.org/zap/internal/stacktrace

     1  // Copyright (c) 2023 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 stacktrace provides support for gathering stack traces
    22  // efficiently.
    23  package stacktrace
    24  
    25  import (
    26  	"runtime"
    27  
    28  	"go.uber.org/zap/buffer"
    29  	"go.uber.org/zap/internal/bufferpool"
    30  	"go.uber.org/zap/internal/pool"
    31  )
    32  
    33  var _stackPool = pool.New(func() *Stack {
    34  	return &Stack{
    35  		storage: make([]uintptr, 64),
    36  	}
    37  })
    38  
    39  // Stack is a captured stack trace.
    40  type Stack struct {
    41  	pcs    []uintptr // program counters; always a subslice of storage
    42  	frames *runtime.Frames
    43  
    44  	// The size of pcs varies depending on requirements:
    45  	// it will be one if the only the first frame was requested,
    46  	// and otherwise it will reflect the depth of the call stack.
    47  	//
    48  	// storage decouples the slice we need (pcs) from the slice we pool.
    49  	// We will always allocate a reasonably large storage, but we'll use
    50  	// only as much of it as we need.
    51  	storage []uintptr
    52  }
    53  
    54  // Depth specifies how deep of a stack trace should be captured.
    55  type Depth int
    56  
    57  const (
    58  	// First captures only the first frame.
    59  	First Depth = iota
    60  
    61  	// Full captures the entire call stack, allocating more
    62  	// storage for it if needed.
    63  	Full
    64  )
    65  
    66  // Capture captures a stack trace of the specified depth, skipping
    67  // the provided number of frames. skip=0 identifies the caller of
    68  // Capture.
    69  //
    70  // The caller must call Free on the returned stacktrace after using it.
    71  func Capture(skip int, depth Depth) *Stack {
    72  	stack := _stackPool.Get()
    73  
    74  	switch depth {
    75  	case First:
    76  		stack.pcs = stack.storage[:1]
    77  	case Full:
    78  		stack.pcs = stack.storage
    79  	}
    80  
    81  	// Unlike other "skip"-based APIs, skip=0 identifies runtime.Callers
    82  	// itself. +2 to skip captureStacktrace and runtime.Callers.
    83  	numFrames := runtime.Callers(
    84  		skip+2,
    85  		stack.pcs,
    86  	)
    87  
    88  	// runtime.Callers truncates the recorded stacktrace if there is no
    89  	// room in the provided slice. For the full stack trace, keep expanding
    90  	// storage until there are fewer frames than there is room.
    91  	if depth == Full {
    92  		pcs := stack.pcs
    93  		for numFrames == len(pcs) {
    94  			pcs = make([]uintptr, len(pcs)*2)
    95  			numFrames = runtime.Callers(skip+2, pcs)
    96  		}
    97  
    98  		// Discard old storage instead of returning it to the pool.
    99  		// This will adjust the pool size over time if stack traces are
   100  		// consistently very deep.
   101  		stack.storage = pcs
   102  		stack.pcs = pcs[:numFrames]
   103  	} else {
   104  		stack.pcs = stack.pcs[:numFrames]
   105  	}
   106  
   107  	stack.frames = runtime.CallersFrames(stack.pcs)
   108  	return stack
   109  }
   110  
   111  // Free releases resources associated with this stacktrace
   112  // and returns it back to the pool.
   113  func (st *Stack) Free() {
   114  	st.frames = nil
   115  	st.pcs = nil
   116  	_stackPool.Put(st)
   117  }
   118  
   119  // Count reports the total number of frames in this stacktrace.
   120  // Count DOES NOT change as Next is called.
   121  func (st *Stack) Count() int {
   122  	return len(st.pcs)
   123  }
   124  
   125  // Next returns the next frame in the stack trace,
   126  // and a boolean indicating whether there are more after it.
   127  func (st *Stack) Next() (_ runtime.Frame, more bool) {
   128  	return st.frames.Next()
   129  }
   130  
   131  // Take returns a string representation of the current stacktrace.
   132  //
   133  // skip is the number of frames to skip before recording the stack trace.
   134  // skip=0 identifies the caller of Take.
   135  func Take(skip int) string {
   136  	stack := Capture(skip+1, Full)
   137  	defer stack.Free()
   138  
   139  	buffer := bufferpool.Get()
   140  	defer buffer.Free()
   141  
   142  	stackfmt := NewFormatter(buffer)
   143  	stackfmt.FormatStack(stack)
   144  	return buffer.String()
   145  }
   146  
   147  // Formatter formats a stack trace into a readable string representation.
   148  type Formatter struct {
   149  	b        *buffer.Buffer
   150  	nonEmpty bool // whehther we've written at least one frame already
   151  }
   152  
   153  // NewFormatter builds a new Formatter.
   154  func NewFormatter(b *buffer.Buffer) Formatter {
   155  	return Formatter{b: b}
   156  }
   157  
   158  // FormatStack formats all remaining frames in the provided stacktrace -- minus
   159  // the final runtime.main/runtime.goexit frame.
   160  func (sf *Formatter) FormatStack(stack *Stack) {
   161  	// Note: On the last iteration, frames.Next() returns false, with a valid
   162  	// frame, but we ignore this frame. The last frame is a runtime frame which
   163  	// adds noise, since it's only either runtime.main or runtime.goexit.
   164  	for frame, more := stack.Next(); more; frame, more = stack.Next() {
   165  		sf.FormatFrame(frame)
   166  	}
   167  }
   168  
   169  // FormatFrame formats the given frame.
   170  func (sf *Formatter) FormatFrame(frame runtime.Frame) {
   171  	if sf.nonEmpty {
   172  		sf.b.AppendByte('\n')
   173  	}
   174  	sf.nonEmpty = true
   175  	sf.b.AppendString(frame.Function)
   176  	sf.b.AppendByte('\n')
   177  	sf.b.AppendByte('\t')
   178  	sf.b.AppendString(frame.File)
   179  	sf.b.AppendByte(':')
   180  	sf.b.AppendInt(int64(frame.Line))
   181  }
   182  

View as plain text