Skip to content

Commit b24149e

Browse files
authored
Swap Active Job log subscriber safely (reidmorrison#97)
1 parent 5f34ab1 commit b24149e

File tree

5 files changed

+131
-122
lines changed

5 files changed

+131
-122
lines changed

lib/rails_semantic_logger.rb

+3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ module ActionController
99
module ActionView
1010
autoload :LogSubscriber, 'rails_semantic_logger/action_view/log_subscriber'
1111
end
12+
module ActiveJob
13+
autoload :LogSubscriber, 'rails_semantic_logger/active_job/log_subscriber'
14+
end
1215
module ActiveRecord
1316
autoload :LogSubscriber, 'rails_semantic_logger/active_record/log_subscriber'
1417
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
require 'active_job'
2+
3+
module RailsSemanticLogger
4+
module ActiveJob
5+
class LogSubscriber < ::ActiveSupport::LogSubscriber
6+
def enqueue(event)
7+
log_with_formatter event: event do |fmt|
8+
"Enqueued #{fmt.job_info}"
9+
end
10+
end
11+
12+
def enqueue_at(event)
13+
log_with_formatter event: event do |fmt|
14+
"Enqueued #{fmt.job_info} at #{fmt.scheduled_at}"
15+
end
16+
end
17+
18+
def perform_start(event)
19+
log_with_formatter event: event do |fmt|
20+
"Performing #{fmt.job_info}"
21+
end
22+
end
23+
24+
def perform(event)
25+
ex = event.payload[:exception_object]
26+
if ex
27+
logger.error ex
28+
else
29+
log_with_formatter event: event, log_duration: true do |fmt|
30+
"Performed #{fmt.job_info} in #{event.duration.round(2)}ms"
31+
end
32+
end
33+
end
34+
35+
private
36+
37+
class EventFormatter
38+
def initialize(event:, log_duration: false)
39+
@event = event
40+
@log_duration = log_duration
41+
end
42+
43+
def job_info
44+
"#{job.class.name} (Job ID: #{job.job_id}) to #{queue_name}"
45+
end
46+
47+
def payload
48+
{}.tap do |h|
49+
h[:event_name] = event.name
50+
h[:adapter] = adapter_name
51+
h[:queue] = job.queue_name
52+
h[:job_class] = job.class.name
53+
h[:job_id] = job.job_id
54+
h[:provider_job_id] = job.try(:provider_job_id) # Not available in Rails 4.2
55+
h[:duration] = event.duration.round(2) if log_duration?
56+
h[:arguments] = formatted_args
57+
end
58+
end
59+
60+
def queue_name
61+
adapter_name + "(#{job.queue_name})"
62+
end
63+
64+
def scheduled_at
65+
Time.at(event.payload[:job].scheduled_at).utc
66+
end
67+
68+
private
69+
70+
attr_reader :event
71+
72+
def job
73+
event.payload[:job]
74+
end
75+
76+
def adapter_name
77+
event.payload[:adapter].class.name.demodulize.remove('Adapter')
78+
end
79+
80+
def formatted_args
81+
JSON.pretty_generate(job.arguments.map { |arg| format(arg) })
82+
end
83+
84+
def format(arg)
85+
case arg
86+
when Hash
87+
arg.transform_values { |value| format(value) }
88+
when Array
89+
arg.map { |value| format(value) }
90+
when GlobalID::Identification
91+
begin
92+
arg.to_global_id
93+
rescue StandardError
94+
arg
95+
end
96+
else
97+
arg
98+
end
99+
end
100+
101+
def log_duration?
102+
@log_duration
103+
end
104+
end
105+
106+
def log_with_formatter(**kw_args)
107+
fmt = EventFormatter.new(**kw_args)
108+
msg = yield fmt
109+
logger.info msg, fmt.payload
110+
end
111+
112+
def logger
113+
::ActiveJob::Base.logger
114+
end
115+
end
116+
end
117+
end

lib/rails_semantic_logger/engine.rb

+9
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,15 @@ class Engine < ::Rails::Engine
137137
require('rails_semantic_logger/extensions/active_model_serializers/logging') if defined?(ActiveModelSerializers)
138138

139139
if config.rails_semantic_logger.semantic
140+
# Active Job
141+
if defined?(::ActiveJob)
142+
RailsSemanticLogger.swap_subscriber(
143+
::ActiveJob::Logging::LogSubscriber,
144+
RailsSemanticLogger::ActiveJob::LogSubscriber,
145+
:active_job
146+
)
147+
end
148+
140149
# Active Record
141150
if defined?(::ActiveRecord)
142151
require 'active_record/log_subscriber'

lib/rails_semantic_logger/extensions/active_job/logging.rb

-120
Original file line numberDiff line numberDiff line change
@@ -10,126 +10,6 @@ module Logging
1010
def tag_logger(*tags, &block)
1111
logger.tagged(*tags, &block)
1212
end
13-
14-
class LogSubscriber < ActiveSupport::LogSubscriber
15-
def enqueue(event)
16-
log_with_formatter event: event do |fmt|
17-
"Enqueued #{fmt.job_info}"
18-
end
19-
end
20-
21-
def enqueue_at(event)
22-
log_with_formatter event: event do |fmt|
23-
"Enqueued #{fmt.job_info} at #{fmt.scheduled_at}"
24-
end
25-
end
26-
27-
def perform_start(event)
28-
log_with_formatter event: event do |fmt|
29-
"Performing #{fmt.job_info}"
30-
end
31-
end
32-
33-
def perform(event)
34-
ex = event.payload[:exception_object]
35-
if ex
36-
logger.error ex
37-
else
38-
log_with_formatter event: event, log_duration: true do |fmt|
39-
"Performed #{fmt.job_info} in #{event.duration.round(2)}ms"
40-
end
41-
end
42-
end
43-
44-
private
45-
46-
class EventFormatter
47-
def initialize(event:, log_duration: false)
48-
@event = event
49-
@log_duration = log_duration
50-
end
51-
52-
def job_info
53-
"#{job.class.name} (Job ID: #{job.job_id}) to #{queue_name}"
54-
end
55-
56-
def payload
57-
{}.tap do |h|
58-
h[:event_name] = event.name
59-
h[:adapter] = adapter_name
60-
h[:queue] = job.queue_name
61-
h[:job_class] = job.class.name
62-
h[:job_id] = job.job_id
63-
h[:provider_job_id] = job.try(:provider_job_id) # Not available in Rails 4.2
64-
h[:duration] = event.duration.round(2) if log_duration?
65-
h[:arguments] = formatted_args
66-
end
67-
end
68-
69-
def queue_name
70-
adapter_name + "(#{job.queue_name})"
71-
end
72-
73-
def scheduled_at
74-
Time.at(event.payload[:job].scheduled_at).utc
75-
end
76-
77-
private
78-
79-
attr_reader :event
80-
81-
def job
82-
event.payload[:job]
83-
end
84-
85-
def adapter_name
86-
event.payload[:adapter].class.name.demodulize.remove('Adapter')
87-
end
88-
89-
def formatted_args
90-
JSON.pretty_generate(job.arguments.map { |arg| format(arg) })
91-
end
92-
93-
def format(arg)
94-
case arg
95-
when Hash
96-
arg.transform_values { |value| format(value) }
97-
when Array
98-
arg.map { |value| format(value) }
99-
when GlobalID::Identification
100-
begin
101-
arg.to_global_id
102-
rescue StandardError
103-
arg
104-
end
105-
else
106-
arg
107-
end
108-
end
109-
110-
def log_duration?
111-
@log_duration
112-
end
113-
end
114-
115-
def log_with_formatter(**kw_args)
116-
fmt = EventFormatter.new(**kw_args)
117-
msg = yield fmt
118-
logger.info msg, fmt.payload
119-
end
120-
121-
def logger
122-
ActiveJob::Base.logger
123-
end
124-
end
12513
end
12614
end
12715

128-
if defined?(ActiveSupport::Notifications)
129-
ActiveSupport::Notifications.unsubscribe('perform_start.active_job')
130-
ActiveSupport::Notifications.unsubscribe('perform.active_job')
131-
ActiveSupport::Notifications.unsubscribe('enqueue_at.active_job')
132-
ActiveSupport::Notifications.unsubscribe('enqueue.active_job')
133-
134-
ActiveJob::Logging::LogSubscriber.attach_to :active_job
135-
end

test/active_job_test.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ def id
3939
skip 'Older rails does not support ActiveSupport::Notification' unless defined?(ActiveSupport::Notifications)
4040
end
4141

42-
let(:subscriber) { ActiveJob::Logging::LogSubscriber.new }
42+
let(:subscriber) { RailsSemanticLogger::ActiveJob::LogSubscriber.new }
4343

4444
let(:event) do
4545
ActiveSupport::Notifications::Event.new 'enqueue.active_job',
@@ -67,7 +67,7 @@ def id
6767

6868
describe 'ActiveJob::Logging::LogSubscriber::EventFormatter' do
6969
let(:formatter) do
70-
ActiveJob::Logging::LogSubscriber::EventFormatter.new(event: event, log_duration: true)
70+
RailsSemanticLogger::ActiveJob::LogSubscriber::EventFormatter.new(event: event, log_duration: true)
7171
end
7272

7373
let(:event) do

0 commit comments

Comments
 (0)