Skip to content

Commit

Permalink
Added a new middleware that adds json logs for exceptions. Plus silen…
Browse files Browse the repository at this point in the history
…ce the old logs if suppress_app_logs.

Closes shadabahmed#79
  • Loading branch information
Marc Grimme committed Jul 6, 2016
1 parent ab78ea0 commit 7b91d94
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 16 deletions.
30 changes: 27 additions & 3 deletions lib/logstasher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,21 @@
require 'logstasher/action_view/log_subscriber'
require 'logstasher/rails_ext/action_controller/base'
require 'logstasher/custom_fields'
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'
require 'active_support/ordered_options'

module LogStasher
class NullLogger < Logger
def initialize(*args)
end

def add(*args, &block)
end
end

extend self
STORE_KEY = :logstasher_data
REQUEST_CONTEXT_KEY = :logstasher_request_context
Expand Down Expand Up @@ -94,12 +103,12 @@ def setup_before(config)
LogStasher::ActionView::LogSubscriber.attach_to :action_view if config.view_enabled
end

def setup(config)
def setup(config, app)
# Path instrumentation class to insert our hook
if (! config.controller_monkey_patch && config.controller_monkey_patch != false) || config.controller_monkey_patch == true
require 'logstasher/rails_ext/action_controller/metal/instrumentation'
end
self.suppress_app_logs(config)
self.suppress_app_logs(config, app)
self.logger_path = config.logger_path || "#{Rails.root}/log/logstash_#{Rails.env}.log"
self.logger = config.logger || new_logger(self.logger_path)
self.logger.level = config.log_level || Logger::WARN
Expand All @@ -126,9 +135,10 @@ def called_as_console?
defined?(Rails::Console) && true || false
end

def suppress_app_logs(config)
def suppress_app_logs(config, app)
if configured_to_suppress_app_logs?(config)
require 'logstasher/rails_ext/rack/logger'
app.env_config["action_dispatch.logger"] = NullLogger.new()
LogStasher.remove_existing_log_subscriptions
end
end
Expand All @@ -138,6 +148,20 @@ 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
app.middleware.insert_after ::ActionDispatch::DebugExceptions, ::LogStasher::ActionDispatch::DebugExceptions, app
end
end

def custom_fields
Thread.current[:logstasher_custom_fields] ||= []
end

def custom_fields=(val)
Thread.current[:logstasher_custom_fields] = val
end

# Log an arbitrary message.
#
# Usually invoked by the level-based wrapper methods defined below.
Expand Down
2 changes: 1 addition & 1 deletion lib/logstasher/active_support/log_subscriber.rb
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ def location(event)
def extract_exception(payload)
if payload[:exception]
exception, message = payload[:exception]
status = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception)
status = ::ActionDispatch::ExceptionWrapper.status_code_for_exception(exception)
if LogStasher.backtrace
backtrace = $!.backtrace.join("\n")
else
Expand Down
45 changes: 45 additions & 0 deletions lib/logstasher/rails_ext/rack/debug_exceptions.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
require 'action_dispatch'
require 'active_support/all'

module LogStasher
module ActionDispatch

class DebugExceptions < ::ActionDispatch::DebugExceptions
include ::LogStasher::ActionDispatch

def initialize(app, routes_app = nil)
@app = app
@routes_app = routes_app
end

def call(env)
begin
status, header, body = @app.call(env)
if header['X-Cascade'] == 'pass'
raise ::ActionController::RoutingError, "No route matches [#{env['REQUEST_METHOD']}] #{env['PATH_INFO'].inspect}"
end
rescue Exception => exception
log_error(env, ::ActionDispatch::ExceptionWrapper.new(env, exception))
end
[status, header, body]
end

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

private

def log_error(env, wrapper)
LogStasher.logger << LogStasher.build_logstash_event(build_exception_hash(wrapper), ["exception"]).to_json + "\n"
end
end
end
end
4 changes: 2 additions & 2 deletions lib/logstasher/railtie.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ class Railtie < Rails::Railtie
LogStasher.setup_before(app.config.logstasher) if app.config.logstasher.enabled
end

initializer :logstasher do
initializer :logstasher do |app|
config.after_initialize do
LogStasher.setup(config.logstasher) if config.logstasher.enabled
LogStasher.setup(config.logstasher, app) if config.logstasher.enabled
end
end
end
Expand Down
46 changes: 46 additions & 0 deletions spec/lib/logstasher/rails_ext/rack/debug_exceptions_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
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

describe ::LogStasher::ActionDispatch::DebugExceptions do
include_examples 'MyApp'

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

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)
subject.call(environment)
end
end
end
end
34 changes: 24 additions & 10 deletions spec/lib/logstasher_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,9 @@
after { LogStasher.source = @previous_source } # Need to restore old source for specs
it 'defines a method in ActionController::Base' do
expect(LogStasher).to receive(:require).with('logstasher/rails_ext/action_controller/metal/instrumentation')
expect(LogStasher).to receive(:suppress_app_logs).with(config.logstasher)
expect(LogStasher).to receive(:suppress_app_logs).with(config.logstasher, app)
expect(logger).to receive(:level=).with('warn')
LogStasher.setup(config.logstasher)
LogStasher.setup(config.logstasher, app)
expect(LogStasher.source).to eq (logstasher_source || 'unknown')
expect(LogStasher).to be_enabled
expect(LogStasher::CustomFields.custom_fields).to be_empty
Expand Down Expand Up @@ -176,27 +176,41 @@
end

describe '.suppress_app_logs' do
let(:logstasher_config){ double(:logstasher => double(:suppress_app_log => true))}
let(:app){ double(:config => logstasher_config)}
it 'removes existing subscription if enabled' do
expect(LogStasher).to receive(:require).with('logstasher/rails_ext/rack/logger')
expect(LogStasher).to receive(:remove_existing_log_subscriptions)
LogStasher.suppress_app_logs(app.config.logstasher)
let(:app){ double(config: logstasher_config, env_config: {})}

context 'when enabled' do
let(:logstasher_config){ double(logstasher: double(:suppress_app_log => true))}
it 'removes existing subscription if enabled' do
expect(LogStasher).to receive(:require).with('logstasher/rails_ext/rack/logger')
expect(LogStasher).to receive(:remove_existing_log_subscriptions)
LogStasher.suppress_app_logs(app.config.logstasher, app)
end

it 'changes "action_dispatch.logger" to NullLogger' do
LogStasher.suppress_app_logs(app.config.logstasher, app)
expect(app.env_config).to include('action_dispatch.logger' => an_instance_of(::LogStasher::NullLogger))
end
end

context 'when disabled' do
let(:logstasher_config){ double(:logstasher => double(:suppress_app_log => false)) }

it 'does not remove existing subscription' do
expect(LogStasher).to_not receive(:remove_existing_log_subscriptions)
LogStasher.suppress_app_logs(app.config.logstasher)
LogStasher.suppress_app_logs(app.config.logstasher, app)
end

it 'leaves "action_dispatch.logger" as before' do
LogStasher.suppress_app_logs(app.config.logstasher, app)
expect(app.env_config).to match({})
end

describe "backward compatibility" do
context 'with spelling "supress_app_log"' do
let(:logstasher_config){ double(:logstasher => double(:suppress_app_log => nil, :supress_app_log => false)) }
it 'does not remove existing subscription' do
expect(LogStasher).to_not receive(:remove_existing_log_subscriptions)
LogStasher.suppress_app_logs(app.config.logstasher)
LogStasher.suppress_app_logs(app.config.logstasher, app)
end
end
end
Expand Down

0 comments on commit 7b91d94

Please sign in to comment.