How to create an Elixir provider for Raygun Crash Reporting (part one)

| 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!