Skip to content

Commit 86ae37b

Browse files
authored
Merge pull request reidmorrison#168 from houseninjadojo/master
feat: action mailer
2 parents a64c480 + 81a7cd7 commit 86ae37b

File tree

4 files changed

+232
-0
lines changed

4 files changed

+232
-0
lines changed

lib/rails_semantic_logger.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ module RailsSemanticLogger
66
module ActionController
77
autoload :LogSubscriber, "rails_semantic_logger/action_controller/log_subscriber"
88
end
9+
module ActionMailer
10+
autoload :LogSubscriber, "rails_semantic_logger/action_mailer/log_subscriber"
11+
end
912
module ActionView
1013
autoload :LogSubscriber, "rails_semantic_logger/action_view/log_subscriber"
1114
end
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
require "active_support/log_subscriber"
2+
require "action_mailer"
3+
4+
module RailsSemanticLogger
5+
module ActionMailer
6+
class LogSubscriber < ::ActiveSupport::LogSubscriber
7+
def deliver(event)
8+
ex = event.payload[:exception_object]
9+
message_id = event.payload[:message_id]
10+
duration = event.duration.round(1)
11+
if ex
12+
log_with_formatter event: event, log_duration: true, level: :error do |fmt|
13+
{
14+
message: "Error delivering mail #{message_id} (#{duration}ms)",
15+
exception: ex
16+
}
17+
end
18+
else
19+
message = begin
20+
if event.payload[:perform_deliveries]
21+
"Delivered mail #{message_id} (#{duration}ms)"
22+
else
23+
"Skipped delivery of mail #{message_id} as `perform_deliveries` is false"
24+
end
25+
end
26+
log_with_formatter event: event, log_duration: true do |fmt|
27+
{ message: message }
28+
end
29+
end
30+
end
31+
32+
# An email was generated.
33+
def process(event)
34+
mailer = event.payload[:mailer]
35+
action = event.payload[:action]
36+
duration = event.duration.round(1)
37+
log_with_formatter event: event do |fmt|
38+
{ message: "#{mailer}##{action}: processed outbound mail in #{duration}ms" }
39+
end
40+
end
41+
42+
private
43+
44+
class EventFormatter
45+
def initialize(event:, log_duration: false)
46+
@event = event
47+
@log_duration = log_duration
48+
end
49+
50+
def mailer
51+
event.payload[:mailer]
52+
end
53+
54+
def payload
55+
{}.tap do |h|
56+
h[:event_name] = event.name
57+
h[:mailer] = mailer
58+
h[:action] = action
59+
h[:message_id] = event.payload[:message_id]
60+
h[:perform_deliveries] = event.payload[:perform_deliveries]
61+
h[:subject] = event.payload[:subject]
62+
h[:to] = event.payload[:to]
63+
h[:from] = event.payload[:from]
64+
h[:bcc] = event.payload[:bcc]
65+
h[:cc] = event.payload[:cc]
66+
h[:date] = date
67+
h[:duration] = event.duration.round(2) if log_duration?
68+
h[:args] = formatted_args
69+
end
70+
end
71+
72+
def date
73+
if event.payload[:date].respond_to?(:to_time)
74+
event.payload[:date].to_time.utc
75+
elsif event.payload[:date].is_a?(String)
76+
Time.parse(date).utc
77+
else
78+
nil
79+
end
80+
end
81+
82+
private
83+
84+
attr_reader :event
85+
86+
def mailer
87+
event.payload[:mailer]
88+
end
89+
90+
def action
91+
event.payload[:action]
92+
end
93+
94+
def formatted_args
95+
if defined?(mailer.contantize.log_arguments?) && !mailer.contantize.log_arguments?
96+
""
97+
else
98+
JSON.pretty_generate(event.payload[:args].map { |arg| format(arg) }) if event.payload[:args].present?
99+
end
100+
end
101+
102+
def format(arg)
103+
case arg
104+
when Hash
105+
arg.transform_values { |value| format(value) }
106+
when Array
107+
arg.map { |value| format(value) }
108+
when GlobalID::Identification
109+
begin
110+
arg.to_global_id
111+
rescue StandardError
112+
arg
113+
end
114+
else
115+
arg
116+
end
117+
end
118+
119+
def log_duration?
120+
@log_duration
121+
end
122+
end
123+
124+
def log_with_formatter(level: :info, **kw_args)
125+
fmt = EventFormatter.new(**kw_args)
126+
msg = yield fmt
127+
logger.public_send(level, **msg, payload: fmt.payload)
128+
end
129+
130+
def logger
131+
::ActionMailer::Base.logger
132+
end
133+
end
134+
end
135+
end

lib/rails_semantic_logger/engine.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
require "rails"
22
require "action_controller/log_subscriber"
33
require "action_view/log_subscriber"
4+
require "action_mailer/log_subscriber"
45
require "rails_semantic_logger/options"
56

67
module RailsSemanticLogger
@@ -198,6 +199,13 @@ class Engine < ::Rails::Engine
198199
RailsSemanticLogger::ActionController::LogSubscriber,
199200
:action_controller
200201
)
202+
203+
# Action Mailer
204+
RailsSemanticLogger.swap_subscriber(
205+
::ActionMailer::LogSubscriber,
206+
RailsSemanticLogger::ActionMailer::LogSubscriber,
207+
:action_mailer
208+
)
201209
end
202210

203211
#

test/action_mailer_test.rb

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
require_relative "test_helper"
2+
3+
class ActionMailerTest < Minitest::Test
4+
class MyMailer < ActionMailer::Base
5+
def some_email(to:, from:, subject:)
6+
mail(to: to, from: from, subject: subject, body: "Hello")
7+
end
8+
end
9+
10+
describe "ActionMailer" do
11+
before do
12+
::ActionMailer::Base.delivery_method = :test
13+
@mock_logger = MockLogger.new
14+
@appender = SemanticLogger.add_appender(logger: @mock_logger, formatter: :raw)
15+
end
16+
17+
after do
18+
SemanticLogger.remove_appender(@appender)
19+
end
20+
21+
describe "#deliver" do
22+
it "sets the ActionMailer logger" do
23+
assert_kind_of SemanticLogger::Logger, MyMailer.logger
24+
end
25+
26+
it "sends the email" do
27+
MyMailer.some_email(to: '[email protected]', from: '[email protected]', subject: 'test').deliver_now
28+
end
29+
end
30+
31+
describe "Logging::LogSubscriber" do
32+
before do
33+
skip "Older rails does not support ActiveSupport::Notification" unless defined?(ActiveSupport::Notifications)
34+
end
35+
36+
let(:subscriber) { RailsSemanticLogger::ActionMailer::LogSubscriber.new }
37+
38+
let(:event) do
39+
ActiveSupport::Notifications::Event.new event_name,
40+
5.seconds.ago,
41+
Time.zone.now,
42+
SecureRandom.uuid,
43+
payload
44+
end
45+
46+
let(:payload) do
47+
{
48+
mailer: 'MyMailer',
49+
action: :some_email,
50+
}
51+
end
52+
53+
let(:event_name) { "deliver.action_mailer" }
54+
55+
let(:mailer) do
56+
MyMailer.some_email(to: '[email protected]', from: '[email protected]', subject: 'test')
57+
end
58+
59+
%i[deliver process].each do |method|
60+
describe "##{method}" do
61+
specify do
62+
assert ActionMailer::Base.logger.info
63+
subscriber.public_send(method, event)
64+
end
65+
end
66+
end
67+
68+
describe "ActiveJob::Logging::LogSubscriber::EventFormatter" do
69+
let(:formatter) do
70+
RailsSemanticLogger::ActionMailer::LogSubscriber::EventFormatter.new(event: event, log_duration: true)
71+
end
72+
73+
let(:event_name) { "deliver.action_mailer" }
74+
75+
describe "#payload" do
76+
specify do
77+
assert_equal(formatter.payload[:event_name], "deliver.action_mailer")
78+
assert_equal(formatter.payload[:mailer], "MyMailer")
79+
assert_equal(formatter.payload[:action], :some_email)
80+
assert_kind_of(Float, formatter.payload[:duration])
81+
end
82+
end
83+
end
84+
end
85+
end
86+
end

0 commit comments

Comments
 (0)