Attaching AppSignal to Oban
As it turns out this is a pretty simple task. Oban emits telemetry events. You can attach a callback function (e.g. in the start function of your Application
module) to the specific events:
:telemetry.attach("oban-failure", [:oban, :failure], &MyApp.Appsignal.handle_event/4, nil)
:telemetry.attach("oban-success", [:oban, :success], &MyApp.Appsignal.handle_event/4, nil)
The callback function then assembles a new transaction and emits it to AppSignal:
defmodule MyApp.Appsignal do
alias Appsignal.Error
alias Appsignal.Transaction
def handle_event([:oban, event], measurement, meta, _) when event in [:success, :failure] do
transaction = record_event(measurement, meta)
if event == :failure && meta.attempt >= meta.max_attempts do
{reason, message, stack} = normalize_error(meta)
Transaction.set_error(transaction, reason, message, stack)
end
Transaction.complete(transaction)
end
defp record_event(measurement, meta) do
metadata = %{"id" => meta.id, "queue" => meta.queue, "attempt" => meta.attempt}
transaction = Transaction.start(Transaction.generate_id(), :background_job)
transaction
|> Transaction.set_action("#{meta.worker}#perform")
|> Transaction.set_meta_data(metadata)
|> Transaction.set_sample_data("params", meta.args)
|> Transaction.record_event("worker.perform", "", "", measurement.duration, 0)
|> Transaction.finish()
transaction
end
defp normalize_error(%{kind: :error, error: error, stack: stack}) do
{reason, message} = Error.metadata(error)
{inspect(reason), inspect(message), stack}
end
defp normalize_error(%{kind: kind, error: error, stack: stack}) do
{inspect(kind), inspect(error), stack}
end
end
Et voilà! You're done. You now monitor your background jobs with AppSignal.