Faulty (Faulty v0.1.5)

View Source

En Elixir-based error tracking solution.

The main objectives behind this project are:

  • Provide a basic free error tracking solution: because tracking errors in your application should be a requirement for almost any project, and helps to provide quality and maintenance to your project.

Requirements

Faulty requires Elixir 1.15+

Integrations

We currently include integrations for what we consider the basic stack of an application: Phoenix, Plug, Oban and Quantum.

If you want to manually report an error, you can use the Faulty.message/2 function.

Context

Aside from the information about each exception (kind, message, stack trace...) we also store contexts.

Contexts are arbitrary maps that allow you to store extra information about an exception to be able to reproduce it later.

Each integration includes a default context with useful information they can gather, but aside from that, you can also add your own information. You can do this in a per-process basis or in a per-call basis (or both).

There are some requirements on the type of data that can be included in the context, so we recommend taking a look at set_context/1 documentation.

Per process

This allows you to set a general context for the current process such as a Phoenix request or an Quantum or Oban job. For example, you could include the following code in your authentication Plug to automatically include the user ID in any error that is tracked during the Phoenix request handling.

Faulty.set_context(%{user_id: conn.assigns.current_user.id})

Per call

As we had seen before, you can use Faulty.message/2 to manually report an error. The second parameter of this function is optional and allows you to include extra context that will be tracked along with the error.

Summary

Types

A map containing the relevant context for a particular error.

An Exception or a {kind, payload} tuple compatible with Exception.normalize/3.

Functions

Obtain the context of the current process.

Reports a message to be stored.

Report an exception to be stored.

Sets the current process context.

Types

context()

@type context() :: %{required(String.t() | atom()) => any()}

A map containing the relevant context for a particular error.

exception()

@type exception() ::
  Exception.t() | {:error, any()} | {Exception.non_error_kind(), any()}

An Exception or a {kind, payload} tuple compatible with Exception.normalize/3.

Functions

get_context()

@spec get_context() :: context()

Obtain the context of the current process.

message(message, given_context \\ %{})

@spec message(binary() | exception(), context()) :: :ok | :noop

Reports a message to be stored.

Returns :ok stored or :noop if the Faulty is disabled by configuration the exception has not been stored.

This allows you to store a message or exception manually, the stacktrace will be added automatically.

Faulty.message("Invalid user or password", %{login: login, password: password})

Faulty.message({ArgumentError, "Invalid user or password"}, %{login: login, password: password})

Exceptions

Exceptions can be passed in three different forms:

  • A binary: This will be stored as an ErlangError.

  • An exception struct: the module of the exception is stored along with the exception message.

  • A {kind, exception} tuple in which case the information is converted to an Elixir exception (if possible) and stored.

This function can also be used to test your setup.

report(exception, stacktrace, given_context \\ %{})

@spec report(exception(), Exception.stacktrace(), context()) :: :ok | :noop

Report an exception to be stored.

Returns :ok stored or :noop if the Faulty is disabled by configuration the exception has not been stored.

Aside from the exception, it is expected to receive the stack trace and, optionally, a context map which will be merged with the current process context.

Keep in mind that errors that occur in Phoenix controllers, Phoenix LiveViews and Quantum and Oban jobs are automatically reported. You will need this function only if you want to report custom errors.

try do
  # your code
catch
  e ->
    Faulty.report(e, __STACKTRACE__)
end

Exceptions

Exceptions can be passed as:

  • An exception struct: the module of the exception is stored along with the exception message.

  • A {kind, exception} tuple in which case the information is converted to an Elixir exception (if possible) and stored.

set_context(params)

@spec set_context(context()) :: context()

Sets the current process context.

The given context will be merged into the current process context. The given context may override existing keys from the current process context.

Context depth

You can store context on more than one level of depth, but take into account that the merge operation is performed on the first level.

That means that any existing data on deep levels for he current context will be replaced if the first level key is received on the new contents.

Content serialization

The content stored on the context should be serializable using the JSON library used by the application (usually Jason), so it is rather recommended to use primitive types (strings, numbers, booleans...).

If you still need to pass more complex data types to your context, please test that they can be encoded to JSON or storing the errors will fail. In the case of Jason that may require defining an Encoder for that data type if not included by default.