View Source ErrorTracker (ErrorTracker v0.5.0)
En Elixir-based built-in 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.
Be easy to use: by providing plug-and-play integrations, documentation and a simple UI to manage your errors.
Be as minimalistic as possible: you just need a database to store errors and a Phoenix application if you want to inspect them via web. That's all.
Requirements
ErrorTracker requires Elixir 1.15+, Ecto 3.11+, Phoenix LiveView 0.19+, and PostgreSQL, MySQL/MariaDB or SQLite3 as database.
Integrations
We currently include integrations for what we consider the basic stack of an application: Phoenix, Plug, and Oban.
However, we may continue working in adding support for more systems and libraries in the future if there is enough interest from the community.
If you want to manually report an error, you can use the ErrorTracker.report/3
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 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.
ErrorTracker.set_context(%{user_id: conn.assigns.current_user.id})
Per call
As we had seen before, you can use ErrorTracker.report/3
to manually report an
error. The third parameter of this function is optional and allows you to include
extra context that will be tracked along with the error.
Breadcrumbs
Aside from contextual information, it is sometimes useful to know in which points of your code the code was executed in a given request / process.
Using breadcrumbs allows you to add that information to any error generated and
stored on a given process / request. And if you are using Ash
or Splode
their
exceptions' breadcrumbs will be automatically populated.
If you want to add a breadcrumb in a point of your code you can do so:
ErrorTracker.add_breadcrumb("Executed my super secret code")
Breadcrumbs can be viewed in the dashboard on the details page of an occurrence.
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
Adds a breadcrumb to the current process.
Obtain the breadcrumbs of the current process.
Obtain the context of the current process.
Report an exception to be stored.
Marks an error as resolved.
Sets the current process context.
Marks an error as unresolved.
Types
A map containing the relevant context for a particular error.
@type exception() :: Exception.t() | {:error, any()} | {Exception.non_error_kind(), any()}
An Exception
or a {kind, payload}
tuple compatible with Exception.normalize/3
.
Functions
Adds a breadcrumb to the current process.
The new breadcrumb will be added as the most recent entry of the breadcrumbs list.
Breadcrumbs limit
Breadcrumbs are a powerful tool that allows to add an infinite number of entries. However, it is not recommended to store errors with an excessive amount of breadcrumbs.
As they are stored as an array of strings under the hood, storing many entries per error can lead to some delays and using extra disk space on the database.
@spec get_breadcrumbs() :: [String.t()]
Obtain the breadcrumbs of the current process.
@spec get_context() :: context()
Obtain the context of the current process.
@spec report(exception(), Exception.stacktrace(), context()) :: ErrorTracker.Occurrence.t() | :noop
Report an exception to be stored.
Returns the occurrence stored or :noop
if the ErrorTracker 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 Oban jobs are automatically reported. You will need this function only if you want to report custom errors.
try do
# your code
catch
e ->
ErrorTracker.report(e, __STACKTRACE__)
end
Exceptions
Exceptions can be passed in three different forms:
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.
@spec resolve(ErrorTracker.Error.t()) :: {:ok, ErrorTracker.Error.t()} | {:error, Ecto.Changeset.t()}
Marks an error as resolved.
If an error is marked as resolved and it happens again, it will automatically appear as unresolved again.
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.
@spec unresolve(ErrorTracker.Error.t()) :: {:ok, ErrorTracker.Error.t()} | {:error, Ecto.Changeset.t()}
Marks an error as unresolved.