reidmorrison / rails_semantic_logger

Rails Semantic Logger replaces the Rails default logger with Semantic Logger
https://logger.rocketjob.io/rails
Apache License 2.0
325 stars 116 forks source link

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

Closed ixti closed 8 months ago

ixti commented 8 months ago

Environment

# file: config/application.rb
config.rails_semantic_logger.format = SemanticLogger::Formatters::Json.new

Expected Behavior

Actual Behavior

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
ixti commented 8 months ago

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.

ixti commented 8 months ago

See: https://github.com/sidekiq/sidekiq/pull/6198