Skip to content

Instantly share code, notes, and snippets.

@sorentwo
Created July 13, 2022 20:16
Show Gist options
  • Save sorentwo/80bf3dbf568e12bb5d44a2ff84af9b30 to your computer and use it in GitHub Desktop.
Save sorentwo/80bf3dbf568e12bb5d44a2ff84af9b30 to your computer and use it in GitHub Desktop.
Oban Pro Hooks Documentation

Worker Hooks

Worker hooks are called after a job finishes executing. They can be defined as callback functions on the worker, or in a separate module for reuse across workers.

Hooks are called synchronously, from within the job's process with safety applied. Any exceptions or crashes are caught and logged, they won't cause the job to fail or the queue to crash.

Hooks do not modify the job or execution results. Think of them as a convenient alternative to globally attached telemetry handlers. They are purely for side-effects such as cleanup, logging, recording metrics, broadcasting notifications, updating other records, error notifications, etc.

Defining Hooks

There are three mechanisms for defining and attaching an c:after_process/2 hook:

  1. Implicitly—hooks are defined directly on the worker and they only run for that worker
  2. Explicitly—hooks are listed when defining a worker and they run anywhere they are listed
  3. Globally—hooks are executed for all Pro workers

It's possible to combine each type of hook on a single worker. When multiple hooks are stacked they're executed in the order: implicit, explicit, and then global.

An c:after_process/2 hook is called with the job and an execution state corresponding to the result from process/1:

  • complete—when process/1 returns :ok or {:ok, result}
  • cancel—when process/1 returns {:cancel, reason}
  • discard—when a job errors and exhausts retries, or returns {:discard, reason}
  • error—when a job crashes, raises an exception, or returns {:error, value}
  • snooze—when a job returns {:snooze, seconds}

First, here's how to define a single implicit local hook on the worker using c:after_process/2:

defmodule MyApp.HookWorker do
  use Oban.Pro.Worker

  @impl Oban.Pro.Worker
  def process(_job) do
    # ...
  end

  @impl Oban.Pro.Worker
  def after_process(state, %Job{} = job) do
    MyApp.Notifier.broadcast("oban-jobs", {state, %{id: job.id}})
  end
end

Any module that exports c:after_process/2 can be used as a hook. For example, here we'll define a shared error notification hook:

defmodule MyApp.ErrorHook do
  def after_process(state, job) when state in [:discard, :error] do
    error = job.unsaved_error
    extra = Map.take([:attempt, :id, :args, :max_attempts, :meta, :queue, :worker])

    Sentry.capture_exception(error.reason, stacktrace: error.stacktrace, extra: extra)
  end

  def after_process(_state, _job), do: :ok
end

defmodule MyApp.HookWorker do
  use Oban.Pro.Worker, hooks: [MyApp.ErrorHook]

  @impl Oban.Pro.Worker
  def process(_job) do
    # ...
  end
end

The same module can be attached globally, for all Oban.Pro.Worker modules, using attach_hook/1:

:ok = Oban.Pro.Worker.attach_hook(MyApp.ErrorHook)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment