reidmorrison/rails_semantic_logger

JSON formatter fails with stack overflow on Rails 7.1 + Sidekiq 7.2

ixti opened this issue · 2 comments

Environment

  • Ruby Version: 3.3.0
  • Rails Version: 7.1.3
  • Semantic Logger Version: 4.15.0
  • Rails Semantic Logger Version: 4.14.0
# file: config/application.rb
config.rails_semantic_logger.format = SemanticLogger::Formatters::Json.new

Expected Behavior

  • Should work just fine

Actual Behavior

  • It does, but...
  • Upon boot, appender catches stack-overflow exception

Root Cause

Sidekiq 7.2 logs debug info about the process with payload:

     average_scheduled_poll_interval: 5,
                   backtrace_cleaner: #<Proc:0x00007f6994840a08 /home/ixti/.gem/ruby/3.3.0/gems/sidekiq-7.2.1/lib/sidekiq/rails.rb:44 (lambda)>,
                         concurrency: 5,
                       dead_max_jobs: 10000
... more stuff ...
                            reloader: #<Sidekiq::Rails::Reloader @app=Demo::Application>,
                             require: ".",
                                 tag: "demo",
                             timeout: 25

The problematic here is :reloader. Attempt to call as_json on it causes stack overflow.

Workaround

We've monkey-patched Sidekiq::Rails::Reloader in our codebase to overcome this:

# file: config/initializers/sidekiq.rb

module Sidekiq
  class Rails < ::Rails::Engine
    class Reloader
      def as_json(...)
        { app: @app.class.name }.as_json(...)
      end
    end
  end
end

Correction. Sidekiq::Rails::Reloader is not the direct problem here. It simply holds instance variable: @app = Rails.application. Calling Rails.application.as_json fails with stack level to deep.

Rails extends Object with as_json:

class Object
  def as_json(options = nil) # :nodoc:
    if respond_to?(:to_hash)
      to_hash.as_json(options)
    else
      instance_values.as_json(options)
    end
  end
end

https://github.com/rails/rails/blob/9e01d93547e2082e2e88472748baa0f9ea63c181/activesupport/lib/active_support/core_ext/object/json.rb#L58-L66

Thus, if class does not implement to_hash or as_json, it will:

class Example
  def initialize
    @answer = 42
  end
end

Example.new.as_json # => { "answer" => 42 }

TLDR; Sorry for the noise. This has nothing to do with SemanticLogger - will open a PR to Sidekiq.