diff --git a/bugsnag_test.go b/bugsnag_test.go index f7c9a139..87f4d428 100644 --- a/bugsnag_test.go +++ b/bugsnag_test.go @@ -95,7 +95,7 @@ func TestNotify(t *testing.T) { md := MetaData{"test": {"password": "sneaky", "value": "able", "broken": complex(1, 2), "recurse": recurse}} user := User{Id: "123", Name: "Conrad", Email: "me@cirw.in"} config := generateSampleConfig(ts.URL) - Notify(fmt.Errorf("hello world"), StartSession(context.Background()), config, user, Context{"testing"}, md) + Notify(fmt.Errorf("hello world"), StartSession(context.Background()), config, user, ErrorClass{Name: "ExpectedErrorClass"}, Context{"testing"}, md) json, err := simplejson.NewJson(<-reports) @@ -116,7 +116,7 @@ func TestNotify(t *testing.T) { Unhandled: false, Request: &RequestJSON{}, User: &User{Id: "123", Name: "Conrad", Email: "me@cirw.in"}, - Exceptions: []exceptionJSON{{ErrorClass: "*errors.errorString", Message: "hello world"}}, + Exceptions: []exceptionJSON{{ErrorClass: "ExpectedErrorClass", Message: "hello world"}}, }) assertValidSession(t, event, handled) diff --git a/event.go b/event.go index bdb7e251..cefbf65e 100644 --- a/event.go +++ b/event.go @@ -130,7 +130,10 @@ func newEvent(rawData []interface{}, notifier *Notifier) (*Event, *Configuration case error, errors.Error: err = errors.New(datum.(error), 1) event.Error = err - event.ErrorClass = err.TypeName() + // 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())) diff --git a/features/fixtures/app/main.go b/features/fixtures/app/main.go index be246343..8330184a 100644 --- a/features/fixtures/app/main.go +++ b/features/fixtures/app/main.go @@ -135,7 +135,11 @@ func unhandledCrash() { func handledError() { if _, err := os.Open("nonexistent_file.txt"); err != nil { - bugsnag.Notify(err) + if errClass := os.Getenv("ERROR_CLASS"); errClass != "" { + bugsnag.Notify(err, bugsnag.ErrorClass{Name: errClass}) + } else { + bugsnag.Notify(err) + } } // Give some time for the error to be sent before exiting time.Sleep(200 * time.Millisecond) diff --git a/features/fixtures/docker-compose.yml b/features/fixtures/docker-compose.yml index 06f9d973..6d39e1b2 100644 --- a/features/fixtures/docker-compose.yml +++ b/features/fixtures/docker-compose.yml @@ -8,6 +8,7 @@ services: - GO_VERSION environment: - API_KEY + - ERROR_CLASS - BUGSNAG_ENDPOINT - APP_VERSION - APP_TYPE @@ -30,6 +31,7 @@ services: - "4512:4512" environment: - API_KEY + - ERROR_CLASS - BUGSNAG_ENDPOINT - APP_VERSION - APP_TYPE @@ -54,6 +56,7 @@ services: - "4511:4511" environment: - API_KEY + - ERROR_CLASS - BUGSNAG_ENDPOINT - APP_VERSION - APP_TYPE @@ -77,6 +80,7 @@ services: - "4513:4513" environment: - API_KEY + - ERROR_CLASS - BUGSNAG_ENDPOINT - APP_VERSION - APP_TYPE @@ -101,6 +105,7 @@ services: - "4514:4514" environment: - API_KEY + - ERROR_CLASS - BUGSNAG_ENDPOINT - APP_VERSION - APP_TYPE @@ -126,6 +131,7 @@ services: - "4515:4515" environment: - API_KEY + - ERROR_CLASS - BUGSNAG_ENDPOINT - APP_VERSION - APP_TYPE diff --git a/features/fixtures/gin/main.go b/features/fixtures/gin/main.go index df2d3cf9..75416a51 100644 --- a/features/fixtures/gin/main.go +++ b/features/fixtures/gin/main.go @@ -71,7 +71,11 @@ func unhandledCrash(c *gin.Context) { func handledError(c *gin.Context) { if _, err := os.Open("nonexistent_file.txt"); err != nil { - bugsnag.Notify(err, c.Request.Context()) + if errClass := os.Getenv("ERROR_CLASS"); errClass != "" { + bugsnag.Notify(err, c.Request.Context(), bugsnag.ErrorClass{Name: errClass}) + } else { + bugsnag.Notify(err, c.Request.Context()) + } } } diff --git a/features/fixtures/martini/main.go b/features/fixtures/martini/main.go index 00c33f9d..feb0acd5 100644 --- a/features/fixtures/martini/main.go +++ b/features/fixtures/martini/main.go @@ -70,7 +70,11 @@ func unhandledCrash() { func handledError(r *http.Request) { if _, err := os.Open("nonexistent_file.txt"); err != nil { - bugsnag.Notify(err, r.Context()) + if errClass := os.Getenv("ERROR_CLASS"); errClass != "" { + bugsnag.Notify(err, r.Context(), bugsnag.ErrorClass{Name: errClass}) + } else { + bugsnag.Notify(err, r.Context()) + } } } diff --git a/features/fixtures/negroni/main.go b/features/fixtures/negroni/main.go index 165b792a..ac9bcefa 100644 --- a/features/fixtures/negroni/main.go +++ b/features/fixtures/negroni/main.go @@ -74,7 +74,11 @@ func unhandledCrash(w http.ResponseWriter, r *http.Request) { func handledError(w http.ResponseWriter, r *http.Request) { if _, err := os.Open("nonexistent_file.txt"); err != nil { - bugsnag.Notify(err, r.Context()) + if errClass := os.Getenv("ERROR_CLASS"); errClass != "" { + bugsnag.Notify(err, r.Context(), bugsnag.ErrorClass{Name: errClass}) + } else { + bugsnag.Notify(err, r.Context()) + } } } diff --git a/features/fixtures/net_http/main.go b/features/fixtures/net_http/main.go index 2556804f..ebbfe3ca 100644 --- a/features/fixtures/net_http/main.go +++ b/features/fixtures/net_http/main.go @@ -76,7 +76,11 @@ func configureBasicBugsnag() { func handledError(w http.ResponseWriter, r *http.Request) { if _, err := os.Open("nonexistent_file.txt"); err != nil { - bugsnag.Notify(err, r.Context()) + if errClass := os.Getenv("ERROR_CLASS"); errClass != "" { + bugsnag.Notify(err, r.Context(), bugsnag.ErrorClass{Name: errClass}) + } else { + bugsnag.Notify(err, r.Context()) + } } } diff --git a/features/fixtures/revel/app/controllers/app.go b/features/fixtures/revel/app/controllers/app.go index 370c602c..de3447eb 100644 --- a/features/fixtures/revel/app/controllers/app.go +++ b/features/fixtures/revel/app/controllers/app.go @@ -18,7 +18,11 @@ func (c App) Index() revel.Result { func (c App) Handled() revel.Result { if _, err := os.Open("nonexistent_file.txt"); err != nil { - bugsnag.Notify(err, c.Args["context"]) + if errClass := os.Getenv("ERROR_CLASS"); errClass != "" { + bugsnag.Notify(err, c.Args["context"], bugsnag.ErrorClass{Name: errClass}) + } else { + bugsnag.Notify(err, c.Args["context"]) + } } return c.Render() } diff --git a/features/gin_features/handled.feature b/features/gin_features/handled.feature index a1179588..0999254c 100644 --- a/features/gin_features/handled.feature +++ b/features/gin_features/handled.feature @@ -18,3 +18,17 @@ Scenario: A handled error sends a report And the event "severityReason.type" equals "handledError" for request 0 And the exception "errorClass" equals "*os.PathError" for request 0 And the "file" of stack frame 0 equals "main.go" for request 0 + +Scenario: A handled error sends a report with a custom name + Given I set environment variable "ERROR_CLASS" to "MyCustomErrorClass" + When I start the service "gin" + And I wait for the app to open port "4511" + And I wait for 2 seconds + And I open the URL "http://localhost:4511/handled" + Then I wait to receive a request + And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" + And the event "unhandled" is false for request 0 + And the event "severity" equals "warning" for request 0 + And the event "severityReason.type" equals "handledError" for request 0 + And the exception "errorClass" equals "MyCustomErrorClass" for request 0 + And the "file" of stack frame 0 equals "main.go" for request 0 diff --git a/features/martini_features/handled.feature b/features/martini_features/handled.feature index 21fe430f..0c0b9ec7 100644 --- a/features/martini_features/handled.feature +++ b/features/martini_features/handled.feature @@ -18,3 +18,17 @@ Scenario: A handled error sends a report And the event "severityReason.type" equals "handledError" for request 0 And the exception "errorClass" equals "*os.PathError" for request 0 And the "file" of stack frame 0 equals "main.go" for request 0 + +Scenario: A handled error sends a report with a custom name + Given I set environment variable "ERROR_CLASS" to "MyCustomErrorClass" + When I start the service "martini" + And I wait for the app to open port "4513" + And I wait for 2 seconds + And I open the URL "http://localhost:4513/handled" + Then I wait to receive a request + And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" + And the event "unhandled" is false for request 0 + And the event "severity" equals "warning" for request 0 + And the event "severityReason.type" equals "handledError" for request 0 + And the exception "errorClass" equals "MyCustomErrorClass" for request 0 + And the "file" of stack frame 0 equals "main.go" for request 0 diff --git a/features/negroni_features/handled.feature b/features/negroni_features/handled.feature index 24d105ef..9e7faab8 100644 --- a/features/negroni_features/handled.feature +++ b/features/negroni_features/handled.feature @@ -18,3 +18,17 @@ Scenario: A handled error sends a report And the event "severityReason.type" equals "handledError" for request 0 And the exception "errorClass" equals "*os.PathError" for request 0 And the "file" of stack frame 0 equals "main.go" for request 0 + +Scenario: A handled error sends a report with a custom name + Given I set environment variable "ERROR_CLASS" to "MyCustomErrorClass" + When I start the service "negroni" + And I wait for the app to open port "4514" + And I wait for 2 seconds + And I open the URL "http://localhost:4514/handled" + Then I wait to receive a request + And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" + And the event "unhandled" is false for request 0 + And the event "severity" equals "warning" for request 0 + And the event "severityReason.type" equals "handledError" for request 0 + And the exception "errorClass" equals "MyCustomErrorClass" for request 0 + And the "file" of stack frame 0 equals "main.go" for request 0 diff --git a/features/net_http_features/handled.feature b/features/net_http_features/handled.feature index 22890084..4c814b4a 100644 --- a/features/net_http_features/handled.feature +++ b/features/net_http_features/handled.feature @@ -18,3 +18,17 @@ Scenario: A handled error sends a report And the event "severityReason.type" equals "handledError" for request 0 And the exception "errorClass" equals "*os.PathError" for request 0 And the "file" of stack frame 0 equals "main.go" for request 0 + +Scenario: A handled error sends a report with a custom name + Given I set environment variable "ERROR_CLASS" to "MyCustomErrorClass" + When I start the service "nethttp" + And I wait for the app to open port "4512" + And I wait for 2 seconds + And I open the URL "http://localhost:4512/handled" + Then I wait to receive a request + And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" + And the event "unhandled" is false for request 0 + And the event "severity" equals "warning" for request 0 + And the event "severityReason.type" equals "handledError" for request 0 + And the exception "errorClass" equals "MyCustomErrorClass" for request 0 + And the "file" of stack frame 0 equals "main.go" for request 0 diff --git a/features/plain_features/handled.feature b/features/plain_features/handled.feature index f82f1cea..e74c38eb 100644 --- a/features/plain_features/handled.feature +++ b/features/plain_features/handled.feature @@ -15,3 +15,14 @@ Scenario: A handled error sends a report And the event "severityReason.type" equals "handledError" And the exception "errorClass" equals "*os.PathError" And the "file" of stack frame 0 equals "main.go" + +Scenario: A handled error sends a report with a custom name + Given I set environment variable "ERROR_CLASS" to "MyCustomErrorClass" + When I run the go service "app" with the test case "handled" + Then I wait to receive a request + And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" + And the event "unhandled" is false + And the event "severity" equals "warning" + And the event "severityReason.type" equals "handledError" + And the exception "errorClass" equals "MyCustomErrorClass" + And the "file" of stack frame 0 equals "main.go" diff --git a/features/revel_features/handled.feature b/features/revel_features/handled.feature index c67730e2..72f23e2d 100644 --- a/features/revel_features/handled.feature +++ b/features/revel_features/handled.feature @@ -18,3 +18,17 @@ Scenario: A handled error sends a report And the event "severityReason.type" equals "handledError" for request 0 And the exception "errorClass" equals "*os.PathError" for request 0 And the "file" of stack frame 0 equals "controllers/app.go" for request 0 + +Scenario: A handled error sends a report with a custom name + Given I set environment variable "ERROR_CLASS" to "MyCustomErrorClass" + When I start the service "revel" + And I wait for the app to open port "4515" + And I wait for 4 seconds + And I open the URL "http://localhost:4515/handled" + Then I wait to receive a request + And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" + And the event "unhandled" is false for request 0 + And the event "severity" equals "warning" for request 0 + And the event "severityReason.type" equals "handledError" for request 0 + And the exception "errorClass" equals "MyCustomErrorClass" for request 0 + And the "file" of stack frame 0 equals "controllers/app.go" for request 0