-
Notifications
You must be signed in to change notification settings - Fork 3
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
Adapt default Rails logs to JSON format #17
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 |
---|---|---|
|
@@ -2,4 +2,5 @@ | |
.rspec_status | ||
.rubocop-*rubocop-yml | ||
.idea | ||
pkg/ | ||
pkg/ | ||
.byebug_history |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
require 'action_mailer/log_subscriber' | ||
|
||
## | ||
# Extends ActionMailer::LogSubscriber to improve JSON logging capabilities | ||
# Original source: https://github.com/rails/rails/blob/4-2-stable/actionmailer/lib/action_mailer/log_subscriber.rb | ||
# | ||
module Carto | ||
module Common | ||
class ActionMailerLogSubscriber < ActionMailer::LogSubscriber | ||
|
||
# Indicates Rails received a request to send an email. | ||
# The original payload of this event contains very little information | ||
def process(event) | ||
payload = event.payload | ||
|
||
info( | ||
message: 'Mail processed', | ||
mailer_class: payload[:mailer], | ||
mailer_action: payload[:action], | ||
duration_ms: event.duration.round(1) | ||
) | ||
end | ||
|
||
# Indicates Rails tried to send the email. Does not imply user received it, | ||
# as an error can still happen while sending it. | ||
def deliver(event) | ||
payload = event.payload | ||
|
||
info( | ||
message: 'Mail sent', | ||
mailer_class: payload[:mailer], | ||
message_id: payload[:message_id], | ||
current_user: current_user(payload), | ||
email_subject: payload[:subject], | ||
email_to_hint: email_to_hint(payload), | ||
email_from: payload[:from], | ||
email_date: payload[:date] | ||
) | ||
end | ||
|
||
private | ||
|
||
def current_user(event_payload) | ||
user_klass = defined?(Carto::User) ? Carto::User : User | ||
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. This allows identifying the user to whom the email was sent both in CartoDB + Central |
||
user_klass.find_by(email: event_payload[:to])&.username | ||
end | ||
|
||
def email_to_hint(event_payload) | ||
email_to = event_payload[:to] | ||
|
||
if email_to.is_a?(Array) | ||
email_to.map { |address| email_address_hint(address) } | ||
else | ||
[email_address_hint(email_to)] | ||
end | ||
end | ||
|
||
def email_address_hint(address) | ||
return unless address.present? | ||
if address.exclude?('@') || | ||
address.length < 8 || | ||
address.split('@').select(&:present?).size != 2 | ||
return '[ADDRESS]' | ||
end | ||
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 do a more aggressive obfuscation of email addresses in case they are very short. Check the specs for examples. |
||
|
||
address.split('@').map { |segment| "#{segment[0]}*****#{segment[-1]}" }.join('@') | ||
end | ||
|
||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
require_relative 'action_controller_log_subscriber' | ||
require_relative 'action_mailer_log_subscriber' | ||
require_relative 'logger_formatter' | ||
require_relative 'rack_logger_middleware' | ||
|
||
|
@@ -29,13 +30,16 @@ def self.remove_existing_log_subscriptions | |
unsubscribe(:action_view, subscriber) | ||
when ActionController::LogSubscriber | ||
unsubscribe(:action_controller, subscriber) | ||
when ActionMailer::LogSubscriber | ||
unsubscribe(:action_mailer, subscriber) | ||
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. This dettaches the default log subscriber (no JSON format, no user identification) |
||
end | ||
end | ||
end | ||
private_class_method :remove_existing_log_subscriptions | ||
|
||
def self.attach_custom_log_subscribers | ||
Carto::Common::ActionControllerLogSubscriber.attach_to(:action_controller) | ||
Carto::Common::ActionMailerLogSubscriber.attach_to(:action_mailer) | ||
end | ||
private_class_method :attach_custom_log_subscribers | ||
|
||
|
@@ -58,4 +62,4 @@ def self.unsubscribe(component, subscriber) | |
|
||
end | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
require 'spec_helper' | ||
require './lib/carto/common/action_mailer_log_subscriber' | ||
|
||
# Mocks CartoDB & Central users as they don't exist in the context of this gem | ||
class User | ||
|
||
attr_accessor :email, :username | ||
|
||
@users = [] | ||
|
||
class << self | ||
|
||
attr_reader :users | ||
|
||
end | ||
|
||
def self.find_by(params = {}) | ||
@users.select { |user| user.email == params[:email] }.first | ||
end | ||
|
||
def initialize(params = {}) | ||
self.email = params[:email] | ||
self.username = params[:username] | ||
User.users << self | ||
end | ||
|
||
end | ||
|
||
RSpec.describe 'Carto::Common::ActionMailerLogSubscriber' do | ||
subject { Carto::Common::ActionMailerLogSubscriber.new } | ||
|
||
let(:event_name) {} | ||
let(:event_payload) { {} } | ||
let(:event) { ActiveSupport::Notifications::Event.new(event_name, Time.now, Time.now + 1.second, 1, event_payload) } | ||
|
||
context '#process' do | ||
let(:event_name) { 'process.action_mailer' } | ||
let(:event_payload) { { mailer: 'DummyMailer', action: 'dummy_mailer_method' } } | ||
|
||
it 'logs email processing' do | ||
expect(subject).to receive(:info).with( | ||
message: 'Mail processed', | ||
mailer_class: 'DummyMailer', | ||
mailer_action: 'dummy_mailer_method', | ||
duration_ms: 1000.0 | ||
) | ||
|
||
subject.process(event) | ||
end | ||
end | ||
|
||
context '#deliver' do | ||
let(:user) { User.new(email: 'somebody@example.com') } | ||
let(:event_name) { 'deliver.action_mailer' } | ||
let(:receiver_addresses) { [user.email] } | ||
let(:event_payload) do | ||
{ | ||
mailer: 'DummyMailer', | ||
message_id: 1, | ||
subject: 'Subject', | ||
from: ['foo@bar.com'], | ||
to: receiver_addresses | ||
} | ||
end | ||
|
||
it 'logs email delivery' do | ||
expect(subject).to receive(:info).with( | ||
message: 'Mail sent', | ||
mailer_class: 'DummyMailer', | ||
message_id: 1, | ||
current_user: user.username, | ||
email_subject: 'Subject', | ||
email_to_hint: ['s*****y@e*****m'], | ||
email_from: ['foo@bar.com'], | ||
email_date: nil | ||
) | ||
|
||
subject.deliver(event) | ||
end | ||
|
||
context 'when several receivers' do | ||
let(:receiver_addresses) { ['first@mail.com', 'second@mail.com'] } | ||
|
||
it 'logs all the addresses' do | ||
expect(subject).to receive(:info).with(hash_including(email_to_hint: ['f*****t@m*****m', 's*****d@m*****m'])) | ||
subject.deliver(event) | ||
end | ||
end | ||
|
||
context 'when receiver address is missing' do | ||
let(:receiver_addresses) { nil } | ||
|
||
it 'does not break' do | ||
expect(subject).to receive(:info).with(hash_including(email_to_hint: [nil])) | ||
subject.deliver(event) | ||
end | ||
end | ||
|
||
context 'when receiver address is incorrect' do | ||
let(:receiver_addresses) { ['@incorrect'] } | ||
|
||
it 'logs a special string' do | ||
expect(subject).to receive(:info).with(hash_including(email_to_hint: ['[ADDRESS]'])) | ||
subject.deliver(event) | ||
end | ||
end | ||
|
||
context 'when receiver address is very short' do | ||
let(:receiver_addresses) { ['a@b.com'] } | ||
|
||
it 'logs a special string' do | ||
expect(subject).to receive(:info).with(hash_including(email_to_hint: ['[ADDRESS]'])) | ||
subject.deliver(event) | ||
end | ||
end | ||
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.
The reason for adding this dependency is that parent log subscriber class
ActionMailer::LogSubscriber
is defined there.