1
0

event.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. package bugsnag
  2. import (
  3. "context"
  4. "net/http"
  5. "strings"
  6. "github.com/bugsnag/bugsnag-go/errors"
  7. )
  8. // Context is the context of the error in Bugsnag.
  9. // This can be passed to Notify, Recover or AutoNotify as rawData.
  10. type Context struct {
  11. String string
  12. }
  13. // User represents the searchable user-data on Bugsnag. The Id is also used
  14. // to determine the number of users affected by a bug. This can be
  15. // passed to Notify, Recover or AutoNotify as rawData.
  16. type User struct {
  17. Id string `json:"id,omitempty"`
  18. Name string `json:"name,omitempty"`
  19. Email string `json:"email,omitempty"`
  20. }
  21. // ErrorClass overrides the error class in Bugsnag.
  22. // This struct enables you to group errors as you like.
  23. type ErrorClass struct {
  24. Name string
  25. }
  26. // Sets the severity of the error on Bugsnag. These values can be
  27. // passed to Notify, Recover or AutoNotify as rawData.
  28. var (
  29. SeverityError = severity{"error"}
  30. SeverityWarning = severity{"warning"}
  31. SeverityInfo = severity{"info"}
  32. )
  33. // The severity tag type, private so that people can only use Error,Warning,Info
  34. type severity struct {
  35. String string
  36. }
  37. // The form of stacktrace that Bugsnag expects
  38. type stackFrame struct {
  39. Method string `json:"method"`
  40. File string `json:"file"`
  41. LineNumber int `json:"lineNumber"`
  42. InProject bool `json:"inProject,omitempty"`
  43. }
  44. type SeverityReason string
  45. const (
  46. SeverityReasonCallbackSpecified SeverityReason = "userCallbackSetSeverity"
  47. SeverityReasonHandledError = "handledError"
  48. SeverityReasonHandledPanic = "handledPanic"
  49. SeverityReasonUnhandledError = "unhandledError"
  50. SeverityReasonUnhandledMiddlewareError = "unhandledErrorMiddleware"
  51. SeverityReasonUnhandledPanic = "unhandledPanic"
  52. SeverityReasonUserSpecified = "userSpecifiedSeverity"
  53. )
  54. type HandledState struct {
  55. SeverityReason SeverityReason
  56. OriginalSeverity severity
  57. Unhandled bool
  58. Framework string
  59. }
  60. // Event represents a payload of data that gets sent to Bugsnag.
  61. // This is passed to each OnBeforeNotify hook.
  62. type Event struct {
  63. // The original error that caused this event, not sent to Bugsnag.
  64. Error *errors.Error
  65. // The rawData affecting this error, not sent to Bugsnag.
  66. RawData []interface{}
  67. // The error class to be sent to Bugsnag. This defaults to the type name of the Error, for
  68. // example *error.String
  69. ErrorClass string
  70. // The error message to be sent to Bugsnag. This defaults to the return value of Error.Error()
  71. Message string
  72. // The stacktrrace of the error to be sent to Bugsnag.
  73. Stacktrace []stackFrame
  74. // The context to be sent to Bugsnag. This should be set to the part of the app that was running,
  75. // e.g. for http requests, set it to the path.
  76. Context string
  77. // The severity of the error. Can be SeverityError, SeverityWarning or SeverityInfo.
  78. Severity severity
  79. // The grouping hash is used to override Bugsnag's grouping. Set this if you'd like all errors with
  80. // the same grouping hash to group together in the dashboard.
  81. GroupingHash string
  82. // User data to send to Bugsnag. This is searchable on the dashboard.
  83. User *User
  84. // Other MetaData to send to Bugsnag. Appears as a set of tabbed tables in the dashboard.
  85. MetaData MetaData
  86. // Ctx is the context of the session the event occurred in. This allows Bugsnag to associate the event with the session.
  87. Ctx context.Context
  88. // Request is the request information that populates the Request tab in the dashboard.
  89. Request *RequestJSON
  90. // The reason for the severity and original value
  91. handledState HandledState
  92. }
  93. func newEvent(rawData []interface{}, notifier *Notifier) (*Event, *Configuration) {
  94. config := notifier.Config
  95. event := &Event{
  96. RawData: append(notifier.RawData, rawData...),
  97. Severity: SeverityWarning,
  98. MetaData: make(MetaData),
  99. handledState: HandledState{
  100. SeverityReason: SeverityReasonHandledError,
  101. OriginalSeverity: SeverityWarning,
  102. Unhandled: false,
  103. Framework: "",
  104. },
  105. }
  106. var err *errors.Error
  107. for _, datum := range event.RawData {
  108. switch datum := datum.(type) {
  109. case error, errors.Error:
  110. err = errors.New(datum.(error), 1)
  111. event.Error = err
  112. event.ErrorClass = err.TypeName()
  113. event.Message = err.Error()
  114. event.Stacktrace = make([]stackFrame, len(err.StackFrames()))
  115. case bool:
  116. config = config.merge(&Configuration{Synchronous: bool(datum)})
  117. case severity:
  118. event.Severity = datum
  119. event.handledState.OriginalSeverity = datum
  120. event.handledState.SeverityReason = SeverityReasonUserSpecified
  121. case Context:
  122. event.Context = datum.String
  123. case context.Context:
  124. populateEventWithContext(datum, event)
  125. case *http.Request:
  126. populateEventWithRequest(datum, event)
  127. case Configuration:
  128. config = config.merge(&datum)
  129. case MetaData:
  130. event.MetaData.Update(datum)
  131. case User:
  132. event.User = &datum
  133. case ErrorClass:
  134. event.ErrorClass = datum.Name
  135. case HandledState:
  136. event.handledState = datum
  137. event.Severity = datum.OriginalSeverity
  138. }
  139. }
  140. for i, frame := range err.StackFrames() {
  141. file := frame.File
  142. inProject := config.isProjectPackage(frame.Package)
  143. // remove $GOROOT and $GOHOME from other frames
  144. if idx := strings.Index(file, frame.Package); idx > -1 {
  145. file = file[idx:]
  146. }
  147. if inProject {
  148. file = config.stripProjectPackages(file)
  149. }
  150. event.Stacktrace[i] = stackFrame{
  151. Method: frame.Name,
  152. File: file,
  153. LineNumber: frame.LineNumber,
  154. InProject: inProject,
  155. }
  156. }
  157. return event, config
  158. }
  159. func populateEventWithContext(ctx context.Context, event *Event) {
  160. event.Ctx = ctx
  161. reqJSON, req := extractRequestInfo(ctx)
  162. if event.Request == nil {
  163. event.Request = reqJSON
  164. }
  165. populateEventWithRequest(req, event)
  166. }
  167. func populateEventWithRequest(req *http.Request, event *Event) {
  168. if req == nil {
  169. return
  170. }
  171. event.Request = extractRequestInfoFromReq(req)
  172. if event.Context == "" {
  173. event.Context = req.URL.Path
  174. }
  175. // Default user.id to IP so that the count of users affected works.
  176. if event.User == nil {
  177. ip := req.RemoteAddr
  178. if idx := strings.LastIndex(ip, ":"); idx != -1 {
  179. ip = ip[:idx]
  180. }
  181. event.User = &User{Id: ip}
  182. }
  183. }