Skip to content

Infrastructure to invoke a backtrace cleaner for failed jobs backtraces #146

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

Merged
merged 5 commits into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@
/test/dummy/storage/
/test/dummy/tmp/
.DS_Store
.ruby-gemset
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ Besides `base_controller_class`, you can also set the following for `MissionCont
- `internal_query_count_limit`: in count queries, the maximum number of records that will be counted if the adapter needs to limit these queries. True counts above this number will be returned as `INFINITY`. This keeps count queries fast—defaults to `500,000`
- `scheduled_job_delay_threshold`: the time duration before a scheduled job is considered delayed. Defaults to `1.minute` (a job is considered delayed if it hasn't transitioned from the `scheduled` status 1 minute after the scheduled time).
- `show_console_help`: whether to show the console help. If you don't want the console help message, set this to `false`—defaults to `true`.
- `backtrace_cleaner`: a backtrace cleaner used for optionally filtering backtraces on the Failed Jobs detail page. Defaults to `Rails::BacktraceCleaner.new`. See the [Advanced configuration](#advanced-configuration) section for how to configure/override this setting on a per application/server basis.

This library extends Active Job with a querying interface and the following setting:
- `config.active_job.default_page_size`: the internal batch size that Active Job will use when sending queries to the underlying adapter and the batch size for the bulk operations defined above—defaults to `1000`.
Expand Down Expand Up @@ -107,7 +108,24 @@ SERVERS_BY_APP.each do |app, servers|
ActiveJob::QueueAdapters::SolidQueueAdapter.new
end

[ server, queue_adapter ]
# Default:
#
# @return Array<String, ActiveJob::QueueAdapters::Base)
# An array where:
# * the String represents the symbolic name for this server within the UI
# * ActiveJob::QueueAdapters::Base adapter instance used to access this Application Server/Service
[ server, queue_adapter ]

# Optional return formats:
#
# @return Array<String, Array<ActiveJob::QueueAdapters::Base>>
# * This is equivalent, and behaves identically to, the format the default format above.
# [ server, [ queue_adapter ]] # without optional backtrace cleaner
#
# @return Array<String, Array<ActiveJob::QueueAdapters::Base, ActiveSupport::BacktraceCleaner>>
# * This format adds an optional ActiveSupport::BacktraceCleaner to override the system wide
# backtrace cleaner for *this* Application Server/Service.
# [ server, [ queue_adapter, BacktraceCleaner.new ]] # with optional backtrace cleaner
end.to_h

MissionControl::Jobs.applications.add(app, queue_adapters_by_name)
Expand Down
1 change: 1 addition & 0 deletions app/controllers/mission_control/jobs/jobs_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ def show
end

private

def jobs_relation
filtered_jobs
end
Expand Down
13 changes: 11 additions & 2 deletions app/helpers/mission_control/jobs/jobs_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,16 @@ def failed_job_error(job)
"#{job.last_execution_error.error_class}: #{job.last_execution_error.message}"
end

def failed_job_backtrace(job)
job.last_execution_error.backtrace.join("\n")
def clean_backtrace?
params["clean_backtrace"] == "true"
end

def failed_job_backtrace(job, server)
if clean_backtrace? && server&.backtrace_cleaner
server.backtrace_cleaner.clean(job.last_execution_error.backtrace).join("\n")
else
job.last_execution_error.backtrace.join("\n")
end
end

def attribute_names_for_job_status(status)
Expand All @@ -31,6 +39,7 @@ def job_delayed?(job)
end

private

def renderable_job_arguments_for(job)
job.serialized_arguments.collect do |argument|
as_renderable_argument(argument)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,9 @@
</tbody>
</table>

<pre class="is-family-monospace mb-4"><%= failed_job_backtrace(job) %></pre>
<% if @server.backtrace_cleaner %>
<%= render "mission_control/jobs/jobs/failed/backtrace_toggle", application: @application, job: job %>
<% end %>

<pre class="is-family-monospace mb-4 backtrace-content"><%= failed_job_backtrace(job, @server) %></pre>
<% end %>
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<%# locals: (application:, job:) %>

<div class="is-flex is-justify-content-flex-end mb-2 mr-2 backtrace-toggle-selector">
<div class="tabs is-toggle is-toggle-rounded is-small">
<ul>
<li class="<%= class_names('backtrace-clean-link', 'is-active' => clean_backtrace?) %>">
<%= link_to "Clean", application_job_path(application, job.job_id, clean_backtrace: true) %>
</li>
<li class="<%= class_names('backtrace-full-link', 'is-active' => !clean_backtrace?) %>">
<%= link_to "Full", application_job_path(application, job.job_id, clean_backtrace: false) %>
</li>
</ul>
</div>
</div>
1 change: 1 addition & 0 deletions lib/mission_control/jobs.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ module Jobs
mattr_accessor :internal_query_count_limit, default: 500_000 # Hard limit to keep unlimited count queries fast enough
mattr_accessor :show_console_help, default: true
mattr_accessor :scheduled_job_delay_threshold, default: 1.minute
mattr_accessor :backtrace_cleaner
end
end
5 changes: 4 additions & 1 deletion lib/mission_control/jobs/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ def initialize(name:)

def add_servers(queue_adapters_by_name)
queue_adapters_by_name.each do |name, queue_adapter|
servers << MissionControl::Jobs::Server.new(name: name.to_s, queue_adapter: queue_adapter, application: self)
adapter, cleaner = queue_adapter

servers << MissionControl::Jobs::Server.new(name: name.to_s, queue_adapter: adapter,
backtrace_cleaner: cleaner, application: self)
end
end
end
1 change: 1 addition & 0 deletions lib/mission_control/jobs/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class Engine < ::Rails::Engine

config.before_initialize do
config.mission_control.jobs.applications = MissionControl::Jobs::Applications.new
config.mission_control.jobs.backtrace_cleaner ||= Rails::BacktraceCleaner.new

config.mission_control.jobs.each do |key, value|
MissionControl::Jobs.public_send("#{key}=", value)
Expand Down
5 changes: 3 additions & 2 deletions lib/mission_control/jobs/server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ class MissionControl::Jobs::Server
include MissionControl::Jobs::IdentifiedByName
include Serializable, RecurringTasks, Workers

attr_reader :name, :queue_adapter, :application
attr_reader :name, :queue_adapter, :application, :backtrace_cleaner

def initialize(name:, queue_adapter:, application:)
def initialize(name:, queue_adapter:, application:, backtrace_cleaner: nil)
super(name: name)
@queue_adapter = queue_adapter
@application = application
@backtrace_cleaner = backtrace_cleaner || MissionControl::Jobs.backtrace_cleaner
end

def activating(&block)
Expand Down
64 changes: 64 additions & 0 deletions test/system/show_failed_job_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,68 @@ class ShowFailedJobsTest < ApplicationSystemTestCase
visit jobs_path(:failed)
assert_text /there are no failed jobs/i
end

test "Has Clean/Full buttons when a backtrace cleaner is configured" do
visit jobs_path(:failed)
within_job_row(/FailingJob\s*2/) do
click_on "RuntimeError: This always fails!"
end

assert_selector ".backtrace-toggle-selector"
end

test "Does not offer Clean/Full buttons when a backtrace cleaner is not configured" do
setup do
# grab the current state
@backtrace_cleaner = MissionControl::Jobs.backtrace_cleaner
@applications = MissionControl::Jobs.backtrace_cleaner

# reset the state
MissionControl::Jobs.backtrace_cleaner = nil
MissionControl::Jobs.applications = Applications.new

# Setup the application with what we had before *minus* a backtrace cleaner
@applications.each do |application|
MissionControl::Jobs.applications.add(application.name).tap do |it|
application.servers.each do |server|
it.add_servers(server.name, server.queue_adapter)
end
end
end
end

teardown do
# reset back to the known state before the start of the test
MissionControl::Jobs.backtrace_cleaner = @backtrace_cleaner
MissionControl::Jobs.applications = @application
end

visit jobs_path(:failed)
within_job_row(/FailingJob\s*2/) do
click_on "RuntimeError: This always fails!"
end

assert_no_selector ".backtrace-toggle-selector"
end

test "click on 'clean' shows a backtrace cleaned by the Rails default backtrace cleaner" do
visit jobs_path(:failed)
within_job_row /FailingJob\s*2/ do
click_on "RuntimeError: This always fails!"
end

assert_selector ".backtrace-toggle-selector"

within ".backtrace-toggle-selector" do
click_on "Clean"
end

assert_selector "pre.backtrace-content", text: /.*/, visible: true

within ".backtrace-toggle-selector" do
click_on "Full"
end

assert_selector "pre.backtrace-content", text: /.*/, visible: true
end
end