|
class ApplicationController < ActionController::Base |
|
|
|
rescue_from Exception, :with => :handle_exception |
|
rescue_from ActiveRecord::RecordNotFound, :with => :handle_not_found |
|
|
|
before_filter :setup_logging |
|
|
|
|
|
# [...] |
|
|
|
|
|
private |
|
|
|
def redirect_to(*a, &b) |
|
#Set previous-request-id to track redirects |
|
flash['previous-request-id'] = request.uuid |
|
super(*a, &b) |
|
end |
|
|
|
|
|
def emit_event(atts) |
|
atts['data'] ||= {} |
|
atts['data']['http'] ||= {} |
|
atts['data']['http']['request-id'] ||= request.uuid |
|
$ZMQ_LOGGER.emit(atts) |
|
rescue Exception => e |
|
logger.error "[LOGGING ERROR] #{e.class}: #{e.message}\n#{e.backtrace.map{|l| " #{l}"}.join("\n")}" |
|
end |
|
|
|
def log_message(atts) |
|
atts['data'] ||= {} |
|
atts['data']['http'] ||= {} |
|
atts['data']['http']['request-id'] ||= request.uuid |
|
$ZMQ_LOGGER.log(atts) |
|
rescue Exception => e |
|
logger.error "[LOGGING ERROR] #{e.class}: #{e.message}\n#{e.backtrace.map{|l| " #{l}"}.join("\n")}" |
|
end |
|
|
|
|
|
def memory_usage#in kB |
|
`ps -o rss= -p #{Process.pid}`.strip.to_i |
|
rescue |
|
nil |
|
end |
|
|
|
|
|
def handle_not_found(exception) |
|
log_exception(exception) |
|
|
|
if true#Rails.env.production? |
|
respond_to do |format| |
|
format.html{ render file: Rails.root.join('public', '404.html'), layout: false, status: 404 } |
|
format.xml{ render xml: error_xml('The requested resource could not be found'), status: 404 } |
|
end |
|
else |
|
raise exception |
|
end |
|
rescue Exception => e |
|
logger.error "[EXCEPTION LOGGING EXCEPTION] #{e.class}: #{e.message}\n#{e.backtrace.map{|l| " #{l}"}.join("\n")}" |
|
render :text => "Inxception :(" |
|
end |
|
|
|
|
|
def handle_exception(exception) |
|
if Rails.env.development? |
|
log_exception(exception) |
|
raise exception |
|
else |
|
begin |
|
log_exception(exception) |
|
respond_to do |format| |
|
format.html{ render file: Rails.root.join('public', '500.html'), layout: false, status: 500 } |
|
end |
|
rescue Exception => e |
|
logger.error "[EXCEPTION LOGGING EXCEPTION] #{e.class}: #{e.message}\n#{e.backtrace.map{|l| " #{l}"}.join("\n")}" |
|
render :text => "Inxception :(", status: 500 |
|
end |
|
end |
|
end |
|
|
|
|
|
def log_exception(exception) |
|
atts = { |
|
'type' => 'exception', |
|
'message' => 'Uncaught exception', |
|
'description' => 'An unanticipated exception occured', |
|
'data' => { |
|
'exception' => { |
|
'type' => exception.class.name, |
|
'message' => exception.message, |
|
'backtrace' => exception.backtrace |
|
}, |
|
'http' => request_data_for_logging |
|
} |
|
} |
|
|
|
log_message(atts) |
|
end |
|
|
|
|
|
def setup_logging |
|
@memory_before = memory_usage |
|
start = Time.now |
|
instr, mw = nil, nil |
|
ActiveSupport::Notifications.instrument('zmq-logger.call-me', ->(atts){ |
|
instr = atts |
|
log_request(atts, mw) if mw |
|
}) |
|
request.env['zmq-logger.get-data'].call(->(data){ |
|
mw = data |
|
log_request(instr, data) if instr |
|
}) |
|
end |
|
|
|
|
|
def log_request(instr, mw) |
|
atts = { |
|
'type' => 'request', |
|
'data' => { |
|
'time' => mw['rack_stop']-mw['rack_start'], |
|
'timing' => { |
|
'rails_start' => instr['start'], |
|
'rails_stop' => instr['stop'], |
|
'rails_time' => instr['stop']-instr['start'], |
|
'rack_start' => mw['rack_start'], |
|
'rack_stop' => mw['rack_stop'], |
|
'rack_time' => mw['rack_stop']-mw['rack_start'], |
|
'rails_db_time' => instr['db_runtime'], |
|
'rails_view_time' => instr['view_runtime'], |
|
'custom' => timing_data_for_logging |
|
}, |
|
'memory' => [@memory_before, memory_usage], #[start of request, end of request] |
|
'queries' => instr['queries'], |
|
'renders' => instr['renders'], |
|
'user' => { |
|
'id' => current_user && current_user.id, |
|
'username' => current_user && current_user.username |
|
}, |
|
'http' => request_data_for_logging.merge( |
|
'response' => { |
|
'status' => mw['status'], |
|
'headers' => mw['headers'], |
|
'body' => false && mw['body'] # You may not want to log every response body |
|
} |
|
) |
|
} |
|
} |
|
|
|
if mw['request_start'] |
|
atts['data']['timing'].merge!( |
|
'request_start' => mw['request_start'], |
|
'request_time' => atts['data']['timing']['rack_stop'] - mw['request_start'], |
|
'queue_time' => atts['data']['timing']['rack_start'] - mw['request_start'] |
|
) |
|
end |
|
|
|
log_message(atts) |
|
end |
|
|
|
|
|
def request_data_for_logging |
|
atts = { |
|
'request-id' => request.uuid, |
|
'headers' => request_headers_for_logging, |
|
'method' => request.method, |
|
'params' => request.filtered_parameters, |
|
'session' => session |
|
} |
|
|
|
#If this was the result of a redirect, see if previous request left its ID so we can tie them together |
|
atts['referer-request-id'] = flash['previous-request-id'] if flash['previous-request-id'] && flash['previous-request-id'] != atts['request-id'] |
|
|
|
atts |
|
end |
|
|
|
|
|
#Scrub headers for logging. headers == rack env, which has lots of crap in it from middleware |
|
def request_headers_for_logging |
|
headers = request.headers.reject do |key, value| |
|
(!key.is_a?(String) && !key.is_a?(Symbol)) || !value.is_a?(String) || |
|
key.to_s =~ /\A(rack|action_controller|action_dispatch|zmq-logger)/ || |
|
key == 'HTTP_COOKIE' |
|
end |
|
#headers['HTTP_X_API_KEY'] = '[FILTERED]' if headers['HTTP_X_API_KEY'] |
|
headers |
|
end |
|
|
|
|
|
def timing_data_for_logging |
|
@timing_data_for_logging ||= Hash.new{|h,k| h[k] = [] } |
|
end |
|
|
|
def time(name, &block) |
|
t = Time.now |
|
r = yield |
|
timing_data_for_logging[name] << Time.now-t |
|
r |
|
end |
|
helper_method :time |
|
|
|
|
|
end |