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.6"}]
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

Remote settings

Errors, performance data and logs can each be switched off for a project in errorgap (Project settings › What SDKs send). Running nodes follow within ten minutes, with no deploy. If errorgap cannot be reached, everything stays on. config :errorgap, remote_settings: false (or ERRORGAP_REMOTE_SETTINGS=false) ignores the project's switches.

Filtering, hooks and delivery

config :errorgap,
  ignore_environments: ["test"],
  queue_size: 100,
  # return the notice (changed or not), or false to drop it
  before_notify: {MyApp.Errorgap, :before_notify}

filter_keys are masked at any depth in params and session, inside lists too, and oversized strings are cut. A send that fails on the network or with a 5xx is retried up to three times; after a 429 the SDK stops sending for the server's Retry-After. When more than queue_size payloads are waiting, new ones are refused ({:error, :queue_full}) instead of growing the mailbox.

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

Request performance

Add plug Errorgap.Plug, apm: true near the top of your endpoint (or Plug.Router) and each request is an APM transaction grouped by its route (/orders/:id, from Phoenix or Plug.Router). Errors reported while it runs carry its transaction id, a request that raises is recorded as a 500 by Errorgap.Plug.report/2, and the x-errorgap-trace header sent by the errorgap browser and mobile SDKs links the caller's view of the request to it.

plug Errorgap.Plug, apm: true

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).

Every transaction has an id (txn["id"]). Run the work inside Errorgap.with_transaction/2 and errors reported from that process carry it as context.transaction_id, so errorgap shows the error a request actually raised on its trace and links each occurrence to its request:

txn = Transaction.web("GET", "/orders/{id}", "/orders/123")
started = System.monotonic_time(:millisecond)
result = Errorgap.with_transaction(txn, fn -> handle(conn) end)
txn
|> Map.merge(%{"status_code" => 200, "duration_ms" => System.monotonic_time(:millisecond) - started})
|> Errorgap.notify_transaction()

The id lives in the process dictionary, so concurrent requests (separate processes) never share one. In a Plug pipeline, Errorgap.Transaction.put_current/1 makes it current for the rest of the request process.

When the errorgap browser SDK is on the page, its API calls send an x-errorgap-trace header. Pass it as :trace_id and errorgap's browser Performance view links each call to the server request that answered it (malformed values are ignored):

trace = conn |> Plug.Conn.get_req_header("x-errorgap-trace") |> List.first()
txn = Transaction.web("GET", "/orders/{id}", conn.request_path, trace_id: trace)

Configuration reference

Key Default Notes
:endpoint ERRORGAP_ENDPOINT or http://127.0.0.1:3030
:project_slug ERRORGAP_PROJECT_SLUG Required
:project_id ERRORGAP_PROJECT_ID
:api_key ERRORGAP_API_KEY Sent as x-errorgap-project-key
:environment ERRORGAP_ENVIRONMENT or production
:release ERRORGAP_RELEASE
:root_directory PWD or File.cwd!() Resolves backtrace source files
:async true Cast to a GenServer worker
:filter_keys ["password", "token", ...] Substring, case-insensitive
:timeout 5_000 HTTP timeout (ms)
:apm_enabled true Deliver APM transactions
:apm_sample_rate 1.0 Fraction (0..1) of transactions delivered
:logs_enabled true Deliver structured logs
:minimum_log_level "info" Drop logs below this level
:max_breadcrumbs 25 Breadcrumbs retained per notice

Graceful flush

Errorgap.flush(5_000)

Development

mix deps.get
mix test

License

MIT.