How to create an Elixir provider for Raygun Crash Reporting (part one)
Posted Nov 4, 2016 | 6 min. (1180 words)With all the languages that Raygun Crash Reporting supports (over 30), we have been eagerly awaiting a chance to work on an Elixir provider. In this article, I’ll walk you through building a sample version to get you tracking and resolving errors.
To make this process a little more digestible, I’ve broken this post into a two part series.
In part one, I’ll go over creating a basic Elixir package to handle the REST API calls to the Raygun Crash Reporting service for error reporting. In part two, I’ll finish up the creation of the package and then add it to a todo list application adapted from an Elixir sample application on Todo-Backend.
Firstly, here’s a little background information on what you’ll need and why error tracking is important:
What do we need to get started?
Getting started:
Firstly, we’ll create a new package by running the following command in your terminal of choice:
mix new laserpistol4elixir
This will create a new Elixir project for us with some default files. Now, change directory to our new laserpistol4elixir project and open the project in your editor of choice.
The next thing we will do is start working on our mix.exs file in the main application directory.
Here is how our mix.exs file will look when we’re done: (I’ll go over what the code means below.)
defmodule Laserpistol4elixir.Mixfile do
use Mix.Project
def project do
[app: :laserpistol4elixir,
version: "0.1.0",
elixir: "~> 1.3",
build_embedded: Mix.env == :prod,
start_permanent: Mix.env == :prod,
deps: deps(),
description: description(),
name: "laserpistol4elixir"
]
end
def application do
[applications: [:logger, :hackney, :poison],
mod: {Laserpistol4elixir, []}
]
end
def deps do
[{:hackney, "~> 1.6"},
{:poison, "~> 3.0"}
]
end
def description() do
"Basic exception tracking tutorial."
end
def package() do
[maintainers: ["Jesse James"],
licenses: ["MIT"],
links: %{"GitHub" => "https://github.com/jrjamespdx/laserpistol4elixir"}
]
end
end
Breakdown:
def project do
[app: :laserpistol4elixir,
version: "0.1.0",
elixir: "~> 1.3",
build_embedded: Mix.env == :prod,
start_permanent: Mix.env == :prod,
deps: deps(),
description: description(),
name: "laserpistol4elixir"
]
end
This portion of the code specifies information about our project. In this case you can see the application name, version information, description, and dependencies:
def application do
[applications: [:logger, :hackney, :poison],
mod: {Laserpistol4elixir, []}
]
end
In Elixir, an application is a component implementing some specific functionality that can be started and stopped as a unit, and which can be re-used in other systems. Here, we’re defining which applications we will have running (logger, hackney, and poison) and an application call back (the mod: {Laserpistol4elixir, []} portion). If needed, specify the default environment variables for these applications.
The application call back is necessary for setting up a supervision tree (more on this later). This supervision tree will need to start and stop when the application starts and stops.
def deps do
[{:hackney, "~> 1.6"},
{:poison, "~> 3.0"}
]
end
Defining external dependencies here allows Hex, the Elixir package manager, to be used to install these dependencies. Specifically we’re telling the system to install any Hackney version 1.6 or greater up to 2.0 and Poison version 3.0 or greater up to 4.0.
def description() do
"Basic exception tracking tutorial."
end
def package() do
[maintainers: ["Jesse James"],
licenses: ["MIT"],
links: %{"GitHub" => "https://github.com/jrjamespdx/laserpistol4elixir"}
]
end
Here the description() function just returns a string of the application description for use in the project section.
The package() function outlines the package information regarding who is maintaining the project, what the license is, and any relevant links concerning the package. It is also used in the project section.
You will want to change these values to reflect your own name, license preference, and link locations.
The next file we’ll need to update is the config.exs located in our config directory. The config.exs file will contain any necessary configuration options for our package:
Config.exs
use Mix.Config
config :laserpistol4elixir,
api_key: "your_key_here",
environment: "production"
Breakdown:
use Mix.Config
This loads a module which allows our configuration options to be set in the application env and later accessed via Application functions.
config :laserpistol4elixir,
api_key: "your_key_here",
environment: "production"
Here, the application is being defined (:laserpistol4elixir ) and then the api_key and environment are being set. You’ll need to change the “your_key_here” to the Raygun Crash Reporting API key from your Raygun Crash Reporting application.
The next file we’ll need to update is the laserpistol4elixir.ex located in our lib directory. The lib directory will hold most of the actual code for the package:
Laserpistol4elixir.ex
defmodule Laserpistol4elixir do
use Application
def start(_type, _args) do
import Supervisor.Spec
api_key = get_config(:api_key)
children = [
worker(Laserpistol4elixir.Client, [api_key])
]
Supervisor.start_link(children, strategy: :one_for_one)
end
@spec track_exception(:error | :exit | :throw, any, [any]) :: :ok
def track_exception(kind, value, stacktrace)
when kind in [:error, :exit, :throw] and is_list(stacktrace) and is_map(custom) and is_map(occurrence_data) do
# provider and report calls will go here
end
end
defp get_config(key)
case Application.fetch_env(:laserpistol4elixir, key) do
{:ok, {:system, var}} when is_binary(var) -> System.get_env(var) ||
raise ArgumentError, "#{inspect(var)} not set"
{:ok, value} -> value
:error -> raise ArgumentError, "config param #{inspect(key)} not set"
end
end
end
Breakdown:
defmodule Laserpistol4elixir do
This line is just defining the laserpistol4elixir module itself:
use Application
Here we define that this module should be used to provide the application call back that we setup in mix.exs earlier:
def start(_type, _args) do
import Supervisor.Spec
api_key = get_config(:api_key)
environment = get_config(:environment)
children = [
worker(Laserpistol4elixir.Client, [api_key])
]
Supervisor.start_link(children, strategy: :one_for_one)
end
The start method will be run every time the application starts and serves as the directions for the application call back. The import Supervisor.Spec gives access to convenience functions for defining supervisor specifications. In this case it gives us the ability to specify the children to be used under the supervisor with Supervisor.start_link/2:
@spec track_exception(:error | :exit | :throw, any, [any]) :: :ok
def track_exception(kind, value, stacktrace)
when kind in [:error, :exit, :throw] and is_list(stacktrace) do
# provider and report calls will go here
end
end
@spec track_exception(:error | :exit | :throw, any, [any]) :: :ok is creating a typespec for the function that follows it, essentially indicating what type the result of the function will be in. In this case the function will be expected to return :ok .
The rest of this portion is the start of the track_exception function which we’ll work on more in part two of this blog post series on Elixir. For now, the function logic is set to check that the kind of the exception is one of the expected values (error, exit, or throw) and if the stacktrace is a list or not.
Summary
So far we’ve covered getting the basic setup done for our Elixir package. At this point we’ve created the Elixir package, updated the mix.exs , config.exs , and lasergun4elixir.ex files so we’re most of the way there. In part two we’ll finish the build out of the feature logic to send the error reports to Raygun Crash Reporting and then integrate the package with out todo list application.
Did you run into any issues so far or have any questions on what we did in part one? If so, let us know in the comments below!