ForgeOpsTracker (forge_ops_tracker v0.5.0)

Copy Markdown View Source

Elixir error reporting client for a ForgeOps instance:

ForgeOpsTracker.init(dsn: "https://<api_key>@your-forgeops-host/api/v1/events")
ForgeOpsTracker.install_handlers()

See the module docs on ForgeOpsTracker.LoggerHandler for what install_handlers/0 actually covers (every process crash anywhere in the whole BEAM VM, via :logger: a meaningfully stronger automatic-capture story than any single-thread uncaught-exception hook) and capture_exception/3 for reporting one you've already rescued yourself.

Summary

Functions

Report an exception (or any raised/thrown term: see ForgeOpsTracker.EventBuilder's own doc for why this doesn't require a real Exception struct) you've already rescued yourself

Configures the client. Call once at startup: application.ex's own start/2, before the rest of your supervision tree starts, is the natural place. Accepts the same keys as ForgeOpsTracker.Configuration's struct fields

Installs the :logger handler described in ForgeOpsTracker.LoggerHandler's own module doc. Call once, after init/1. Safe to call more than once: a later call is a no-op.

Records one occurrence of transaction_name (a request, a database query, a queue job) taking duration_ms, bucketed by kind ("controller"/"job"/"query"). Not something app code normally calls directly: ForgeOpsTracker.Integrations.Phoenix/Ecto/Oban all call this once wired up via their own attach/0/attach/1.

Manually attaches an affected user to whatever gets reported for the rest of this process (a Phoenix request's own connection process, a GenServer, an IEx session): stored in the process dictionary, the same "one process per request" isolation boundary Phoenix itself already gives every request, and the direct Elixir analog to Thread.current in gems/forge_ops_tracker's own equivalent. id/email/username are all independently optional; call with an empty map (or nothing set at all) to clear whatever was set.

Functions

capture_exception(reason, stacktrace, context \\ %{}, user \\ :from_process)

@spec capture_exception(term(), Exception.stacktrace(), map(), map() | nil) :: :ok

Report an exception (or any raised/thrown term: see ForgeOpsTracker.EventBuilder's own doc for why this doesn't require a real Exception struct) you've already rescued yourself:

try do
  charge_card(order)
rescue
  e ->
    ForgeOpsTracker.capture_exception(e, __STACKTRACE__, %{order_id: order.id})
    reraise e, __STACKTRACE__
end

stacktrace has to come from the actual rescue/catch site (__STACKTRACE__): unlike languages where an exception object carries its own trace, Elixir's stacktrace is only available via that special form, and only for as long as nothing else has run since the rescue/catch (the same reason the example above captures it before doing anything else).

user, if not given, defaults to whatever set_user/1 last set for this process (nil if nothing did); pass one explicitly to override that for this one report.

init(opts \\ [])

Configures the client. Call once at startup: application.ex's own start/2, before the rest of your supervision tree starts, is the natural place. Accepts the same keys as ForgeOpsTracker.Configuration's struct fields:

ForgeOpsTracker.init(dsn: "...", environment: "production")

Fields not passed here fall back to Application.get_env(:forge_ops_tracker, key) / FORGE_OPS_DSN-style environment variables read when this app started: see ForgeOpsTracker.Configuration's own module doc.

install_handlers()

@spec install_handlers() :: :ok

Installs the :logger handler described in ForgeOpsTracker.LoggerHandler's own module doc. Call once, after init/1. Safe to call more than once: a later call is a no-op.

record_performance(transaction_name, kind \\ "controller", duration_ms)

@spec record_performance(String.t(), String.t(), number()) :: :ok

Records one occurrence of transaction_name (a request, a database query, a queue job) taking duration_ms, bucketed by kind ("controller"/"job"/"query"). Not something app code normally calls directly: ForgeOpsTracker.Integrations.Phoenix/Ecto/Oban all call this once wired up via their own attach/0/attach/1.

Re-checks Configuration.enabled?/1 and track_performance on every call, the same "guarded every time, not just once at attach time" posture capture_exception/3 already has over Reporter.report/3: config can change at runtime, so a :telemetry.attached handler that checked this only once at startup could keep recording long after either flag turned off.

set_user(user \\ %{})

@spec set_user(map()) :: :ok

Manually attaches an affected user to whatever gets reported for the rest of this process (a Phoenix request's own connection process, a GenServer, an IEx session): stored in the process dictionary, the same "one process per request" isolation boundary Phoenix itself already gives every request, and the direct Elixir analog to Thread.current in gems/forge_ops_tracker's own equivalent. id/email/username are all independently optional; call with an empty map (or nothing set at all) to clear whatever was set.