diff --git a/lib/logstasher.rb b/lib/logstasher.rb index 866c943..dc5a9ac 100644 --- a/lib/logstasher.rb +++ b/lib/logstasher.rb @@ -4,6 +4,7 @@ require 'logstasher/active_record/log_subscriber' require 'logstasher/action_view/log_subscriber' require 'logstasher/rails_ext/action_controller/base' +require 'logstasher/rails_ext/rack/debug_exceptions' require 'request_store' require 'active_support/core_ext/module/attribute_accessors' require 'active_support/core_ext/string/inflections' @@ -137,6 +138,16 @@ def configured_to_suppress_app_logs?(config) !!(config.suppress_app_log.nil? ? config.supress_app_log : config.suppress_app_log) end + def modify_middleware(app) + if enabled + if configured_to_suppress_app_logs? app.config.logstasher + app.middleware.swap ::ActionDispatch::DebugExceptions, ::LogStasher::ActionDispatch::DebugExceptions + else + app.middleware.swap ::ActionDispatch::DebugExceptions, ::LogStasher::ActionDispatch::TwoWayDebugExceptions + end + end + end + def custom_fields Thread.current[:logstasher_custom_fields] ||= [] end diff --git a/lib/logstasher/rails_ext/rack/debug_exceptions.rb b/lib/logstasher/rails_ext/rack/debug_exceptions.rb new file mode 100644 index 0000000..23de0d4 --- /dev/null +++ b/lib/logstasher/rails_ext/rack/debug_exceptions.rb @@ -0,0 +1,38 @@ +require 'action_dispatch' +require 'active_support/all' + +module LogStasher + module ActionDispatch + def build_exception_hash(wrapper) + exception = wrapper.exception + trace = wrapper.application_trace + trace = wrapper.framework_trace if trace.empty? + + { error: + ({ exception: exception.class.name, message: exception.message, trace: trace}. + merge!( exception.respond_to?(:annotated_source_code) && { annotated_source_code: exception.annoted_source_code } || {} )) + } + end + + class DebugExceptions < ::ActionDispatch::DebugExceptions + include ::LogStasher::ActionDispatch + + private + def log_error(env, wrapper) + LogStasher.logger << LogStasher.build_logstash_event(build_exception_hash(wrapper), ["exception"]).to_json + "\n" + end + + end + + class TwoWayDebugExceptions < ::ActionDispatch::DebugExceptions + include ::LogStasher::ActionDispatch + + private + def log_error(env, wrapper) + LogStasher.logger << LogStasher.build_logstash_event(build_exception_hash(wrapper), ["exception"]).to_json + "\n" + + super(env, wrapper) + end + end + end +end diff --git a/lib/logstasher/railtie.rb b/lib/logstasher/railtie.rb index 79e5363..dbde2e5 100644 --- a/lib/logstasher/railtie.rb +++ b/lib/logstasher/railtie.rb @@ -2,6 +2,8 @@ require 'action_view/log_subscriber' require 'action_controller/log_subscriber' require 'socket' +require 'action_dispatch' +require 'active_support/all' module LogStasher class Railtie < Rails::Railtie @@ -21,7 +23,7 @@ class Railtie < Rails::Railtie # Load and ERB templating of YAML files LOGSTASHER = File.exists?(config_file) ? YAML.load(ERB.new(File.read(config_file)).result).symbolize_keys : nil - initializer :logstasher, :before => :load_config_initializers do |app| + initializer :logstasher, before: :load_config_initializers do |app| if LOGSTASHER.present? # process common configs LogStasher.process_config(app.config.logstasher, LOGSTASHER) @@ -38,6 +40,10 @@ class Railtie < Rails::Railtie LogStasher.setup(config.logstasher) if config.logstasher.enabled end end + + initializer 'logstasher.insert_middleware', after: :load_config_initializers do |app| + LogStasher.modify_middleware app + end end def process_config(config, yml_config) diff --git a/spec/lib/logstasher/rails_ext/rack/debug_exceptions_spec.rb b/spec/lib/logstasher/rails_ext/rack/debug_exceptions_spec.rb new file mode 100644 index 0000000..2f6ec8f --- /dev/null +++ b/spec/lib/logstasher/rails_ext/rack/debug_exceptions_spec.rb @@ -0,0 +1,72 @@ +require 'spec_helper' + +shared_examples 'MyApp' do + before do + class MyApp + def initialize() + end + def call(*args) + raise Exception.new("My Exception") + end + end + end + + let(:app) { MyApp.new } + let(:environment) { { 'action_dispatch.show_exceptions' => true } } + let(:logger) { double } + subject{ described_class.new(app) } + + before(:each) do + allow(LogStasher).to receive(:logger).and_return(logger) + allow(LogStasher.logger).to receive(:'<<').and_return(true) + end +end + +shared_examples 'build_exception_hash' do + describe '#build_exception_hash' do + let (:wrapper) { double(exception: Exception.new("My Exception"), application_trace: [ "line5" ]) } + it do + hash = subject.build_exception_hash(wrapper) + + expect(hash).to match({:error=>{:exception=>"Exception", :message=>"My Exception", :trace=>["line5"]}}) + end + end +end + +describe ::LogStasher::ActionDispatch::DebugExceptions do + include_examples 'MyApp' + + describe 'calls LogStasher.logger with json format exception' do + describe '#log_error' do + it do + expect(LogStasher).to receive(:build_logstash_event) + expect(LogStasher.logger).to receive(:'<<').and_return(true) + expect{ subject.call(environment) }.to raise_error(Exception, "My Exception") + end + end + end + + include_examples 'build_exception_hash' +end + +describe ::LogStasher::ActionDispatch::TwoWayDebugExceptions do + include_examples 'MyApp' + + let(:logger_2nd) { double } + before(:each) do + allow(subject).to receive(:logger).and_return(logger_2nd) + end + + describe 'calls LogStasher.logger with json format exception' do + describe '#log_error' do + it do + expect(LogStasher).to receive(:build_logstash_event) + expect(LogStasher.logger).to receive(:'<<').and_return(true) + expect(logger_2nd).to receive(:fatal).and_return(true) + expect{ subject.call(environment) }.to raise_error(Exception, "My Exception") + end + end + end + + include_examples 'build_exception_hash' +end