Last active
December 26, 2016 13:01
-
-
Save johncox00/8eff8f9eb94cc47415217f4c038c0489 to your computer and use it in GitHub Desktop.
Forensic logging...This will log just about EVERYTHING in any class in which is included. In a blocking/synchronous framework it will negatively impact performance. Do not use in production unless you have a big problem to diagnose and can't figure out a better way. Even then, don't leave it on for long. There is an async option that uses Resque…
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Application < Rails::Application | |
#... | |
config.after_initialize do | |
method_logger_config = YAML::load(ERB.new(File.read(File.join(Rails.root, 'config/constants/method_logger.yaml'))).result)[Rails.env] | |
if Rails.env != "test" | |
Rails.application.eager_load! | |
if Rails.logger.level <= "Logger::Severity::#{method_logger_config["controller_logging_threshold"].upcase}".constantize | |
ApplicationController.subclasses.each{|c| c.send(:include, MethodLogger) unless method_logger_config["excluded_controllers"].include?(c.to_s)} | |
end | |
if Rails.logger.level <= "Logger::Severity::#{method_logger_config["model_logging_threshold"].upcase}".constantize | |
ActiveRecord::Base.subclasses.each{|c| c.send(:include, MethodLogger) unless method_logger_config["excluded_models"].include?(c.to_s)} | |
end | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class LoggerJob | |
@queue = :log | |
@@method_logger = YAML::load(ERB.new(File.read(File.join(Rails.root, 'config/constants/method_logger.yaml'))).result)[Rails.env] | |
@log_level = @@method_logger["log_level"].to_sym | |
def self.perform(message) | |
Rails.logger.send(@log_level, message) | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module MethodLogger | |
@@method_logger = YAML::load(ERB.new(File.read(File.join(Rails.root, 'config/constants/method_logger.yaml'))).result)[Rails.env] | |
@@log_level = @@method_logger["log_level"].to_sym || :debug | |
@@async = Boolean(@@method_logger["async"]) || false | |
@@record_method = @@async ? :record_async : :record | |
@@enabled = Boolean(@@method_logger["enabled"]) || false | |
# here we exclude methods that will cause circular reference within this module | |
# or that the logging thereof isn't necessarily useful | |
@@excluded_class_methods = [:record, :record_async, :format_message, :hidden_actions] | |
# The hash below defines methods that we know will have passwords in the args. | |
# The key is the method name. The value is the position within the args where the password is passed. | |
@@filtered_methods = { | |
secure_digest: 2 | |
} | |
@@hostname = Socket.gethostname | |
def record(timestamp, baseclass, methodname, args, inbound) | |
string = format_message(timestamp, baseclass, methodname, args, inbound) | |
Rails.logger.send(@@log_level, string) | |
end | |
def record_async(timestamp, baseclass, methodname, args, inbound) | |
string = format_message(timestamp, baseclass, methodname, args, inbound) | |
Resque.enqueue(LoggerJob, string) | |
end | |
def format_message(timestamp, baseclass, methodname, args, inbound) | |
"#{@@hostname} #{timestamp.to_formatted_s(:db)} #{inbound ? '<-' : '->'} #{baseclass}##{methodname}(#{args.inspect})" | |
end | |
def self.included(base) | |
if @@enabled | |
puts "\n\n***** Method Logging: #{base} *****" | |
# set up the instance methods that we'll override | |
methods = base.instance_methods(false) + base.private_instance_methods(false) | |
base.class_eval do | |
# override the instance methods | |
methods.each do |method_name| | |
# get the original method object for use later | |
original_method = instance_method(method_name) | |
puts "-> Instance method: #{base}##{method_name}" | |
define_method(method_name) do |*args, &block| | |
timestamp = Time.now | |
# create a new array of args that we can manipulate for logging | |
new_args = args.dup | |
# we want to make sure we don't log passwords, so we look for them as a parameter | |
if original_method.parameters.index([:req, :password]) | |
# find where the password param would be in the args list | |
pos = original_method.parameters.index([:req, :password]) | |
# find the password arg and filter it out | |
new_args[pos] = "FILTERED" | |
end | |
# find the password arg and filter it out for specific methods | |
new_args[@@filtered_methods[method_name]] = "FILTERED" if @@filtered_methods.keys.include?(method_name) | |
# record the message | |
self.send(@@record_method, timestamp, base, method_name, new_args, true) | |
# get the return value by calling the original method | |
return_value = original_method.bind(self).call(*args, &block) | |
self.send(@@record_method, timestamp, base, method_name, return_value, false) | |
# return the return from the original method | |
return_value | |
end | |
end | |
metaclass = class << self | |
self | |
end | |
metaclass.instance_eval do | |
define_method(:record_async) do |timestamp, baseclass, methodname, args, inbound| | |
string = format_message(timestamp, baseclass, methodname, args, inbound) | |
Resque.enqueue(LoggerJob, string) | |
end | |
define_method(:record) do |timestamp, baseclass, methodname, args, inbound| | |
string = format_message(timestamp, baseclass, methodname, args, inbound) | |
Rails.logger.send(@@log_level, string) | |
end | |
define_method(:format_message) do |timestamp, baseclass, methodname, args, inbound| | |
"#{@@hostname} #{timestamp.to_formatted_s(:db)} #{inbound ? '<-' : '->'} #{baseclass}##{methodname}(#{args.inspect})" | |
end | |
(instance_methods - @@excluded_class_methods - ActiveRecord::Base.methods ).each do |method_name| | |
puts "-> Class method: #{base}##{method_name}" | |
original_method = base.method(method_name) | |
define_method(method_name) do |*args, &block| | |
timestamp = Time.now | |
new_args = args.dup | |
if original_method.parameters.index([:req, :password]) | |
pos = original_method.parameters.index([:req, :password]) | |
new_args[pos] = "FILTERED" | |
end | |
new_args[@@filtered_methods[method_name]] = "FILTERED" if @@filtered_methods.keys.include?(method_name) | |
base.send(@@record_method, timestamp, base, method_name, new_args, true) | |
return_value = original_method.call(*args, &block) | |
base.send(@@record_method, timestamp, base, method_name, return_value, false) | |
return_value | |
end | |
end | |
end | |
end | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Logger levels: | |
# debug: 0 | |
# info: 1 | |
# warn: 2 | |
# error: 3 | |
# fatal: 4 | |
# unknown: 5 | |
development: &defaults | |
enabled: false # This is an overall switch to turn method logging on or off. | |
controller_logging_threshold: debug # This determines at what application logging level we will inject our logging logic into the controllers. | |
model_logging_threshold: debug # This determines at what application logging level we will inject our logging logic into the models. | |
log_level: info # Sets the Rails.logger method that will be called with logging messages. | |
async: false # Determines if we send log messages to file or to Resque/Kafka. | |
excluded_models: # List models here that do not need granular logging. | |
- User | |
excluded_v5_controllers: # List controllers here that do not need granular logging. | |
- AddressScrubController | |
test: | |
<<: *defaults | |
qa: | |
<<: *defaults | |
enabled: false | |
controller_logging_threshold: info | |
model_logging_threshold: info | |
async: false | |
qa_grn: | |
<<: *defaults | |
controller_logging_threshold: info | |
model_logging_threshold: info | |
async: false | |
dev: | |
<<: *defaults | |
beta: | |
<<: *defaults | |
async: false | |
production: | |
<<: *defaults | |
enabled: false | |
controller_logging_threshold: info | |
model_logging_threshold: info | |
log_level: info | |
async: false |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment