diff --git a/Gemfile b/Gemfile index 869d0ca5..3726795f 100644 --- a/Gemfile +++ b/Gemfile @@ -14,6 +14,7 @@ gem 'mitlibraries-theme', git: 'https://github.com/mitlibraries/mitlibraries-the gem 'puma' gem 'rack-attack' gem 'rails', '~> 7.1.0' +gem 'rails_semantic_logger' gem 'redis' gem 'sentry-rails' gem 'sentry-ruby' @@ -33,6 +34,7 @@ group :development, :test do end group :development do + gem 'amazing_print' gem 'annotate' gem 'better_errors' gem 'binding_of_caller' diff --git a/Gemfile.lock b/Gemfile.lock index cb921ed7..4af08731 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -89,6 +89,7 @@ GEM tzinfo (~> 2.0) addressable (2.8.7) public_suffix (>= 2.0.2, < 7.0) + amazing_print (1.8.1) annotate (3.2.0) activerecord (>= 3.2, < 8.0) rake (>= 10.4, < 14.0) @@ -279,6 +280,10 @@ GEM rails-html-sanitizer (1.6.2) loofah (~> 2.21) nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) + rails_semantic_logger (4.18.0) + rack + railties (>= 5.1) + semantic_logger (~> 4.16) railties (7.1.5.1) actionpack (= 7.1.5.1) activesupport (= 7.1.5.1) @@ -339,6 +344,8 @@ GEM rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) + semantic_logger (4.17.0) + concurrent-ruby (~> 1.0) sentry-rails (5.26.0) railties (>= 5.0) sentry-ruby (~> 5.26.0) @@ -411,6 +418,7 @@ PLATFORMS x86_64-linux DEPENDENCIES + amazing_print annotate better_errors binding_of_caller @@ -433,6 +441,7 @@ DEPENDENCIES puma rack-attack rails (~> 7.1.0) + rails_semantic_logger redis rubocop rubocop-rails diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 9085b9e0..f24969ef 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -21,12 +21,34 @@ def results # hand off enhanced query to builder query = QueryBuilder.new(@enhanced_query).query + # Create cache key for this query + # Sorting query hash to ensure consistent key generation + sorted_query = query.sort_by { |k, v| k.to_sym }.to_h + cache_key = Digest::MD5.hexdigest(sorted_query.to_s) + + Rails.logger.info("Cache key for this query: #{cache_key}") + Rails.logger.info("Query: #{query}") + Rails.logger.info("Sorted Query: #{sorted_query}") + # builder hands off to wrapper which returns raw results here - response = if Flipflop.enabled?(:gdt) - execute_geospatial_query(query) - else - TimdexBase::Client.query(TimdexSearch::BaseQuery, variables: query) - end + marshaled_response = if Flipflop.enabled?(:gdt) + Rails.cache.fetch("#{cache_key}/geo", expires_in: 12.hours) do + raw = execute_geospatial_query(query) + Marshal.dump({ + data: raw.data.to_h, + errors: raw.errors.details.to_h + }) + end + else + Rails.cache.fetch("#{cache_key}/use", expires_in: 12.hours) do + raw = TimdexBase::Client.query(TimdexSearch::BaseQuery, variables: query) + Marshal.dump({ + data: raw.data.to_h, + errors: raw.errors.details.to_h + }) + end + end + response = Marshal.load(marshaled_response) # Handle errors @errors = extract_errors(response) @@ -59,11 +81,16 @@ def execute_geospatial_query(query) end def extract_errors(response) - response&.errors&.details&.to_h&.dig('data') + response[:errors]['data'] if response.is_a?(Hash) && response.key?(:errors) && response[:errors].key?('data') end def extract_filters(response) - aggs = response&.data&.search&.to_h&.dig('aggregations') + # aggs = response&.data&.search&.to_h&.dig('aggregations') + # aggs = response['data']['search']['aggregations'] + # aggs = response.dig('data', 'search', 'aggregations') + return unless response.is_a?(Hash) && response.key?(:data) && response[:data].key?('search') + + aggs = response[:data]['search']['aggregations'] return if aggs.blank? aggs = reorder_filters(aggs, active_filters) unless active_filters.blank? @@ -78,7 +105,9 @@ def extract_filters(response) end def extract_results(response) - response&.data&.search&.to_h&.dig('records') + return unless response.is_a?(Hash) && response.key?(:data) && response[:data].key?('search') + + response[:data]['search']['records'] end def reorder_filters(aggs, active_filters) diff --git a/app/models/analyzer.rb b/app/models/analyzer.rb index 2e043df5..3361592a 100644 --- a/app/models/analyzer.rb +++ b/app/models/analyzer.rb @@ -15,7 +15,11 @@ def initialize(enhanced_query, response) private def hits(response) - response&.data&.search&.to_h&.dig('hits') + return 0 if response.nil? + return 0 unless response.is_a?(Hash) && response.key?(:data) && response[:data].key?('search') + return 0 unless response[:data]['search'].is_a?(Hash) && response[:data]['search'].key?('hits') + + response[:data]['search']['hits'] end def next_page(page, hits) diff --git a/config/environments/development.rb b/config/environments/development.rb index 97e037ce..ed8532dd 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -81,4 +81,16 @@ # Raise error when a before_action's only/except options reference missing actions config.action_controller.raise_on_missing_callback_actions = true + + config.rails_semantic_logger.semantic = true + config.rails_semantic_logger.started = false + config.rails_semantic_logger.processing = false + config.rails_semantic_logger.rendered = false + # config.rails_semantic_logger.format = :json + + # $stdout.sync = true + # config.rails_semantic_logger.add_file_appender = false + # config.semantic_logger.add_appender(io: $stdout, formatter: config.rails_semantic_logger.format) + + config.log_level = :info end diff --git a/config/environments/production.rb b/config/environments/production.rb index 58f07798..04c4e1e5 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -66,7 +66,14 @@ # Use a different cache store in production. # config.cache_store = :mem_cache_store - config.cache_store = :redis_cache_store, { url: ENV['REDIS_URL'], ssl_params: { verify_mode: OpenSSL::SSL::VERIFY_NONE } } + # User redis cache if REDIS_URL is set, otherwise fallback to memory store + if ENV['REDIS_URL'].blank? + puts "WARNING: REDIS_URL not set, falling back to memory_store for cache. This is not recommended for production environments." + config.cache_store = :memory_store, { size: 32.megabytes } + else + puts "Using redis cache store at #{ENV['REDIS_URL']}" + config.cache_store = :redis_cache_store, { url: ENV['REDIS_URL'], ssl_params: { verify_mode: OpenSSL::SSL::VERIFY_NONE } } + end # Use a real queuing backend for Active Job (and separate queues per environment). # config.active_job.queue_adapter = :resque