This is an Airbrake webhook implemented in Go as a microservice. As part of its integrations, Airbrake allows the definition of a webhook (as described in the documentation.) An application using Airbrake is expected to provide an endpoint that gets called by Airbrake whenever a new error occurs, and this implementation provides a generic service to be exposed for that purpose - the received message is forwarded to a messaging exchange to be consumed asynchronously at a later point.
This microservice showcases the usage of several high-performance Go libraries and frameworks (detailed below), and also demonstrates how to send messages to a RabbitMQ exchange using a connection pool - the exchange's asynchronous consumer(s) must act on the received message, but that's outside the scope of this project.
Assuming that you've installed Go and have correctly set-up the $GOPATH
and $GOROOT
environment variables, do the following:
- Clone the repository: https://github.com/tappsi/airbrake-webhook.git
- Go to the
airbrake-webhook
directory - Install dependencies using
go get -u ./...
- Compile the project
go install github.com/tappsi/airbrake-webhook
Airbrake will invoke the endpoint exposed by this microservice whenever an error occurs. Each invocation will send a new message to our webhook using the following format:
{
"error":{
"id":37463546,
"error_message":"KitchenException: You are all out of bacon!",
"error_class":"KitchenException",
"file":"[PROJECT_ROOT]/app/controllers/bacon_controller.rb",
"line_number":35,
"project":{
"id":1111,
"name":"Baconator"
},
"last_notice":{
"id":4505303522,
"request_method":null,
"request_url":"http://airbrake.io:445/bacon/cook",
"backtrace":[
"[PROJECT_ROOT]/app/controllers/bacon_controller.rb:35:in `cook'",
"[PROJECT_ROOT]/app/middleware/kitchen.rb:19:in `oven'",
"[PROJECT_ROOT]/app/middleware/kitchen.rb:33:in `chef'",
"[PROJECT_ROOT]/app/middleware/salumi.rb:23:in `store'"
]
},
"environment":"avocado",
"first_occurred_at":"2012-02-23T22:03:03Z",
"last_occurred_at":"2012-03-21T08:37:15Z",
"times_occurred":118
},
"airbrake_error_url": "https://airbrake.io/airbrake-error-url"
}
To handle the messages, we have to start the service:
- Create the
config/production.json
file with values appropriate for your environment, useconfig/development.json
as a guide, see the "Configuration" section below for more details. - Configure Airbrake. Go to your project's Dashboard -> Settings -> Integrations and set the URL where the service is going to live. This is defined based on the parameters configured in the previous step.
- Go to the
bin
directory of your Go projects and run the executable, passing the appropriate environment. For example:GO_ENV=production $GOPATH/bin/airbrake-webhook
- The previous step will leave a web server running, listening for calls into the configured endpoint. To kill the service, simply press
Ctrl+C
.
I just described a very basic setup for the service, in a real production environment you might have a separate web server acting as proxy and handling secure connections and redirections to the actual service. Also you should take all previsions to ensure that the service never goes down, for instance by using supervisord
- but this is outside the scope of this document.
Now it's up to you to decide how to handle the message. By default, this microservice parses the JSON with the received message, extracts some of its fields and sends a new message to a RabbitMQ exchange for further processing; the newly created message has the following structure:
{
"service": "mattermost",
"recipients": ["opslog"],
"message": "Environment: production,
Occurrences: 1,
Error ID: 37463546,
Error URL: https://airbrake.io/airbrake-error-url,
Error Message: missing attribute"
}
In our production environment, we have a different process that reads from the exchange and sends the message to a Mattermost log chat for all developers to see. If you want to do something different, modify the Process()
function in webhook.go
as required.
The project is structured as follows:
- A
main.go
file in themain
package. - All the
.go
logic files reside inside thewebhook
package. - Configuration files in
.json
format, residing in theconfig
directory.
The main
package contains a single file, main.go
, in charge of starting up the application:
main.go
: Creates a pool of RMQ connections, starts the web server in charge of handling requests to the exposed endpoint and closes the pool when the application exits.
The main
package contains all the files implementing the application's logic:
config.go
: Reads and parses JSON configuration files using Go's standard JSON libraries. The appropriate file is processed depending on the execution environment set in theGO_ENV
environment variable. IfGO_ENV
was not specified, uses the"development"
environment by default.connection-pool.go
: Adapts a generic pool library for handling a pool of RMQ's connections. Implements thePooledObject
interface, required bygo-commons-pool
.easyjson.go
: File auto-generated using theeasyjson
library, customized for performing fast encoding/decoding of notification JSONs.messaging-queue.go
: Implements the functionality for creating a connection to a RabbitMQ server, sending messages to it and freeing-up resources afterwards. The connections are handled using a pool; seeconnection-pool.go
. Several QoS attributes can be configured, check RMQ's andamqp
's documentation.util.go
: Utility functions for dealing with strings, errors, etc.webhook.go
: The actual webhook. Implements aniris
handler for processing an http request, parses the JSON message sent by Airbrake, creates a new JSON message with the relevant fields and publishes it to a RabbitMQ exchange for further asynchronous processing.
The webhook's configuration is handled via JSON files, located in the config
directory. Create one for each of the environments, for instance: development.json
, production.json
. The expected format is:
{
"webserver-port": 8181,
"endpoint-name": "airbrake-webhook",
"exchange-name": "notifications_dev",
"queue-uri": "amqp://test:test@192.168.1.13:5672",
"pool-config": {
"max-total": 10,
"min-idle": 0,
"max-idle": 10
}
}
Each of the configuration options is detailed below:
webserver-port
: Port where the web server runs.endpoint-name
: Path name of the exposed service.exchange-name
: Name of the exchange used to publish messages.queue-uri
: URL and credentials for RMQ.max-total
: Maximum number of total connections to be opened.min-idle
: Minimum number of idle connections allowed.max-idle
: Maximum number of idle connections allowed.
Several high-performance, third party open source libraries and frameworks were used for writing this project. This was done for showcasing the best libraries for each job, given Go's emphasis on high-speed processing:
iris
: According to Iris' benchmarks, this is the fastest web framework for Go, starting up its own web server. It's used for exposing a RESTful endpoint consumed by Airbrake. Bear in mind that Iris is a recent framework and somewhat unstable.amqp
: The standard Go client for AMQP, used for connecting to a RabbitMQ server and sending messages to it.go-commons-pool
: A generic object pool for Go, the connection to RabbitMQ are pooled using this library.jsonparser
: Alternative JSON parser for Go that does not require schema, it's the fastest parser for decoding a JSON object. Used for decoding the message sent by Airbrake.easyjson
: Fast JSON serializer for Go, used for encoding the message sent to a RMQ exchange.
Current: v1.0.0
The author of airbrake-webhook
is @oscar-lopez, it was developed as an internal project in Tappsi, and now it's open source.
If you have any issues or questions regarding airbrake-webhook
, feel free to contact the author using any of the following channels, or post a question in StackOverflow:
Unless otherwise noted, the airbrake-webhook
source files are distributed under the MIT License found in the LICENSE file.