Current section
Files
Jump to
Current section
Files
lib/checkend.ex
defmodule Checkend do
@moduledoc """
Elixir SDK for [Checkend](https://checkend.com) error monitoring and exception tracking.
Checkend provides simple, powerful error monitoring for Elixir and Phoenix applications.
This SDK enables automatic exception capture, error tracking, and real-time alerting
for production applications.
## Features
* **Async by default** - Non-blocking error reporting via GenServer
* **Phoenix & Plug integration** - Automatic request context capture
* **User tracking** - Associate errors with specific users
* **Sensitive data filtering** - Automatic scrubbing of passwords, tokens, etc.
* **Testing utilities** - Capture errors in tests without HTTP calls
## Quick Start
# Configure the SDK
Checkend.configure(api_key: "your-api-key")
# Report an error
try do
do_something()
rescue
e -> Checkend.notify(e, __STACKTRACE__)
end
## Configuration
Checkend.configure(
api_key: "your-api-key",
endpoint: "https://app.checkend.com",
environment: "production",
enabled: true
)
See `configure/1` for all available options.
## Guides
* [Getting Started](getting-started.html) - Installation and basic setup
* [Phoenix Integration](phoenix-integration.html) - Phoenix and LiveView setup
* [Testing Guide](testing.html) - Testing error reporting
## Links
* [Checkend](https://checkend.com) - Error monitoring service
* [GitHub](https://github.com/Checkend/checkend-elixir) - Source code
* Project sponsored by [Furvur](https://furvur.com)
"""
@version "1.0.0"
alias Checkend.{Configuration, Notice, NoticeBuilder, Client, Worker, Testing}
alias Checkend.Filters.IgnoreFilter
@doc """
Returns the SDK version.
"""
def version, do: @version
@doc """
Configure the Checkend SDK.
## Options
* `:api_key` - Your Checkend ingestion API key (required)
* `:endpoint` - API endpoint URL (default: "https://app.checkend.com")
* `:environment` - Environment name (auto-detected if not provided)
* `:enabled` - Whether error reporting is enabled (default: true in production)
* `:async_send` - Whether to send errors asynchronously (default: true)
* `:max_queue_size` - Maximum queue size for async sending (default: 1000)
* `:timeout` - HTTP request timeout in milliseconds (default: 15000)
* `:filter_keys` - List of keys to filter from payloads
* `:ignored_exceptions` - List of exception modules to ignore
* `:before_notify` - List of callback functions to run before sending
* `:debug` - Enable debug logging (default: false)
## Examples
Checkend.configure(api_key: "your-api-key")
Checkend.configure(
api_key: "your-api-key",
environment: "staging",
filter_keys: ["custom_secret"]
)
"""
@spec configure(keyword()) :: :ok
def configure(opts \\ []) do
config = Configuration.new(opts)
Application.put_env(:checkend, :configuration, config)
if config.async_send and config.enabled do
Worker.start()
end
:ok
end
@doc """
Get the current configuration.
"""
@spec get_configuration() :: Configuration.t() | nil
def get_configuration do
Application.get_env(:checkend, :configuration)
end
@doc """
Report an exception to Checkend asynchronously.
## Options
* `:context` - Additional context data
* `:user` - User information
* `:request` - Request information
* `:fingerprint` - Custom fingerprint for grouping
* `:tags` - List of tags for the error
## Examples
try do
do_something()
rescue
e ->
Checkend.notify(e, __STACKTRACE__)
end
Checkend.notify(error, stacktrace,
context: %{order_id: 123},
user: %{id: "user-1"},
tags: ["critical"]
)
"""
@spec notify(Exception.t(), list(), keyword()) :: :ok | {:error, term()}
def notify(exception, stacktrace, opts \\ []) do
config = get_configuration()
cond do
is_nil(config) ->
{:error, :not_configured}
not config.enabled ->
:ok
should_ignore?(exception, config) ->
:ok
true ->
notice = build_notice(exception, stacktrace, opts, config)
case run_before_notify(notice, config) do
{:ok, notice} ->
if Testing.enabled?() do
Testing.add_notice(notice)
:ok
else
if config.async_send do
Worker.push(notice)
else
Client.send(notice, config)
end
end
:skip ->
:ok
end
end
end
@doc """
Report an exception to Checkend synchronously.
Returns the API response or an error.
"""
@spec notify_sync(Exception.t(), list(), keyword()) ::
{:ok, map()} | {:error, term()}
def notify_sync(exception, stacktrace, opts \\ []) do
config = get_configuration()
cond do
is_nil(config) ->
{:error, :not_configured}
not config.enabled ->
{:ok, %{}}
should_ignore?(exception, config) ->
{:ok, %{}}
true ->
notice = build_notice(exception, stacktrace, opts, config)
case run_before_notify(notice, config) do
{:ok, notice} ->
if Testing.enabled?() do
Testing.add_notice(notice)
{:ok, %{id: 0, problem_id: 0}}
else
Client.send(notice, config)
end
:skip ->
{:ok, %{}}
end
end
end
@doc """
Set context data for the current process.
"""
@spec set_context(map()) :: :ok
def set_context(context) when is_map(context) do
current = Process.get(:checkend_context, %{})
Process.put(:checkend_context, Map.merge(current, context))
:ok
end
@doc """
Get the current context data.
"""
@spec get_context() :: map()
def get_context do
Process.get(:checkend_context, %{})
end
@doc """
Set user information for the current process.
"""
@spec set_user(map()) :: :ok
def set_user(user) when is_map(user) do
Process.put(:checkend_user, user)
:ok
end
@doc """
Get the current user information.
"""
@spec get_user() :: map()
def get_user do
Process.get(:checkend_user, %{})
end
@doc """
Set request information for the current process.
"""
@spec set_request(map()) :: :ok
def set_request(request) when is_map(request) do
Process.put(:checkend_request, request)
:ok
end
@doc """
Get the current request information.
"""
@spec get_request() :: map()
def get_request do
Process.get(:checkend_request, %{})
end
@doc """
Clear all context, user, and request data.
"""
@spec clear() :: :ok
def clear do
Process.delete(:checkend_context)
Process.delete(:checkend_user)
Process.delete(:checkend_request)
:ok
end
@doc """
Wait for all queued notices to be sent.
"""
@spec flush(timeout()) :: :ok
def flush(timeout \\ 5000) do
Worker.flush(timeout)
end
@doc """
Stop the worker.
"""
@spec stop() :: :ok
def stop do
Worker.stop()
end
@doc """
Reset all state (useful for testing).
"""
@spec reset() :: :ok
def reset do
stop()
Application.delete_env(:checkend, :configuration)
clear()
Testing.teardown()
:ok
end
# Private functions
defp should_ignore?(exception, config) do
IgnoreFilter.should_ignore?(exception, config.ignored_exceptions)
end
defp build_notice(exception, stacktrace, opts, config) do
context = Map.merge(get_context(), opts[:context] || %{})
user = opts[:user] || get_user()
request = Map.merge(get_request(), opts[:request] || %{})
NoticeBuilder.build(
exception,
stacktrace,
context: context,
user: user,
request: request,
fingerprint: opts[:fingerprint],
tags: opts[:tags] || [],
config: config
)
end
defp run_before_notify(notice, config) do
Enum.reduce_while(config.before_notify, {:ok, notice}, fn callback, {:ok, n} ->
case callback.(n) do
false -> {:halt, :skip}
true -> {:cont, {:ok, n}}
%Notice{} = updated -> {:cont, {:ok, updated}}
_ -> {:cont, {:ok, n}}
end
end)
end
end