diff --git a/CHANGELOG.md b/CHANGELOG.md index fcb9a6c..51efcc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Is It Ready? CHANGELOG +## 0.0.5 +* Silence Rails and the Rack middleware by default +* Add an option to allow logging of requests + ## 0.0.4 * Add support for HTTP Authorization Bearer tokens diff --git a/lib/is_it_ready.rb b/lib/is_it_ready.rb index 5a6b2e3..02f34e4 100644 --- a/lib/is_it_ready.rb +++ b/lib/is_it_ready.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +require 'is_it_ready/log_silencer' require "is_it_ready/engine" # The namespace holding everything related to our Gem. @@ -19,4 +20,9 @@ module IsItReady # When enabled however, the request will need to provide the required token as a Bearer value # in the AUTHORIZATION header of the request. mattr_accessor :bearer_token + + # Silences the logging of the request against the endpoint. Defaults to true. + # When disabled, the entire request will appear in the Rails logs. + mattr_accessor :silence_logs + @@silence_logs = true end diff --git a/lib/is_it_ready/engine.rb b/lib/is_it_ready/engine.rb index 2a02e8c..608d2b1 100644 --- a/lib/is_it_ready/engine.rb +++ b/lib/is_it_ready/engine.rb @@ -14,5 +14,11 @@ class Engine < ::Rails::Engine mount ::IsItReady::Engine => ::IsItReady.endpoint end end + + # If the user has enabled the silencing of the loggers, we will mount the middleware + # to do so, otherwise skip the process entirely. + initializer 'is_it_ready.add_middleware' do |app| + app.middleware.insert_before(::Rails::Rack::Logger, ::IsItReady::LogSilencer, silenced: ::IsItReady.endpoint) + end end end diff --git a/lib/is_it_ready/log_silencer.rb b/lib/is_it_ready/log_silencer.rb new file mode 100644 index 0000000..91c54d0 --- /dev/null +++ b/lib/is_it_ready/log_silencer.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module IsItReady + # This class is a Rack::Middleware implementation that will support us in silencing the + # logging of calls for incoming requests against the endpoint. Otherwise the app would be + # writing all requests in the Rails logs, causing an overload of information to be reported + # that's simply not relevant. The usage of this Middleware can be controlled through the Engine's + # configuration on whether to silence logging or not. + class LogSilencer + # Creates a new instance of the Middleware and initializes it using the Rack standard approach + # for setting up the required values in a Rack::Middleware. + def initialize(app, opts = {}) + @silenced = opts.delete(:silenced) + @app = app + end + + # Executes the Middleware. + # If the environment contains the special X-SILENCE-LOGGER header to globally silence the request, + # or the path matches the provided silence configuration, the middleware will silence Rails for the + # request, otherwise pass the request along. + def call(env) + if ::IsItReady.silence_logs && silence_path?(env['PATH_INFO']) + ::Rails.logger.silence do + @app.call(env) + end + else + @app.call(env) + end + end + + private + + # Returns true when the given path needs to be silenced. + # This uses a manual Regex check, since the .match? method might not exist depending on the Ruby + # version that's being used for this gem. So we perform a manual match and return true if there's + # a 0 response. + def silence_path?(path) + (path =~ /#{@silenced}/).present? + end + end +end diff --git a/lib/is_it_ready/version.rb b/lib/is_it_ready/version.rb index ea8cb1c..40a90de 100644 --- a/lib/is_it_ready/version.rb +++ b/lib/is_it_ready/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module IsItReady - VERSION = '0.0.4' + VERSION = '0.0.5' end diff --git a/test/integration/custom_navigation_test.rb b/test/integration/custom_navigation_test.rb index f02b9d1..7ae1743 100644 --- a/test/integration/custom_navigation_test.rb +++ b/test/integration/custom_navigation_test.rb @@ -1,12 +1,20 @@ require 'test_helper' module IsItReady + # This test verifies whether the Engine respects the dynamic configuration of the endpoint, + # allowing it to be dynamically loaded when Rails loads the configuration, ensuring that we can + # mount the engine using a custom path when required. class CustomNavigationTest < ActionDispatch::IntegrationTest include Engine.routes.url_helpers setup do ::IsItReady.endpoint = '/something_else' - Rails.application.reload_routes! + ::Rails.application.reload_routes! + end + + teardown do + ::IsItReady.endpoint = ::IsItReady::DEFAULT_PATH + ::Rails.application.reload_routes! end test('it returns the correct response status on the root') do diff --git a/test/integration/logging_test.rb b/test/integration/logging_test.rb new file mode 100644 index 0000000..0b8e688 --- /dev/null +++ b/test/integration/logging_test.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'test_helper' + +module IsItReady + # This class tests whether the ::Rails.logger is properly enabled during the execution of the Rails.engine. + # By default silencing of the incoming requests on the health check is disabled to avoid the Rails logs + # from being spammed with the repeated health checks. With this test, we check whether the Engine + # respects the configuration at runtime. + class SilentLoggingTest < ::ActionDispatch::IntegrationTest + include Engine.routes.url_helpers + include ::LoggerIntrospection + + setup do + ::IsItReady.endpoint = ::IsItReady::DEFAULT_PATH + ::IsItReady.silence_logs = false + end + + teardown do + ::IsItReady.silence_logs = true + end + + test('it writes the request to the standard Rails logger') do + with_logger_introspection do |logger_output| + get root_url + + assert_match(/Started GET "#{::IsItReady.endpoint}\/" for 127.0.0.1 at */, logger_output.string) + end + end + end +end diff --git a/test/integration/silent_logging_test.rb b/test/integration/silent_logging_test.rb new file mode 100644 index 0000000..7a45d48 --- /dev/null +++ b/test/integration/silent_logging_test.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'test_helper' + +module IsItReady + # This class tests whether the ::Rails.logger is properly silenced during the execution of the Rails.engine. + # By default silencing of the incoming requests on the health check is disabled to avoid the Rails logs + # from being spammed with the repeated health checks. + class SilentLoggingTest < ::ActionDispatch::IntegrationTest + include Engine.routes.url_helpers + include ::LoggerIntrospection + + test('it does not write anything to the standard Rails logger') do + with_logger_introspection do |logger_output| + get root_url + + refute_match(/Started GET "#{::IsItReady.endpoint}" for 127.0.0.1 at */, logger_output.string) + end + end + end +end diff --git a/test/support/logger_introspection.rb b/test/support/logger_introspection.rb new file mode 100644 index 0000000..b0b53d3 --- /dev/null +++ b/test/support/logger_introspection.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +# This module contains methods that can be used in tests to perform introspection on the Rails logger. +# This allows us to wrap some support functionality inside our tests and check whether certain behavior +# is implemented correctly without overloading the test itself. +module LoggerIntrospection + # Performs introspection on the ::Rails.logger with the given block inside the test. + # The method will duplicate the original logger, and replace the logger with a simple StringIO object. + # All log entries are then made available in the object, and after the test the logger is restored + # to the original functionality to not affect other tests. + def with_logger_introspection(&block) + original_logger = ::Rails.logger.dup + @logger_output = ::StringIO.new + + begin + ::Rails.logger = ::ActiveSupport::Logger.new(@logger_output) + block.call(@logger_output) + ensure + ::Rails.logger = original_logger + end + end +end