stacktrace.go 2.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  1. package internal
  2. import (
  3. "bytes"
  4. "path"
  5. "runtime"
  6. )
  7. // StackTrace is a stack trace.
  8. type StackTrace []uintptr
  9. // GetStackTrace returns a new StackTrace.
  10. func GetStackTrace(skipFrames int) StackTrace {
  11. skip := 2 // skips runtime.Callers and this function
  12. skip += skipFrames
  13. callers := make([]uintptr, maxStackTraceFrames)
  14. written := runtime.Callers(skip, callers)
  15. return StackTrace(callers[0:written])
  16. }
  17. func pcToFunc(pc uintptr) (*runtime.Func, uintptr) {
  18. // The Golang runtime package documentation says "To look up the file
  19. // and line number of the call itself, use pc[i]-1. As an exception to
  20. // this rule, if pc[i-1] corresponds to the function runtime.sigpanic,
  21. // then pc[i] is the program counter of a faulting instruction and
  22. // should be used without any subtraction."
  23. //
  24. // TODO: Fully understand when this subtraction is necessary.
  25. place := pc - 1
  26. return runtime.FuncForPC(place), place
  27. }
  28. func topCallerNameBase(st StackTrace) string {
  29. f, _ := pcToFunc(st[0])
  30. if nil == f {
  31. return ""
  32. }
  33. return path.Base(f.Name())
  34. }
  35. // WriteJSON adds the stack trace to the buffer in the JSON form expected by the
  36. // collector.
  37. func (st StackTrace) WriteJSON(buf *bytes.Buffer) {
  38. buf.WriteByte('[')
  39. for i, pc := range st {
  40. // Stack traces may be provided by the customer, and therefore
  41. // may be excessively long. The truncation is done here to
  42. // facilitate testing.
  43. if i >= maxStackTraceFrames {
  44. break
  45. }
  46. if i > 0 {
  47. buf.WriteByte(',')
  48. }
  49. // Implements the format documented here:
  50. // https://source.datanerd.us/agents/agent-specs/blob/master/Stack-Traces.md
  51. buf.WriteByte('{')
  52. if f, place := pcToFunc(pc); nil != f {
  53. name := path.Base(f.Name())
  54. file, line := f.FileLine(place)
  55. w := jsonFieldsWriter{buf: buf}
  56. w.stringField("filepath", file)
  57. w.stringField("name", name)
  58. w.intField("line", int64(line))
  59. }
  60. buf.WriteByte('}')
  61. }
  62. buf.WriteByte(']')
  63. }
  64. // MarshalJSON prepares JSON in the format expected by the collector.
  65. func (st StackTrace) MarshalJSON() ([]byte, error) {
  66. estimate := 256 * len(st)
  67. buf := bytes.NewBuffer(make([]byte, 0, estimate))
  68. st.WriteJSON(buf)
  69. return buf.Bytes(), nil
  70. }