-
Notifications
You must be signed in to change notification settings - Fork 46
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
Use ActiveSupport::Notifications and LogSubscriber for customizing log output #338
Changes from all commits
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 |
---|---|---|
|
@@ -142,13 +142,12 @@ def interruptible_perform(*arguments) | |
self.start_time = Time.now.utc | ||
|
||
enumerator = nil | ||
ActiveSupport::Notifications.instrument("build_enumerator.iteration", iteration_instrumentation_tags) do | ||
ActiveSupport::Notifications.instrument("build_enumerator.iteration", instrumentation_tags) do | ||
enumerator = build_enumerator(*arguments, cursor: cursor_position) | ||
end | ||
|
||
unless enumerator | ||
logger.info("[JobIteration::Iteration] `build_enumerator` returned nil. " \ | ||
"Skipping the job.") | ||
ActiveSupport::Notifications.instrument("nil_enumerator.iteration", instrumentation_tags) | ||
return | ||
end | ||
|
||
|
@@ -157,7 +156,10 @@ def interruptible_perform(*arguments) | |
if executions == 1 && times_interrupted == 0 | ||
run_callbacks(:start) | ||
else | ||
ActiveSupport::Notifications.instrument("resumed.iteration", iteration_instrumentation_tags) | ||
ActiveSupport::Notifications.instrument( | ||
"resumed.iteration", | ||
instrumentation_tags.merge(times_interrupted: times_interrupted, total_time: total_time), | ||
) | ||
end | ||
|
||
completed = catch(:abort) do | ||
|
@@ -171,7 +173,10 @@ def interruptible_perform(*arguments) | |
reenqueue_iteration_job | ||
elsif completed | ||
run_callbacks(:complete) | ||
output_interrupt_summary | ||
ActiveSupport::Notifications.instrument( | ||
"completed.iteration", | ||
instrumentation_tags.merge(times_interrupted: times_interrupted, total_time: total_time), | ||
) | ||
end | ||
end | ||
|
||
|
@@ -184,10 +189,11 @@ def iterate_with_enumerator(enumerator, arguments) | |
# Deferred until 2.0.0 | ||
# assert_valid_cursor!(index) | ||
|
||
record_unit_of_work do | ||
ActiveSupport::Notifications.instrument("each_iteration.iteration", {}) do |tags| | ||
found_record = true | ||
each_iteration(object_from_enumerator, *arguments) | ||
self.cursor_position = index | ||
tags.replace(instrumentation_tags) | ||
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. Do we really want the updated 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. I believe so, yes. I would like to know how long the thing ( class SlowJob < ActiveJob::Base
include JobIteration::Iteration
def build_enumerator(cursor:)
enumerator_builder.active_record_on_records(
Product.where("id <= 5"),
cursor: cursor,
)
end
def each_iteration(product)
sleep 0.1 * product.id
end
end
def test_notification_cursor_position
collector = []
ActiveSupport::Notifications.monotonic_subscribe("each_iteration.iteration") do |_, started, finished, _, tags|
collector << { cursor_position: tags[:cursor_position], elapsed: finished - started }
end
push(SlowJob)
work_one_job
pp collector
end output:
This looks useful to me, I know exactly the ID of the thing that is slow. If I'm understanding your suggestion correctly, it would be this implementation: ActiveSupport::Notifications.instrument("each_iteration.iteration", instrumentation_tags) do
found_record = true
each_iteration(object_from_enumerator, *arguments)
self.cursor_position = index
end which outputs:
Which doesn't seem as useful. The mental overhead for this example is still manageable to answer the question what thing was slow but it quickly becomes impossible to do from the top of your head, eg when using ULIDs as database IDs. 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. Okay, that makes sense. I think my confusion comes from the fact that when you enqueue the job with |
||
end | ||
|
||
next unless job_should_exit? | ||
|
@@ -197,23 +203,18 @@ def iterate_with_enumerator(enumerator, arguments) | |
return false | ||
end | ||
|
||
logger.info( | ||
"[JobIteration::Iteration] Enumerator found nothing to iterate! " \ | ||
"times_interrupted=#{times_interrupted} cursor_position=#{cursor_position}", | ||
ActiveSupport::Notifications.instrument( | ||
"not_found.iteration", | ||
instrumentation_tags.merge(times_interrupted: times_interrupted), | ||
) unless found_record | ||
|
||
true | ||
ensure | ||
adjust_total_time | ||
end | ||
|
||
def record_unit_of_work(&block) | ||
ActiveSupport::Notifications.instrument("each_iteration.iteration", iteration_instrumentation_tags, &block) | ||
end | ||
|
||
def reenqueue_iteration_job | ||
ActiveSupport::Notifications.instrument("interrupted.iteration", iteration_instrumentation_tags) | ||
logger.info("[JobIteration::Iteration] Interrupting and re-enqueueing the job cursor_position=#{cursor_position}") | ||
ActiveSupport::Notifications.instrument("interrupted.iteration", instrumentation_tags) | ||
|
||
self.times_interrupted += 1 | ||
|
||
|
@@ -283,13 +284,8 @@ def method_parameters(method_name) | |
method.parameters | ||
end | ||
|
||
def iteration_instrumentation_tags | ||
{ job_class: self.class.name } | ||
end | ||
|
||
def output_interrupt_summary | ||
message = "[JobIteration::Iteration] Completed iterating. times_interrupted=%d total_time=%.3f" | ||
logger.info(Kernel.format(message, times_interrupted, total_time)) | ||
def instrumentation_tags | ||
{ job_class: self.class.name, cursor_position: cursor_position } | ||
end | ||
|
||
def job_should_exit? | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
# frozen_string_literal: true | ||
|
||
module JobIteration | ||
class LogSubscriber < ActiveSupport::LogSubscriber | ||
def logger | ||
JobIteration.logger | ||
end | ||
|
||
def nil_enumerator(event) | ||
info do | ||
"[JobIteration::Iteration] `build_enumerator` returned nil. Skipping the job." | ||
end | ||
bdewater marked this conversation as resolved.
Show resolved
Hide resolved
|
||
end | ||
|
||
def not_found(event) | ||
info do | ||
"[JobIteration::Iteration] Enumerator found nothing to iterate! " \ | ||
"times_interrupted=#{event.payload[:times_interrupted]} cursor_position=#{event.payload[:cursor_position]}" | ||
end | ||
end | ||
|
||
def interrupted(event) | ||
info do | ||
"[JobIteration::Iteration] Interrupting and re-enqueueing the job " \ | ||
"cursor_position=#{event.payload[:cursor_position]}" | ||
end | ||
end | ||
|
||
def completed(event) | ||
info do | ||
message = "[JobIteration::Iteration] Completed iterating. times_interrupted=%d total_time=%.3f" | ||
Kernel.format(message, event.payload[:times_interrupted], event.payload[:total_time]) | ||
end | ||
end | ||
end | ||
end | ||
|
||
JobIteration::LogSubscriber.attach_to(:iteration) |
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.
This does change something. Previously we ended up logging to
Rails.logger
, because when ActiveJob initializes itself in the context of a Rails application, it sets its logger to the Rails one:https://github.com/rails/rails/blob/v7.0.7.2/activejob/lib/active_job/railtie.rb#L13-L15
Whereas now it will use the default Active Job logger which logs to stdout: https://github.com/rails/rails/blob/v7.0.7.2/activejob/lib/active_job/logging.rb#L11
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.
TIL. Thanks for also providing a fix - linking #427 here in case others run into this problem.