From 17f7704c1d26cd712350b07cd8d646b26060c807 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Tue, 24 Mar 2026 09:53:46 +0000 Subject: [PATCH 1/2] fix: add request logger middleware --- config/initializers/request_logger.rb | 11 ++++ lib/middleware/request_logger.rb | 87 +++++++++++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 config/initializers/request_logger.rb create mode 100644 lib/middleware/request_logger.rb diff --git a/config/initializers/request_logger.rb b/config/initializers/request_logger.rb new file mode 100644 index 00000000..3a94825a --- /dev/null +++ b/config/initializers/request_logger.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require_relative '../../lib/middleware/request_logger' + +Rails.application.configure do + # Insert RequestLogger near the top, before Rails::Rack::Logger + config.middleware.insert_before(Rails::Rack::Logger, Middleware::RequestLogger) +end + +# Add backtrace silencers for middleware so that we don't see it in backtraces. +Rails.backtrace_cleaner.add_silencer { |line| line.include?('request_logger') } diff --git a/lib/middleware/request_logger.rb b/lib/middleware/request_logger.rb new file mode 100644 index 00000000..d2ad7bd9 --- /dev/null +++ b/lib/middleware/request_logger.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true + +module Middleware + # Log request and response details for monitoring and high-level profiling. + # + # @param log_level [Symbol] the log level to use for logging requests (default: :info) + # @param environment_context [Hash] additional context to include in log entries, such as host and version information + # + # Returns a JSON parseable log entry like: + # [INFO] [RequestLogger] {"method":"GET","path":"/samples/1234","format":"html","status_code":200, + # "status_message":"OK","duration_ms":935,"client_ip":"172.21.43.210", + # "request_id":"9fd18098-dea3-46f0-83c8-c41852441db3","tags":["request","success"], + # "@timestamp":"2026-02-12T12:10:50.284+00:00"} + class RequestLogger + def initialize(app, log_level: :info, environment_context: nil) + @app = app + @log_level = log_level + @environment_context = environment_context + end + + def call(env) + response, elapsed_ms = elapsed_milliseconds { @app.call(env) } + + request = ActionDispatch::Request.new(env) + log_request(request, response, elapsed_ms) + + response + end + + private + + # Get the current clock time using the Rack::Runtime clock + def clock_time + Rack::Utils.clock_time + end + + def elapsed_milliseconds + start_time = clock_time + result = yield + end_time = clock_time + + elapsed_ms = ((end_time - start_time) * 1000).round + [result, elapsed_ms] + end + + def tag_for_status(status_code) + case status_code + when 100..199 then 'informational' + when 200..299 then 'success' + when 300..399 then 'redirection' + when 400..499 then 'client_error' + when 500..599 then 'server_error' + end + end + + def tags(status_code) + tags = ['request'] + tags << tag_for_status(status_code) + tags.compact! + tags + end + + def log_request(request, response, elapsed_ms) # rubocop:disable Metrics/AbcSize + status_code, _headers, _body = response + + status_message = Rack::Utils::HTTP_STATUS_CODES[status_code] || 'Unknown Status' + timestamp = Time.zone.now.iso8601(3) + + record = { + method: request.request_method, + url: request.fullpath, + path: request.path, + format: request.format.symbol, + status_code: status_code, + status_message: status_message, + duration_ms: elapsed_ms, + client_ip: request.remote_ip, + request_id: request.request_id, + tags: tags(status_code), + '@timestamp': timestamp + } + record.merge!(@environment_context) if @environment_context.present? + + Rails.logger.public_send(@log_level, "[RequestLogger] #{record.to_json}") + end + end +end From 59aea60bcd92df1e7182444edda2498651c600ab Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Tue, 24 Mar 2026 10:11:39 +0000 Subject: [PATCH 2/2] style: lint --- lib/middleware/request_logger.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/middleware/request_logger.rb b/lib/middleware/request_logger.rb index d2ad7bd9..5942cbc1 100644 --- a/lib/middleware/request_logger.rb +++ b/lib/middleware/request_logger.rb @@ -60,7 +60,7 @@ def tags(status_code) tags end - def log_request(request, response, elapsed_ms) # rubocop:disable Metrics/AbcSize + def log_request(request, response, elapsed_ms) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength status_code, _headers, _body = response status_message = Rack::Utils::HTTP_STATUS_CODES[status_code] || 'Unknown Status'