Skip to content

Commit e54ce1a

Browse files
authored
Vets::SharedLogging example (#21130)
* Vets::SharedLogging example * Add deprecation warning for SentryLogging * Use SharedLogging in ExceptionHandling
1 parent 20c2196 commit e54ce1a

File tree

10 files changed

+109
-55
lines changed

10 files changed

+109
-55
lines changed

app/controllers/concerns/exception_handling.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44
require 'common/client/errors'
55
require 'json_schema/json_api_missing_attribute'
66
require 'datadog'
7+
require 'vets/shared_logging'
78

89
module ExceptionHandling
910
extend ActiveSupport::Concern
11+
include Vets::SharedLogging
1012

1113
# In addition to Common::Exceptions::BackendServiceException that have sentry_type :none the following exceptions
1214
# will also be skipped.
@@ -76,6 +78,7 @@ def report_original_exception(exception)
7678
elsif exception.is_a?(Common::Exceptions::BackendServiceException) && exception.generic_error?
7779
# Warn about VA900 needing to be added to exception.en.yml
7880
log_message_to_sentry(exception.va900_warning, :warn, i18n_exception_hint: exception.va900_hint)
81+
log_message_to_rails(exception.va900_warning, :warn, i18n_exception_hint: exception.va900_hint)
7982
end
8083
end
8184

@@ -88,6 +91,7 @@ def report_mapped_exception(exception, va_exception)
8891
end
8992
va_exception_info = { va_exception_errors: va_exception.errors.map(&:to_hash) }
9093
log_exception_to_sentry(exception, extra.merge(va_exception_info))
94+
log_exception_to_rails(exception)
9195

9296
# Because we are handling exceptions here and not re-raising, we need to set the error on the
9397
# Datadog span for it to be reported correctly. We also need to set it on the top-level

app/models/form_attachment.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
# frozen_string_literal: true
22

3+
require 'vets/shared_logging'
4+
35
class FormAttachment < ApplicationRecord
46
include SetGuid
5-
include SentryLogging
7+
include Vets::SharedLogging
68

79
has_kms_key
810
has_encrypted :file_data, key: :kms_key, **lockbox_options
@@ -18,6 +20,7 @@ def set_file_data!(file, file_password = nil)
1820
self.file_data = { filename: attachment_uploader.filename }.to_json
1921
rescue CarrierWave::IntegrityError => e
2022
log_exception_to_sentry(e, nil, nil, 'warn')
23+
log_exception_to_rails(e, 'warn')
2124
raise Common::Exceptions::UnprocessableEntity.new(detail: e.message, source: 'FormAttachment.set_file_data')
2225
end
2326

@@ -46,6 +49,7 @@ def unlock_pdf(file, file_password)
4649
password_regex = /(input_pw).*?(output)/
4750
sanitized_message = e.message.gsub(file_regex, '[FILTERED FILENAME]').gsub(password_regex, '\1 [FILTERED] \2')
4851
log_message_to_sentry(sanitized_message, 'warn')
52+
log_message_to_rails(sanitized_message, 'warn')
4953
raise Common::Exceptions::UnprocessableEntity.new(
5054
detail: I18n.t('errors.messages.uploads.pdf.incorrect_password'),
5155
source: 'FormAttachment.unlock_pdf'

lib/sentry_logging.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
require 'sentry_logging'
44

55
module SentryLogging
6+
# WARNING: This module is deprecated, and will be removed in June 2025. Please use Vets::SharedLogging instead.
7+
# If your team currently uses this module, please see documentation for migrating to Vets::SharedLogging: TODO
8+
ActiveSupport::Deprecation
9+
.new.warn('SentryLogging is deprecated and will be removed in June 2025. Use Vets::SharedLogging instead.')
610
extend self
711

812
def log_message_to_sentry(message, level, extra_context = {}, tags_context = {})

lib/vets/shared_logging.rb

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# frozen_string_literal: true
2+
3+
module Vets
4+
module SharedLogging
5+
extend ActiveSupport::Concern
6+
7+
def log_message_to_sentry(message, level, extra_context = {}, tags_context = {})
8+
level = normalize_shared_level(level, nil)
9+
# https://docs.sentry.io/platforms/ruby/usage/set-level/
10+
# valid sentry levels: log, debug, info, warning, error, fatal
11+
level = 'warning' if level == 'warn'
12+
13+
if Settings.sentry.dsn.present?
14+
set_sentry_metadata(extra_context, tags_context)
15+
Sentry.capture_message(message, level:)
16+
end
17+
end
18+
19+
def log_exception_to_sentry(exception, extra_context = {}, tags_context = {}, level = 'error')
20+
level = normalize_shared_level(level, exception)
21+
22+
if Settings.sentry.dsn.present?
23+
set_sentry_metadata(extra_context, tags_context)
24+
Sentry.capture_exception(exception.cause.presence || exception, level:)
25+
end
26+
end
27+
28+
def log_message_to_rails(message, level, extra_context = {})
29+
# this can be a drop-in replacement for now, but maybe suggest teams
30+
# handle extra context on their own and move to a direct Rails.logger call?
31+
formatted_message = extra_context.empty? ? message : "#{message} : #{extra_context}"
32+
Rails.logger.send(level, formatted_message)
33+
end
34+
35+
def log_exception_to_rails(exception, level = 'error')
36+
level = normalize_shared_level(level, exception)
37+
if exception.is_a? Common::Exceptions::BackendServiceException
38+
error_details = exception.errors.first.attributes.compact.reject { |_k, v| v.try(:empty?) }
39+
log_message_to_rails(exception.message, level, error_details.merge(backtrace: exception.backtrace))
40+
else
41+
log_message_to_rails("#{exception.message}.", level)
42+
end
43+
44+
log_message_to_rails(exception.backtrace.join("\n"), level) unless exception.backtrace.nil?
45+
end
46+
47+
def normalize_shared_level(level, exception)
48+
case exception
49+
when Pundit::NotAuthorizedError
50+
'info'
51+
when Common::Exceptions::BaseError
52+
# could change this attribute to log_level
53+
# to make clear it is not just a Sentry concern
54+
exception.sentry_type.to_s
55+
else
56+
level.to_s
57+
end
58+
end
59+
60+
def non_nil_hash?(h)
61+
h.is_a?(Hash) && !h.empty?
62+
end
63+
64+
private
65+
66+
def set_sentry_metadata(extra_context, tags_context)
67+
Sentry.set_extras(extra_context) if non_nil_hash?(extra_context)
68+
Sentry.set_tags(tags_context) if non_nil_hash?(tags_context)
69+
end
70+
end
71+
end

modules/decision_reviews/app/controllers/decision_reviews/application_controller.rb

Lines changed: 0 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -24,46 +24,5 @@ def set_csrf_header
2424
token = form_authenticity_token
2525
response.set_header('X-CSRF-Token', token)
2626
end
27-
28-
def log_exception(exception, _extra_context = {}, _tags_context = {}, level = 'error')
29-
level = normalize_level(level, exception)
30-
31-
if exception.is_a? Common::Exceptions::BackendServiceException
32-
rails_logger(level, exception.message, exception.errors, exception.backtrace)
33-
else
34-
rails_logger(level, "#{exception.message}.")
35-
end
36-
rails_logger(level, exception.backtrace.join("\n")) unless exception.backtrace.nil?
37-
end
38-
39-
alias log_exception_to_sentry log_exception # Method call in shared ExceptionHandling is :log_exception_to_sentry
40-
41-
def normalize_level(level, exception)
42-
# https://docs.sentry.io/platforms/ruby/usage/set-level/
43-
# valid sentry levels: log, debug, info, warning, error, fatal
44-
level = case exception
45-
when Pundit::NotAuthorizedError
46-
'info'
47-
when Common::Exceptions::BaseError
48-
exception.sentry_type.to_s
49-
else
50-
level.to_s
51-
end
52-
53-
return 'warning' if level == 'warn'
54-
55-
level
56-
end
57-
58-
def rails_logger(level, message, errors = nil, backtrace = nil)
59-
# rails logger uses 'warn' instead of 'warning'
60-
level = 'warn' if level == 'warning'
61-
if errors.present?
62-
error_details = errors.first.attributes.compact.reject { |_k, v| v.try(:empty?) }
63-
Rails.logger.send(level, message, error_details.merge(backtrace:))
64-
else
65-
Rails.logger.send(level, message)
66-
end
67-
end
6827
end
6928
end

modules/decision_reviews/spec/requests/v1/notice_of_disagreements_spec.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,9 @@ def personal_information_logs
120120
message: "Exception occurred while submitting Notice Of Disagreement: #{extra_error_log_message}",
121121
backtrace: anything
122122
)
123-
expect(Rails.logger).to receive(:error).with(extra_error_log_message, anything)
123+
expect(Rails.logger).to receive(:error) do |message|
124+
expect(message).to include(extra_error_log_message)
125+
end
124126
allow(StatsD).to receive(:increment)
125127
expect(StatsD).to receive(:increment).with('decision_review.form_10182.overall_claim_submission.failure')
126128
expect(personal_information_logs.count).to be 0

modules/decision_reviews/spec/requests/v1/supplemental_claims_spec.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,9 @@ def personal_information_logs
106106
message: "Exception occurred while submitting Supplemental Claim: #{extra_error_log_message}",
107107
backtrace: anything
108108
)
109-
expect(Rails.logger).to receive(:error).with(extra_error_log_message, anything)
109+
expect(Rails.logger).to receive(:error) do |message|
110+
expect(message).to include(extra_error_log_message)
111+
end
110112
allow(StatsD).to receive(:increment)
111113
expect(StatsD).to receive(:increment).with('decision_review.form_995.overall_claim_submission.failure')
112114

modules/decision_reviews/spec/requests/v2/higher_level_reviews_spec.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,9 @@ def personal_information_logs
107107
message: "Exception occurred while submitting Higher Level Review: #{extra_error_log_message}",
108108
backtrace: anything
109109
)
110-
expect(Rails.logger).to receive(:error).with(extra_error_log_message, anything)
110+
expect(Rails.logger).to receive(:error) do |message|
111+
expect(message).to include(extra_error_log_message)
112+
end
111113
allow(StatsD).to receive(:increment)
112114
expect(StatsD).to receive(:increment).with('decision_review.form_996.overall_claim_submission.failure')
113115

spec/controllers/application_controller_spec.rb

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,6 @@ def append_info_to_payload(payload)
9393
before { allow(Settings.sentry).to receive(:dsn).and_return('asdf') }
9494

9595
describe '#log_message_to_sentry' do
96-
it 'error logs to Rails logger' do
97-
expect(Rails.logger).to receive(:error).with(/blah/).with(/context/)
98-
subject.log_message_to_sentry('blah', :error, { extra: 'context' }, tags: 'tagging')
99-
end
100-
10196
it 'logs to Sentry' do
10297
expect(Sentry).to receive(:set_tags)
10398
expect(Sentry).to receive(:set_extras)
@@ -106,17 +101,26 @@ def append_info_to_payload(payload)
106101
end
107102
end
108103

109-
describe '#log_exception_to_sentry' do
110-
it 'warn logs to Rails logger' do
111-
expect(Rails.logger).to receive(:error).with("#{exception.message}.")
112-
subject.log_exception_to_sentry(exception)
104+
describe '#log_message_to_rails' do
105+
it 'error logs to Rails logger' do
106+
expect(Rails.logger).to receive(:error).with(/blah/).with(/context/)
107+
subject.log_message_to_rails('blah', :error, { extra: 'context' })
113108
end
109+
end
114110

111+
describe '#log_exception_to_sentry' do
115112
it 'logs to Sentry' do
116113
expect(Sentry).to receive(:capture_exception).with(exception, level: 'error').once
117114
subject.log_exception_to_sentry(exception)
118115
end
119116
end
117+
118+
describe '#log_exception_to_rails' do
119+
it 'warn logs to Rails logger' do
120+
expect(Rails.logger).to receive(:error).with("#{exception.message}.")
121+
subject.log_exception_to_rails(exception)
122+
end
123+
end
120124
end
121125

122126
context 'without SENTRY_DSN set' do

spec/models/form_attachment_spec.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,12 @@
1515
let(:bad_password) { 'bad_pw' }
1616

1717
context 'when provided password is incorrect' do
18-
it 'logs a sanitized message to Sentry' do
18+
it 'logs a sanitized message to Sentry and Rails' do
1919
error_message = nil
2020
allow_any_instance_of(FormAttachment).to receive(:log_message_to_sentry) do |_, message, _level|
2121
error_message = message
2222
end
23+
allow(Rails.logger).to receive(:warn)
2324

2425
tempfile = Tempfile.new(['', "-#{file_name}"])
2526
file = ActionDispatch::Http::UploadedFile.new(original_filename: file_name, type: 'application/pdf',
@@ -30,6 +31,7 @@
3031
end.to raise_error(Common::Exceptions::UnprocessableEntity)
3132
expect(error_message).not_to include(file_name)
3233
expect(error_message).not_to include(bad_password)
34+
expect(Rails.logger).to have_received(:warn)
3335
end
3436
end
3537
end

0 commit comments

Comments
 (0)