package bugsnag import ( "context" "net/http" "strings" "github.com/bugsnag/bugsnag-go/errors" ) // Context is the context of the error in Bugsnag. // This can be passed to Notify, Recover or AutoNotify as rawData. type Context struct { String string } // User represents the searchable user-data on Bugsnag. The Id is also used // to determine the number of users affected by a bug. This can be // passed to Notify, Recover or AutoNotify as rawData. type User struct { Id string `json:"id,omitempty"` Name string `json:"name,omitempty"` Email string `json:"email,omitempty"` } // ErrorClass overrides the error class in Bugsnag. // This struct enables you to group errors as you like. type ErrorClass struct { Name string } // Sets the severity of the error on Bugsnag. These values can be // passed to Notify, Recover or AutoNotify as rawData. var ( SeverityError = severity{"error"} SeverityWarning = severity{"warning"} SeverityInfo = severity{"info"} ) // The severity tag type, private so that people can only use Error,Warning,Info type severity struct { String string } // The form of stacktrace that Bugsnag expects type StackFrame struct { Method string `json:"method"` File string `json:"file"` LineNumber int `json:"lineNumber"` InProject bool `json:"inProject,omitempty"` } type SeverityReason string const ( SeverityReasonCallbackSpecified SeverityReason = "userCallbackSetSeverity" SeverityReasonHandledError = "handledError" SeverityReasonHandledPanic = "handledPanic" SeverityReasonUnhandledError = "unhandledError" SeverityReasonUnhandledMiddlewareError = "unhandledErrorMiddleware" SeverityReasonUnhandledPanic = "unhandledPanic" SeverityReasonUserSpecified = "userSpecifiedSeverity" ) type HandledState struct { SeverityReason SeverityReason OriginalSeverity severity Unhandled bool Framework string } // Event represents a payload of data that gets sent to Bugsnag. // This is passed to each OnBeforeNotify hook. type Event struct { // The original error that caused this event, not sent to Bugsnag. Error *errors.Error // The rawData affecting this error, not sent to Bugsnag. RawData []interface{} // The error class to be sent to Bugsnag. This defaults to the type name of the Error, for // example *error.String ErrorClass string // The error message to be sent to Bugsnag. This defaults to the return value of Error.Error() Message string // The stacktrrace of the error to be sent to Bugsnag. Stacktrace []StackFrame // The context to be sent to Bugsnag. This should be set to the part of the app that was running, // e.g. for http requests, set it to the path. Context string // The severity of the error. Can be SeverityError, SeverityWarning or SeverityInfo. Severity severity // The grouping hash is used to override Bugsnag's grouping. Set this if you'd like all errors with // the same grouping hash to group together in the dashboard. GroupingHash string // User data to send to Bugsnag. This is searchable on the dashboard. User *User // Other MetaData to send to Bugsnag. Appears as a set of tabbed tables in the dashboard. MetaData MetaData // Ctx is the context of the session the event occurred in. This allows Bugsnag to associate the event with the session. Ctx context.Context // Request is the request information that populates the Request tab in the dashboard. Request *RequestJSON // The reason for the severity and original value handledState HandledState // True if the event was caused by an automatic event Unhandled bool } func newEvent(rawData []interface{}, notifier *Notifier) (*Event, *Configuration) { config := notifier.Config event := &Event{ RawData: append(notifier.RawData, rawData...), Severity: SeverityWarning, MetaData: make(MetaData), handledState: HandledState{ SeverityReason: SeverityReasonHandledError, OriginalSeverity: SeverityWarning, Unhandled: false, Framework: "", }, Unhandled: false, } var err *errors.Error var callbacks []func(*Event) for _, datum := range event.RawData { switch datum := datum.(type) { case error, errors.Error: err = errors.New(datum.(error), 1) event.Error = err // Only assign automatically if not explicitly set through ErrorClass already if event.ErrorClass == "" { event.ErrorClass = err.TypeName() } event.Message = err.Error() event.Stacktrace = make([]StackFrame, len(err.StackFrames())) case bool: config = config.merge(&Configuration{Synchronous: bool(datum)}) case severity: event.Severity = datum event.handledState.OriginalSeverity = datum event.handledState.SeverityReason = SeverityReasonUserSpecified case Context: event.Context = datum.String case context.Context: populateEventWithContext(datum, event) case *http.Request: populateEventWithRequest(datum, event) case Configuration: config = config.merge(&datum) case MetaData: event.MetaData.Update(datum) case User: event.User = &datum case ErrorClass: event.ErrorClass = datum.Name case HandledState: event.handledState = datum event.Severity = datum.OriginalSeverity event.Unhandled = datum.Unhandled case func(*Event): callbacks = append(callbacks, datum) } } event.Stacktrace = generateStacktrace(err, config) for _, callback := range callbacks { callback(event) if event.Severity != event.handledState.OriginalSeverity { event.handledState.SeverityReason = SeverityReasonCallbackSpecified } } return event, config } func generateStacktrace(err *errors.Error, config *Configuration) []StackFrame { stack := make([]StackFrame, len(err.StackFrames())) for i, frame := range err.StackFrames() { file := frame.File inProject := config.isProjectPackage(frame.Package) // remove $GOROOT and $GOHOME from other frames if idx := strings.Index(file, frame.Package); idx > -1 { file = file[idx:] } if inProject { file = config.stripProjectPackages(file) } stack[i] = StackFrame{ Method: frame.Name, File: file, LineNumber: frame.LineNumber, InProject: inProject, } } return stack } func populateEventWithContext(ctx context.Context, event *Event) { event.Ctx = ctx reqJSON, req := extractRequestInfo(ctx) if event.Request == nil { event.Request = reqJSON } populateEventWithRequest(req, event) } func populateEventWithRequest(req *http.Request, event *Event) { if req == nil { return } event.Request = extractRequestInfoFromReq(req) if event.Context == "" { event.Context = req.URL.Path } // Default user.id to IP so that the count of users affected works. if event.User == nil { ip := req.RemoteAddr if idx := strings.LastIndex(ip, ":"); idx != -1 { ip = ip[:idx] } event.User = &User{Id: ip} } }