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 }