1
0

notice.go 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. package honeybadger
  2. import (
  3. "encoding/json"
  4. "net/url"
  5. "os"
  6. "regexp"
  7. "time"
  8. "github.com/pborman/uuid"
  9. "github.com/shirou/gopsutil/load"
  10. "github.com/shirou/gopsutil/mem"
  11. )
  12. // ErrorClass represents the class name of the error which is sent to
  13. // Honeybadger.
  14. type ErrorClass struct {
  15. Name string
  16. }
  17. // Fingerprint represents the fingerprint of the error, which controls grouping
  18. // in Honeybadger.
  19. type Fingerprint struct {
  20. Content string
  21. }
  22. func (f *Fingerprint) String() string {
  23. return f.Content
  24. }
  25. // Notice is a representation of the error which is sent to Honeybadger, and
  26. // implements the Payload interface.
  27. type Notice struct {
  28. APIKey string
  29. Error Error
  30. Token string
  31. ErrorMessage string
  32. ErrorClass string
  33. Tags []string
  34. Hostname string
  35. Env string
  36. Backtrace []*Frame
  37. ProjectRoot string
  38. Context Context
  39. Params Params
  40. CGIData CGIData
  41. URL string
  42. Fingerprint string
  43. }
  44. func (n *Notice) asJSON() *hash {
  45. return &hash{
  46. "api_key": n.APIKey,
  47. "notifier": &hash{
  48. "name": "honeybadger",
  49. "url": "https://github.com/honeybadger-io/honeybadger-go",
  50. "version": VERSION,
  51. },
  52. "error": &hash{
  53. "token": n.Token,
  54. "message": n.ErrorMessage,
  55. "class": n.ErrorClass,
  56. "tags": n.Tags,
  57. "backtrace": n.Backtrace,
  58. "fingerprint": n.Fingerprint,
  59. },
  60. "request": &hash{
  61. "context": n.Context,
  62. "params": n.Params,
  63. "cgi_data": n.CGIData,
  64. "url": n.URL,
  65. },
  66. "server": &hash{
  67. "project_root": n.ProjectRoot,
  68. "environment_name": n.Env,
  69. "hostname": n.Hostname,
  70. "time": time.Now().UTC(),
  71. "pid": os.Getpid(),
  72. "stats": getStats(),
  73. },
  74. }
  75. }
  76. func bytesToKB(bytes uint64) float64 {
  77. return float64(bytes) / 1024.0
  78. }
  79. func getStats() *hash {
  80. var m, l *hash
  81. if stat, err := mem.VirtualMemory(); err == nil {
  82. m = &hash{
  83. "total": bytesToKB(stat.Total),
  84. "free": bytesToKB(stat.Free),
  85. "buffers": bytesToKB(stat.Buffers),
  86. "cached": bytesToKB(stat.Cached),
  87. "free_total": bytesToKB(stat.Free + stat.Buffers + stat.Cached),
  88. }
  89. }
  90. if stat, err := load.Avg(); err == nil {
  91. l = &hash{
  92. "one": stat.Load1,
  93. "five": stat.Load5,
  94. "fifteen": stat.Load15,
  95. }
  96. }
  97. return &hash{"mem": m, "load": l}
  98. }
  99. func (n *Notice) toJSON() []byte {
  100. out, err := json.Marshal(n.asJSON())
  101. if err == nil {
  102. return out
  103. }
  104. panic(err)
  105. }
  106. func (n *Notice) setContext(context Context) {
  107. n.Context.Update(context)
  108. }
  109. func composeStack(stack []*Frame, root string) (frames []*Frame) {
  110. if root == "" {
  111. return stack
  112. }
  113. re, err := regexp.Compile("^" + regexp.QuoteMeta(root))
  114. if err != nil {
  115. return stack
  116. }
  117. for _, frame := range stack {
  118. file := re.ReplaceAllString(frame.File, "[PROJECT_ROOT]")
  119. frames = append(frames, &Frame{
  120. File: file,
  121. Number: frame.Number,
  122. Method: frame.Method,
  123. })
  124. }
  125. return
  126. }
  127. func newNotice(config *Configuration, err Error, extra ...interface{}) *Notice {
  128. notice := Notice{
  129. APIKey: config.APIKey,
  130. Error: err,
  131. Token: uuid.NewRandom().String(),
  132. ErrorMessage: err.Message,
  133. ErrorClass: err.Class,
  134. Env: config.Env,
  135. Hostname: config.Hostname,
  136. Backtrace: composeStack(err.Stack, config.Root),
  137. ProjectRoot: config.Root,
  138. Context: Context{},
  139. }
  140. for _, thing := range extra {
  141. switch t := thing.(type) {
  142. case Context:
  143. notice.setContext(t)
  144. case ErrorClass:
  145. notice.ErrorClass = t.Name
  146. case Tags:
  147. for _, tag := range t {
  148. notice.Tags = append(notice.Tags, tag)
  149. }
  150. case Fingerprint:
  151. notice.Fingerprint = t.String()
  152. case Params:
  153. notice.Params = t
  154. case CGIData:
  155. notice.CGIData = t
  156. case url.URL:
  157. notice.URL = t.String()
  158. }
  159. }
  160. return &notice
  161. }