-
Notifications
You must be signed in to change notification settings - Fork 30
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Failure backend #36
Failure backend #36
Changes from 5 commits
e663ce2
8780eaa
73628ba
76f672a
62c7f01
d3dc0c5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,3 +17,6 @@ erl_crash.dump | |
*.ez | ||
|
||
/logs | ||
|
||
# asdf version file | ||
.tool-versions |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
defmodule TaskBunny.FailureBackend do | ||
@moduledoc """ | ||
A behaviour module to implment the your own failure backend. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. typo: implement |
||
|
||
Note the backend is called only for the errors caught during job processing. | ||
Any other errors won't be reported to the backend. | ||
|
||
## Configuration | ||
|
||
By default, TaskBunny reports the job failures to Logger. | ||
If you want to report the error to different services, you can configure | ||
your custom failure backend. | ||
|
||
config :task_bunny, failure_backend: [YourApp.CustomFailureBackend] | ||
|
||
You can also report the errors to the multiple backends. For example, if you | ||
want to use our default Logger backend with your custom backend you can | ||
configure like below: | ||
|
||
config :task_bunny, failure_backend: [ | ||
TaskBunny.FailureBackend.Logger, | ||
YourApp.CustomFailureBackend | ||
] | ||
|
||
## Example | ||
|
||
See the implmentation of `TaskBunny.FailureBackend.Logger`. | ||
|
||
## Argument | ||
|
||
See `TaskBunny.JobError` for the details. | ||
|
||
""" | ||
alias TaskBunny.{JobError, Config, FailureBackend} | ||
|
||
@doc """ | ||
Callback to report a job error. | ||
""" | ||
@callback report_job_error(JobError.t) :: any | ||
|
||
defmacro __using__(_options \\ []) do | ||
quote do | ||
@behaviour FailureBackend | ||
end | ||
end | ||
|
||
@doc false | ||
@spec report_job_error(JobError.t) :: :ok | ||
def report_job_error(job_error = %JobError{}) do | ||
Config.failure_backend() | ||
|> Enum.each(&(&1.report_job_error(job_error))) | ||
|
||
:ok | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not really necessary, the result of |
||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
defmodule TaskBunny.FailureBackend.Logger do | ||
@moduledoc """ | ||
Default failure backend that reports job errors to Logger. | ||
""" | ||
use TaskBunny.FailureBackend | ||
require Logger | ||
alias TaskBunny.JobError | ||
|
||
def report_job_error(error = %JobError{error_type: :exception}) do | ||
message = """ | ||
TaskBunny - #{error.job} failed for an exception. | ||
|
||
Exception: | ||
#{my_inspect error.exception} | ||
|
||
#{common_message error} | ||
|
||
Stacktrace: | ||
#{Exception.format_stacktrace(error.stacktrace)} | ||
""" | ||
|
||
do_report(message, error.reject) | ||
end | ||
|
||
def report_job_error(error = %JobError{error_type: :return_value}) do | ||
message = """ | ||
TaskBunny - #{error.job} failed for an invalid return value. | ||
|
||
Return value: | ||
#{my_inspect error.return_value} | ||
|
||
#{common_message error} | ||
""" | ||
|
||
do_report(message, error.reject) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. More an option suggestion, I know your preference, so I'm just putting it out there, because it will align the triple-quotes nicely. If you like it you can also write:
(You know how I love aligning and pipes 😛) |
||
end | ||
|
||
def report_job_error(error = %JobError{error_type: :exit}) do | ||
message = """ | ||
TaskBunny - #{error.job} failed for EXIT signal. | ||
|
||
Reason: | ||
#{my_inspect error.reason} | ||
|
||
#{common_message error} | ||
""" | ||
|
||
do_report(message, error.reject) | ||
end | ||
|
||
def report_job_error(error = %JobError{error_type: :timeout}) do | ||
message = """ | ||
TaskBunny - #{error.job} failed for timeout. | ||
|
||
#{common_message error} | ||
""" | ||
|
||
do_report(message, error.reject) | ||
end | ||
|
||
def report_job_error(error) do | ||
message = """ | ||
TaskBunny - Failed with the unknown error type. | ||
|
||
Error dump: | ||
#{my_inspect error} | ||
""" | ||
|
||
do_report(message, true) | ||
end | ||
|
||
defp do_report(message, rejected) do | ||
if rejected do | ||
Logger.error message | ||
else | ||
Logger.warn message | ||
end | ||
end | ||
|
||
defp common_message(error) do | ||
""" | ||
Payload: | ||
#{my_inspect error.payload} | ||
|
||
History: | ||
- Failed count: #{error.failed_count} | ||
- Reject: #{error.reject} | ||
|
||
Worker: | ||
- Queue: #{error.queue} | ||
- Concurrency: #{error.concurrency} | ||
- PID: #{inspect error.pid} | ||
""" | ||
end | ||
|
||
defp my_inspect(arg) do | ||
inspect arg, pretty: true, width: 100 | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
defmodule TaskBunny.JobError do | ||
@moduledoc """ | ||
A struct that holds an error information occured during the job processing. | ||
|
||
## Attributes | ||
|
||
- job: the job module failed | ||
- payload: the payload(arguments) for the job execution | ||
- error_type: the type of the error. :exception, :return_value, :timeout or :exit | ||
- exception: the inner exception (option) | ||
- stacktrace: the stacktrace (only available for the exception) | ||
- return_value: the return value from the job (only available for the return value error) | ||
- reason: the reason information passed with EXIT signal (only available for exit error) | ||
- raw_body: the raw body for the message | ||
- meta: the meta data given by RabbitMQ | ||
- failed_count: the number of failures for the job processing request | ||
- queue: the name of the queue | ||
- concurrency: the number of concurrent job processing of the worker | ||
- pid: the process ID of the worker | ||
- reject: sets true if the job is rejected for the failure (means it won't be retried again) | ||
|
||
""" | ||
|
||
@type t :: %__MODULE__{ | ||
job: atom, | ||
payload: any, | ||
error_type: :exception | :return_value | :timeout | :exit | nil, | ||
exception: struct | nil, | ||
stacktrace: list(tuple) | nil, | ||
return_value: any, | ||
reason: any, | ||
raw_body: String.t, | ||
meta: map, | ||
failed_count: integer, | ||
queue: String.t, | ||
concurrency: integer, | ||
pid: pid, | ||
reject: boolean | ||
} | ||
|
||
defstruct [ | ||
job: nil, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The value of If you go for The same goes for |
||
payload: nil, | ||
error_type: nil, | ||
exception: nil, | ||
stacktrace: nil, | ||
return_value: nil, | ||
reason: nil, | ||
raw_body: "", | ||
meta: %{}, | ||
failed_count: 0, | ||
queue: "", | ||
concurrency: 1, | ||
pid: nil, | ||
reject: false | ||
] | ||
|
||
@doc false | ||
def handle_exception(job, payload, exception) do | ||
%__MODULE__{ | ||
job: job, | ||
payload: payload, | ||
error_type: :exception, | ||
exception: exception, | ||
stacktrace: System.stacktrace() | ||
} | ||
end | ||
|
||
@doc false | ||
def handle_exit(job, payload, reason) do | ||
%__MODULE__{ | ||
job: job, | ||
payload: payload, | ||
error_type: :exit, | ||
reason: reason | ||
} | ||
end | ||
|
||
@doc false | ||
def handle_return_value(job, payload, return_value) do | ||
%__MODULE__{ | ||
job: job, | ||
payload: payload, | ||
error_type: :return_value, | ||
return_value: return_value | ||
} | ||
end | ||
|
||
@doc false | ||
def handle_timeout(job, payload) do | ||
%__MODULE__{ | ||
job: job, | ||
payload: payload, | ||
error_type: :timeout | ||
} | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that if you put
TaskBunny.FailureBackend.Logger
between backticks that ExDoc will pick up on the module and make it linkable.Ref: Auto-Linking