git.colasdn.top/newrelic/go-agent@v3.26.0+incompatible/internal/stacktrace.go (about)

     1  // Copyright 2020 New Relic Corporation. All rights reserved.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package internal
     5  
     6  import (
     7  	"bytes"
     8  	"path"
     9  	"runtime"
    10  	"strings"
    11  )
    12  
    13  // StackTrace is a stack trace.
    14  type StackTrace []uintptr
    15  
    16  // GetStackTrace returns a new StackTrace.
    17  func GetStackTrace() StackTrace {
    18  	skip := 1 // skip runtime.Callers
    19  	callers := make([]uintptr, maxStackTraceFrames)
    20  	written := runtime.Callers(skip, callers)
    21  	return callers[:written]
    22  }
    23  
    24  type stacktraceFrame struct {
    25  	Name string
    26  	File string
    27  	Line int64
    28  }
    29  
    30  func (f stacktraceFrame) formattedName() string {
    31  	if strings.HasPrefix(f.Name, "go.") {
    32  		// This indicates an anonymous struct. eg.
    33  		// "go.(*struct { github.com/newrelic/go-agent.threadWithExtras }).NoticeError"
    34  		return f.Name
    35  	}
    36  	return path.Base(f.Name)
    37  }
    38  
    39  func (f stacktraceFrame) isAgent() bool {
    40  	// Note this is not a contains conditional rather than a prefix
    41  	// conditional to handle anonymous functions like:
    42  	// "go.(*struct { github.com/newrelic/go-agent.threadWithExtras }).NoticeError"
    43  	return strings.Contains(f.Name, "github.com/newrelic/go-agent/internal.") ||
    44  		strings.Contains(f.Name, "github.com/newrelic/go-agent.")
    45  }
    46  
    47  func (f stacktraceFrame) WriteJSON(buf *bytes.Buffer) {
    48  	buf.WriteByte('{')
    49  	w := jsonFieldsWriter{buf: buf}
    50  	if f.Name != "" {
    51  		w.stringField("name", f.formattedName())
    52  	}
    53  	if f.File != "" {
    54  		w.stringField("filepath", f.File)
    55  	}
    56  	if f.Line != 0 {
    57  		w.intField("line", f.Line)
    58  	}
    59  	buf.WriteByte('}')
    60  }
    61  
    62  func writeFrames(buf *bytes.Buffer, frames []stacktraceFrame) {
    63  	// Remove top agent frames.
    64  	for len(frames) > 0 && frames[0].isAgent() {
    65  		frames = frames[1:]
    66  	}
    67  	// Truncate excessively long stack traces (they may be provided by the
    68  	// customer).
    69  	if len(frames) > maxStackTraceFrames {
    70  		frames = frames[0:maxStackTraceFrames]
    71  	}
    72  
    73  	buf.WriteByte('[')
    74  	for idx, frame := range frames {
    75  		if idx > 0 {
    76  			buf.WriteByte(',')
    77  		}
    78  		frame.WriteJSON(buf)
    79  	}
    80  	buf.WriteByte(']')
    81  }
    82  
    83  // WriteJSON adds the stack trace to the buffer in the JSON form expected by the
    84  // collector.
    85  func (st StackTrace) WriteJSON(buf *bytes.Buffer) {
    86  	frames := st.frames()
    87  	writeFrames(buf, frames)
    88  }
    89  
    90  // MarshalJSON prepares JSON in the format expected by the collector.
    91  func (st StackTrace) MarshalJSON() ([]byte, error) {
    92  	estimate := 256 * len(st)
    93  	buf := bytes.NewBuffer(make([]byte, 0, estimate))
    94  
    95  	st.WriteJSON(buf)
    96  
    97  	return buf.Bytes(), nil
    98  }