Packages
sentry
12.0.2
13.3.0
13.2.0
13.1.0
13.0.1
13.0.0
12.0.3
12.0.2
12.0.1
12.0.0
11.0.4
11.0.3
11.0.2
11.0.1
11.0.0
10.10.0
10.9.0
10.8.1
10.8.0
10.7.1
10.7.0
10.6.2
10.6.1
10.6.0
10.5.0
10.4.0
10.3.0
10.2.1
10.2.0
10.2.0-rc.2
10.2.0-rc.1
10.1.0
10.0.3
10.0.2
10.0.1
10.0.0
9.1.0
9.0.0
8.1.0
8.0.6
8.0.5
8.0.4
8.0.3
8.0.2
8.0.1
8.0.0
8.0.0-rc.2
8.0.0-rc.1
8.0.0-rc.0
retired
7.2.5
7.2.4
7.2.3
7.2.2
7.2.1
7.2.0
7.1.0
7.0.6
7.0.5
7.0.4
7.0.3
7.0.2
7.0.1
7.0.0
6.4.2
6.4.1
6.4.0
6.3.0
6.2.1
6.2.0
6.1.0
6.0.5
6.0.4
6.0.3
6.0.2
6.0.1
6.0.0
5.0.1
5.0.0
4.0.3
4.0.2
4.0.1
4.0.0
3.0.0
2.2.0
2.1.0
2.0.2
2.0.1
2.0.0
1.1.2
1.1.1
1.1.0
1.0.0
0.3.2
0.3.1
0.3.0
0.2.0
0.1.3
0.1.2
0.1.1
0.1.0
The Official Elixir client for Sentry
Current section
Files
Jump to
Current section
Files
lib/sentry/integrations/oban/error_reporter.ex
defmodule Sentry.Integrations.Oban.ErrorReporter do
@moduledoc false
# See this blog post:
# https://getoban.pro/articles/enhancing-error-reporting
require Logger
@spec attach(keyword()) :: :ok
def attach(config \\ []) when is_list(config) do
_ =
:telemetry.attach(
__MODULE__,
[:oban, :job, :exception],
&__MODULE__.handle_event/4,
config
)
:ok
end
@spec handle_event(
[atom(), ...],
term(),
%{required(:job) => struct(), optional(term()) => term()},
keyword()
) :: :ok
def handle_event(
[:oban, :job, :exception],
_measurements,
%{job: job, kind: kind, reason: reason, stacktrace: stacktrace} = _metadata,
config
) do
if report?(reason) and should_report?(job, config) do
report(job, kind, reason, stacktrace, config)
else
:ok
end
end
defp should_report?(job, config) do
case Keyword.get(config, :should_report_error_callback) do
callback when is_function(callback, 2) ->
call_should_report_error_callback(callback, job)
_ ->
true
end
end
defp call_should_report_error_callback(callback, job) do
worker =
case apply(Oban.Worker, :from_string, [job.worker]) do
{:ok, mod} ->
mod
{:error, _} ->
Logger.warning(
"Could not resolve Oban worker module from string: #{inspect(job.worker)}"
)
nil
end
try do
callback.(worker, job) == true
rescue
error ->
Logger.warning("""
:should_report_error_callback failed for worker #{inspect(worker)} \
(job ID #{job.id}):
#{Exception.format(:error, error, __STACKTRACE__)}\
""")
true
end
end
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 = merge_oban_tags(base_tags, config[:oban_tags_to_sentry_tags], job)
opts =
[
stacktrace: stacktrace,
tags: tags,
fingerprint: [job.worker, "{{ default }}"],
extra:
Map.take(job, [:args, :attempt, :id, :max_attempts, :meta, :queue, :tags, :worker]),
integration_meta: %{oban: %{job: job}}
]
_ =
case maybe_unwrap_exception(kind, reason, stacktrace) do
exception when is_exception(exception) ->
Sentry.capture_exception(exception, opts)
_other ->
message =
case kind do
:exit -> "Oban job #{job.worker} exited: %s"
:throw -> "Oban job #{job.worker} exited with an uncaught throw: %s"
_other -> "Oban job #{job.worker} errored out: %s"
end
Sentry.capture_message(message, opts ++ [interpolation_parameters: [inspect(reason)]])
end
:ok
end
# Oban.PerformError also wraps {:discard, _} and {:cancel, _} tuples, but those are
# not *errors* and should not be reported to Sentry automatically.
defp report?(%{reason: {type, _reason}} = error) when is_exception(error, Oban.PerformError) do
type == :error
end
defp report?(_error) do
true
end
defp maybe_unwrap_exception(
:error = _kind,
%{reason: {:error, error}} = perform_error,
_stacktrace
)
when is_exception(perform_error, Oban.PerformError) and is_exception(error) do
error
end
defp maybe_unwrap_exception(kind, reason, stacktrace) do
Exception.normalize(kind, reason, stacktrace)
end
defp merge_oban_tags(base_tags, nil, _job), do: base_tags
defp merge_oban_tags(base_tags, tags_config, job) do
try do
custom_tags = call_oban_tags_to_sentry_tags(tags_config, job)
if is_map(custom_tags) do
Map.merge(base_tags, custom_tags)
else
Logger.warning(
"oban_tags_to_sentry_tags function returned a non-map value: #{inspect(custom_tags)}"
)
base_tags
end
rescue
error ->
Logger.warning("oban_tags_to_sentry_tags function failed: #{inspect(error)}")
base_tags
end
end
defp call_oban_tags_to_sentry_tags(fun, job) when is_function(fun, 1) do
fun.(job)
end
defp call_oban_tags_to_sentry_tags({module, function}, job) do
apply(module, function, [job])
end
end