errorgap (Elixir)

Elixir notifier for Errorgap. Reports exceptions from Plug/Phoenix apps, OTP processes, and plain Elixir scripts — with source-aware backtraces, nested causes, breadcrumbs, structured logs, and APM transactions.

Backtrace frames are resolved against the project source tree — file, line, function, an app-versus-dependency flag, and a surrounding source excerpt — so the dashboard renders highlighted source without any repository integration.

Requires Elixir 1.15+ and OTP 26+.

Install

def deps do
[{:errorgap, "~> 0.2"}]
end

Configure

config/runtime.exs:

config :errorgap,
endpoint: System.get_env("ERRORGAP_ENDPOINT"),
project_slug: System.get_env("ERRORGAP_PROJECT_SLUG"),
api_key: System.get_env("ERRORGAP_API_KEY"),
environment: System.get_env("APP_ENV", "production")

Falls back to ERRORGAP_* environment variables when keys are omitted.

Manual notification

try do
risky()
rescue
exc ->
Errorgap.notify(exc, stacktrace: __STACKTRACE__, context: %{component: "billing"})
reraise exc, __STACKTRACE__
end

Phoenix / Plug

Use Plug.ErrorHandler and delegate to Errorgap.Plug.report/2:

defmodule MyAppWeb.Endpoint do
use Phoenix.Endpoint, otp_app: :my_app
use Plug.ErrorHandler
# ... plugs ...
@impl Plug.ErrorHandler
def handle_errors(conn, error_info) do
Errorgap.Plug.report(conn, error_info)
conn
end
end

OTP crash reports

Attach the :logger handler once at startup so unhandled exceptions in supervised processes are captured automatically:

Errorgap.LoggerHandler.attach()

Causes

Wrap an exception with a :cause field (or pass cause:) and each cause's type/message lands in context.causes:

Errorgap.notify(%MyApp.CheckoutError{message: "checkout failed", cause: db_error},
stacktrace: __STACKTRACE__)

Breadcrumbs

Breadcrumbs accumulate per-process and attach to notices as context.breadcrumbs:

Errorgap.add_breadcrumb("loaded checkout", "navigation", %{from: "cart"})

Structured logs

Errorgap.log("payment gateway timeout", "error", "payments")

Levels are trace < debug < info < warn < error < fatal; anything below :minimum_log_level is dropped client-side.

Performance (APM)

alias Errorgap.{Span, Transaction}
spans = [
Span.database("SELECT * FROM orders WHERE id = 123", 4.2, function: "OrderRepo.get/1"),
Span.external(88.0, function: "PaymentGateway.charge/1")
]
Transaction.web("GET", "/orders/{id}", "/orders/123", status_code: 200, duration_ms: 120.0, spans: spans)
|> Errorgap.notify_transaction()
# Background work:
Transaction.job("ReceiptJob", "mailers", duration_ms: 40.0)
|> Errorgap.notify_transaction()

path is the normalized route template used for grouping; path_raw is the concrete URL. APM delivery requires :apm_enabled (default true).

Configuration reference

KeyDefaultNotes
:endpointERRORGAP_ENDPOINT or http://127.0.0.1:3030
:project_slugERRORGAP_PROJECT_SLUGRequired
:project_idERRORGAP_PROJECT_ID
:api_keyERRORGAP_API_KEYSent as x-errorgap-project-key
:environmentERRORGAP_ENVIRONMENT or production
:releaseERRORGAP_RELEASE
:root_directoryPWD or File.cwd!()Resolves backtrace source files
:asynctrueCast to a GenServer worker
:filter_keys["password", "token", ...]Substring, case-insensitive
:timeout5_000HTTP timeout (ms)
:apm_enabledtrueDeliver APM transactions
:apm_sample_rate1.0Fraction (0..1) of transactions delivered
:logs_enabledtrueDeliver structured logs
:minimum_log_level"info"Drop logs below this level
:max_breadcrumbs25Breadcrumbs retained per notice

Graceful flush

Errorgap.flush(5_000)

Development

mix deps.get
mix test

License

MIT.