I highly recommend trying a crash reporting services, like Sentry or Raygun. You will discover a lot of errors that you have never discovered during testing.
What if your organization feels uneasy trusting crash data (and source) to a 3rd party? Luckily, you can stand your own Sentry server, because it is an open source project, hosted at github.com/getsentry/sentry. But for purely client-side browser crash reporting it has a huge drawback: it only allows sending exceptions via GET transport. There are open issues and even a pull request but no resolution yet.
The main drawback of using GET requests is that all information must be encoded in the URL, that has limits (most importantly, IE limits the length of the URL to about 1-2 KB). Thus all my extra information about the state of the program when the crash happened might be trimmed.
On the other hand, Raygun uses POST to send the exceptions by default and it works beautifully. Yet, Raygun is not open source, and we cannot just deploy and host it ourselves.
In this blog post I will show how stand your own simple crash server. The code is hosted at bahmutov/error-receiver
- Runs on Nodejs
- Compatible with Raygun clients, like raygun4js
- Only exposes a single end point that receives the crash data via POST
- Checks the API key against environment variable to prevent unauthorized submissions
Raygun4js client
First, I investigated the raygun4js client library that catches the JavaScript exceptions in the browser and sends the data to the server. It is MIT licensed (nice!) and seems to be well-maintained and under active development. I created a sample page, trying to see what it takes to send the error to our own server. I am using the unminified Raygun client straight from the CDN for this demo.
1 |
|
I hosted the page using http-server running on port 3004
The error was sent by the rg4js
to the server
Request URL:http://localhost:3004/crash/entries?apikey=demo-api-key
Request Method:POST
The important request headers
Content-Length:1104
Content-Type:text/plain;charset=UTF-8
Cookie:raygun4js-userid=63db206a-e9c4-13df-25b0-a8c091f041f5
The error object has the following data
1 | { |
I highly recommend to provide tags, version and (if available) user information with each error, for now these fields are sent empty.
Server implementation
I decided to use the simplest HTTP server implementation. The first implementation is below. To see the latest code, look at the server source file.
1 | var config = require('./src/config'); |
To test the server from the command line, I used httpie tool.
$ http POST localhost:3004/foo?apiKey=demo-api-key key=value another=value
HTTP/1.1 200 OK
Access-Control-Allow-Headers: Content-Type
Access-Control-Allow-Methods: POST
Access-Control-Allow-Origin: *
Connection: keep-alive
Content-Type: text/plain
Date: Fri, 13 Nov 2015 20:52:50 GMT
Transfer-Encoding: chunked
Hello there
The server parsed the query and the payload parameters
POST - /foo?apiKey=demo-api-key query { apiKey: 'demo-api-key' }
{ key: 'value', another: 'value' }
Any other request gets 4xx response
$ http localhost:3004
HTTP/1.1 400 Bad Request
Connection: keep-alive
Content-Type: text/plain
Date: Fri, 13 Nov 2015 20:17:28 GMT
Transfer-Encoding: chunked
Invalid request
To test from the actual page, I opened the above HTML (saved in test-page/index.html
)
and the server received the correct data
1 | allowed api key "demo-api-key" at end point "/crash/entries |
Future work
I just showed the error receiver, and even this code is just the beginning. There is plenty of features that can be implemented to intelligently group and process exceptions. For example, one can open GitHub issues or attach comments to existing ones using my github-issue-filer module. One should also support a dynamic list of API keys to check. Each project should get its own API key to prevent error collisions.
Follow me at @bahmutov and github.com/bahmutov to get updates on this project.