Source File
stack.go
Belonging Package
go.uber.org/zap/internal/stacktrace
// Copyright (c) 2023 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 stacktrace provides support for gathering stack traces
// efficiently.
package stacktrace
import (
)
var _stackPool = pool.New(func() *Stack {
return &Stack{
storage: make([]uintptr, 64),
}
})
// Stack is a captured stack trace.
type Stack 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
}
// Depth specifies how deep of a stack trace should be captured.
type Depth int
const (
// First captures only the first frame.
First Depth = iota
// Full captures the entire call stack, allocating more
// storage for it if needed.
Full
)
// Capture captures a stack trace of the specified depth, skipping
// the provided number of frames. skip=0 identifies the caller of
// Capture.
//
// The caller must call Free on the returned stacktrace after using it.
func ( int, Depth) *Stack {
:= _stackPool.Get()
switch {
case First:
.pcs = .storage[:1]
case Full:
.pcs = .storage
}
// Unlike other "skip"-based APIs, skip=0 identifies runtime.Callers
// itself. +2 to skip captureStacktrace and runtime.Callers.
:= runtime.Callers(
+2,
.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 == Full {
:= .pcs
for == len() {
= make([]uintptr, len()*2)
= runtime.Callers(+2, )
}
// 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.
.storage =
.pcs = [:]
} else {
.pcs = .pcs[:]
}
.frames = runtime.CallersFrames(.pcs)
return
}
// Free releases resources associated with this stacktrace
// and returns it back to the pool.
func ( *Stack) () {
.frames = nil
.pcs = nil
_stackPool.Put()
}
// Count reports the total number of frames in this stacktrace.
// Count DOES NOT change as Next is called.
func ( *Stack) () int {
return len(.pcs)
}
// Next returns the next frame in the stack trace,
// and a boolean indicating whether there are more after it.
func ( *Stack) () ( runtime.Frame, bool) {
return .frames.Next()
}
// Take returns a string representation of the current stacktrace.
//
// skip is the number of frames to skip before recording the stack trace.
// skip=0 identifies the caller of Take.
func ( int) string {
:= Capture(+1, Full)
defer .Free()
:= bufferpool.Get()
defer .Free()
:= NewFormatter()
.FormatStack()
return .String()
}
// Formatter formats a stack trace into a readable string representation.
type Formatter struct {
b *buffer.Buffer
nonEmpty bool // whehther we've written at least one frame already
}
// NewFormatter builds a new Formatter.
func ( *buffer.Buffer) Formatter {
return Formatter{b: }
}
// FormatStack formats all remaining frames in the provided stacktrace -- minus
// the final runtime.main/runtime.goexit frame.
func ( *Formatter) ( *Stack) {
// Note: On the last iteration, frames.Next() returns false, with a valid
// frame, but we ignore this frame. The last frame is a runtime frame which
// adds noise, since it's only either runtime.main or runtime.goexit.
for , := .Next(); ; , = .Next() {
.FormatFrame()
}
}
// FormatFrame formats the given frame.
func ( *Formatter) ( runtime.Frame) {
if .nonEmpty {
.b.AppendByte('\n')
}
.nonEmpty = true
.b.AppendString(.Function)
.b.AppendByte('\n')
.b.AppendByte('\t')
.b.AppendString(.File)
.b.AppendByte(':')
.b.AppendInt(int64(.Line))
}
The pages are generated with Golds v0.6.7. (GOOS=linux GOARCH=amd64) Golds is a Go 101 project developed by Tapir Liu. PR and bug reports are welcome and can be submitted to the issue list. Please follow @Go100and1 (reachable from the left QR code) to get the latest news of Golds. |