From 90f3e2cbe30342a91247326e2e75e54cb45289a4 Mon Sep 17 00:00:00 2001 From: Adrien Cohen <18153974+adriencohen@users.noreply.github.com> Date: Tue, 28 Oct 2025 11:47:13 +0100 Subject: [PATCH] feat(oban): add option to include job tags into reported exception tags --- lib/sentry/application.ex | 2 +- lib/sentry/config.ex | 8 ++++++ .../integrations/oban/error_reporter.ex | 25 ++++++++++++------ .../integrations/oban/error_reporter_test.exs | 26 ++++++++++++++++--- 4 files changed, 49 insertions(+), 12 deletions(-) diff --git a/lib/sentry/application.ex b/lib/sentry/application.ex index 0d8d6058..f8275556 100644 --- a/lib/sentry/application.ex +++ b/lib/sentry/application.ex @@ -77,7 +77,7 @@ defmodule Sentry.Application do end if config[:oban][:capture_errors] do - Sentry.Integrations.Oban.ErrorReporter.attach() + Sentry.Integrations.Oban.ErrorReporter.attach(config[:oban]) end if config[:quantum][:cron][:enabled] do diff --git a/lib/sentry/config.ex b/lib/sentry/config.ex index 9a41069e..cb4fe035 100644 --- a/lib/sentry/config.ex +++ b/lib/sentry/config.ex @@ -51,6 +51,14 @@ defmodule Sentry.Config do tuples. *Available since 10.3.0*. """ ], + oban_tags: [ + type: :boolean, + default: false, + doc: """ + Whether to include Oban job tags in Sentry error tags. When enabled, the `job.tags` + will be joined with a "," and added as an `oban_tags` tag to Sentry events. + """ + ], cron: [ doc: """ Configuration options for configuring [*crons*](https://docs.sentry.io/product/crons/) diff --git a/lib/sentry/integrations/oban/error_reporter.ex b/lib/sentry/integrations/oban/error_reporter.ex index 3a11ba0a..e22169fa 100644 --- a/lib/sentry/integrations/oban/error_reporter.ex +++ b/lib/sentry/integrations/oban/error_reporter.ex @@ -4,14 +4,14 @@ defmodule Sentry.Integrations.Oban.ErrorReporter do # See this blog post: # https://getoban.pro/articles/enhancing-error-reporting - @spec attach() :: :ok - def attach do + @spec attach(keyword()) :: :ok + def attach(config \\ []) when is_list(config) do _ = :telemetry.attach( __MODULE__, [:oban, :job, :exception], &__MODULE__.handle_event/4, - :no_config + config ) :ok @@ -21,32 +21,41 @@ defmodule Sentry.Integrations.Oban.ErrorReporter do [atom(), ...], term(), %{required(:job) => struct(), optional(term()) => term()}, - :no_config + keyword() ) :: :ok def handle_event( [:oban, :job, :exception], _measurements, %{job: job, kind: kind, reason: reason, stacktrace: stacktrace} = _metadata, - :no_config + config ) do if report?(reason) do - report(job, kind, reason, stacktrace) + report(job, kind, reason, stacktrace, config) else :ok end end - defp report(job, kind, reason, stacktrace) do + defp report(job, kind, reason, stacktrace, config) do stacktrace = case {apply(Oban.Worker, :from_string, [job.worker]), stacktrace} do {{:ok, atom_worker}, []} -> [{atom_worker, :process, 1, []}] _ -> stacktrace end + base_tags = %{oban_worker: job.worker, oban_queue: job.queue, oban_state: job.state} + + tags = + if config[:oban_tags] === true and is_list(job.tags) and length(job.tags) > 0 do + Map.put(base_tags, :oban_tags, Enum.join(job.tags, ",")) + else + base_tags + end + opts = [ stacktrace: stacktrace, - tags: %{oban_worker: job.worker, oban_queue: job.queue, oban_state: job.state}, + tags: tags, fingerprint: [job.worker, "{{ default }}"], extra: Map.take(job, [:args, :attempt, :id, :max_attempts, :meta, :queue, :tags, :worker]), diff --git a/test/sentry/integrations/oban/error_reporter_test.exs b/test/sentry/integrations/oban/error_reporter_test.exs index 0d48fcdb..dae43ff2 100644 --- a/test/sentry/integrations/oban/error_reporter_test.exs +++ b/test/sentry/integrations/oban/error_reporter_test.exs @@ -4,7 +4,7 @@ defmodule Sentry.Integrations.Oban.ErrorReporterTest do alias Sentry.Integrations.Oban.ErrorReporter defmodule MyWorker do - use Oban.Worker + use Oban.Worker, tags: ["tag1", "tag2"] @impl Oban.Worker def perform(%Oban.Job{}), do: :ok @@ -33,6 +33,7 @@ defmodule Sentry.Integrations.Oban.ErrorReporterTest do assert event.tags.oban_queue == "default" assert event.tags.oban_state == "available" assert event.tags.oban_worker == "Sentry.Integrations.Oban.ErrorReporterTest.MyWorker" + assert event.tags.oban_tags == "tag1,tag2" assert %{job: %Oban.Job{}} = event.integration_meta.oban assert event.fingerprint == [@worker_as_string, "{{ default }}"] @@ -64,6 +65,7 @@ defmodule Sentry.Integrations.Oban.ErrorReporterTest do assert event.tags.oban_queue == "default" assert event.tags.oban_state == "available" assert event.tags.oban_worker == "Sentry.Integrations.Oban.ErrorReporterTest.MyWorker" + assert event.tags.oban_tags == "tag1,tag2" assert %{job: %Oban.Job{}} = event.integration_meta.oban assert event.fingerprint == [@worker_as_string, "{{ default }}"] @@ -154,11 +156,29 @@ defmodule Sentry.Integrations.Oban.ErrorReporterTest do assert Sentry.Test.pop_sentry_reports() == [] end end + + test "includes oban_tags when config option is enabled" do + Sentry.Test.start_collecting() + + emit_telemetry_for_failed_job(:error, %RuntimeError{message: "oops"}, [], oban_tags: true) + + assert [event] = Sentry.Test.pop_sentry_reports() + assert event.tags.oban_tags == "tag1,tag2" + end + + test "excludes oban_tags when config option is disabled" do + Sentry.Test.start_collecting() + + emit_telemetry_for_failed_job(:error, %RuntimeError{message: "oops"}, [], oban_tags: false) + + assert [event] = Sentry.Test.pop_sentry_reports() + assert is_nil(Map.get(event.tags, :oban_tags)) + end end ## Helpers - defp emit_telemetry_for_failed_job(kind, reason, stacktrace) do + defp emit_telemetry_for_failed_job(kind, reason, stacktrace, config \\ []) do job = %{"id" => "123", "entity" => "user", "type" => "delete"} |> MyWorker.new() @@ -169,7 +189,7 @@ defmodule Sentry.Integrations.Oban.ErrorReporterTest do [:oban, :job, :exception], %{}, %{job: job, kind: kind, reason: reason, stacktrace: stacktrace}, - :no_config + config ) job