diff --git a/config/initializers/deprecation_warning_subscriber.rb b/config/initializers/deprecation_warning_subscriber.rb new file mode 100644 index 00000000000..89ee7dafced --- /dev/null +++ b/config/initializers/deprecation_warning_subscriber.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +# @note For use in conjuction with setting `Rails.application.config.active_support.deprecation = :notify`. +# Whenever a “deprecation.rails” notification is published, it will dispatch the event +# (ActiveSupport::Notifications::Event) to method #deprecation. +class DeprecationWarningSubscriber < ActiveSupport::Subscriber + APP_NAME = "caseflow" + SLACK_ALERT_CHANNEL = "#appeals-deprecation-alerts" + + attach_to :rails + + def deprecation(event) + emit_warning_to_application_logs(event) + emit_warning_to_sentry(event) + emit_warning_to_slack_alerts_channel(event) + rescue StandardError => error + Raven.capture_exception(error) + end + + private + + def emit_warning_to_application_logs(event) + Rails.logger.warn(event.payload[:message]) + end + + def emit_warning_to_sentry(event) + # Pre-emptive bugfix for future versions of the `sentry-raven` gem: + # Need to convert callstack elements from `Thread::Backtrace::Location` objects to `Strings` + # to avoid a `TypeError` on `options.deep_dup` in `Raven.capture_message`: + # https://github.com/getsentry/sentry-ruby/blob/2e07e0295ba83df4c76c7bf3315d199c7050a7f9/lib/raven/instance.rb#L114 + callstack_strings = event.payload[:callstack].map(&:to_s) + + Raven.capture_message( + event.payload[:message], + level: "warning", + extra: { + message: event.payload[:message], + gem_name: event.payload[:gem_name], + deprecation_horizon: event.payload[:deprecation_horizon], + callstack: callstack_strings, + environment: Rails.env + } + ) + end + + def emit_warning_to_slack_alerts_channel(event) + slack_alert_title = "Deprecation Warning - #{APP_NAME} (#{ENV['DEPLOY_ENV']})" + + SlackService + .new(url: ENV["SLACK_DISPATCH_ALERT_URL"]) + .send_notification(event.payload[:message], slack_alert_title, SLACK_ALERT_CHANNEL) + end +end diff --git a/spec/initializers/deprecation_warning_subscriber_spec.rb b/spec/initializers/deprecation_warning_subscriber_spec.rb new file mode 100644 index 00000000000..57e88778983 --- /dev/null +++ b/spec/initializers/deprecation_warning_subscriber_spec.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +describe "DeprecationWarningSubscriber" do + let(:rails_logger) { Rails.logger } + let(:slack_service) { SlackService.new(url: "dummy-url") } + + before do + allow(Rails).to receive(:logger).and_return(rails_logger) + allow(rails_logger).to receive(:warn) + + allow(Raven).to receive(:capture_message) + allow(Raven).to receive(:capture_exception) + + allow(SlackService).to receive(:new).with(url: anything).and_return(slack_service) + allow(slack_service).to receive(:send_notification) + end + + context "when a 'deprecation.rails' event is instrumented" do + let(:app_name) { "caseflow" } + let(:deploy_env) { ENV["DEPLOY_ENV"] } + let(:payload) do + { + message: "test message", + gem_name: "Rails", + deprecation_horizon: "6.0", + callstack: [location_1, location_2] + } + end + let(:location_1) { instance_double("Thread::Backtrace::Location", to_s: "location 1") } + let(:location_2) { instance_double("Thread::Backtrace::Location", to_s: "location 2") } + + def instrument_deprecation_warning + ActiveSupport::Notifications.instrument("deprecation.rails", payload) + end + + it "emits a warning to the application logs" do + instrument_deprecation_warning + + expect(rails_logger).to have_received(:warn).with(payload[:message]) + end + + it "emits a warning to Sentry" do + instrument_deprecation_warning + + expect(Raven).to have_received(:capture_message).with( + payload[:message], + level: "warning", + extra: { + message: payload[:message], + gem_name: "Rails", + deprecation_horizon: "6.0", + callstack: ["location 1", "location 2"], + environment: Rails.env + } + ) + end + + it "emits a warning to Slack channel" do + slack_alert_title = "Deprecation Warning - #{app_name} (#{deploy_env})" + + instrument_deprecation_warning + + expect(slack_service).to have_received(:send_notification).with( + payload[:message], + slack_alert_title, + "#appeals-deprecation-alerts" + ) + end + + context "when an exception occurs" do + before { allow(slack_service).to receive(:send_notification).and_raise(StandardError) } + + it "logs error to Sentry" do + instrument_deprecation_warning + + expect(Raven).to have_received(:capture_exception).with(StandardError) + end + + it "does not raise error" do + expect { instrument_deprecation_warning }.not_to raise_error + end + end + end +end