Skip to content

Instantly share code, notes, and snippets.

@entrity
Last active December 22, 2015 11:49
Show Gist options
  • Save entrity/6468511 to your computer and use it in GitHub Desktop.
Save entrity/6468511 to your computer and use it in GitHub Desktop.
AuditLogger & Helper functions for Rails applications
=begin
Usage:
require 'audit_logger'
class MyClass
include AuditLogger::Helpers
audit_logger_name 'my-class.log'
# It works in instance methods
def foo
# Standard logging
audit # => "#{self.class}[id:#{id}]##{method}:#{line}"
# Log with a message
audit 'this message' # => "#{self.class}[id:#{id}]##{method}:#{line}\nthis message"
# If you want to log to a different logfile than the current class default:
audit log:'mylogger.log'
# There are several options you can pass in
audit log:'mylogger.log', body:'this message', level:'error'
end
# It works in class or static methods
def self.foo
audit # => "#{self}::#{method}:#{line}"
end
end
=end
end
class AuditLogger < Logger
cattr_accessor :audit_loggers
self.audit_loggers = {}
at_exit { AuditLogger.close_all }
# AuditLogger.[] is the preferred creation method
def initialize(name, *args)
# make sure to track any creations
super
self.class.audit_loggers[name] = self
end
def audit(*args)
end
def puts(*args); info args.join "\n\t"; end
def tabs(*args); info args.join "\t"; end
def self.file_name(name)
Rails.root.join('log', "#{name.to_s.downcase}.log").to_s
end
def self.[](name)
name = file_name(name)
audit_loggers[name] || begin
# log rotation: keep 25 old log files with 20MB
AuditLogger.new(name, 25, (2097152*10))
end
end
def self.close_all
audit_loggers.values.each do |name, logger|
if logger
logger.close rescue nil # Ignore
end
end
audit_loggers.clear
end
def format_message(severity, timestamp, progname, msg)
"#{timestamp.to_formatted_s(:db)} #{severity} #{msg}\n"
end
module Helpers
@audit_logger_name = 'default_audit'
private
def audit(*args)
proc =
if self.respond_to?(:id)
lambda{|line, method| "#{self.class}[id:#{id}]##{method}:#{line}" }
else
lambda{|line, method| "#{self.class}##{method}:#{line}" }
end
self.class._audit proc, args
end
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
def audit(*args)
_audit lambda{|line, method| "#{self}::#{method}:#{line}"}, args
end
def audit_logger_name name
@audit_logger_name = name
end
def _audit header_proc, args
# build header line
line, method = _stack_data 2
header = header_proc.call(line,method)
# set options
options = args.first.is_a?(Hash) ? args.first : {body:args}
log = options[:log] ? AuditLogger[options[:log]] : AuditLogger[@audit_logger_name]
# build logger output
output =
if options[:body]
body = Array(options[:body]).join options.fetch(:delim, " ")
[header,body].join options.fetch(:header_delim, "\n\t")
else
header
end
# write to log, using appropriate log level
options[:level] ||= :info
log.send options[:level], output
end
# Returns method and line number from backtrace
def _stack_data caller_steps=0
match_data = caller[caller_steps].match /:(\d+):in `(.+)'/
return [match_data[1], match_data[2]]
end
end
end
end

Usage:

    require 'audit_logger'

    class MyClass
      include AuditLogger::Helpers
      audit_logger_name 'my-class.log'

      # It works in instance methods
      def foo
        # Standard logging
        audit # => "#{self.class}[id:#{id}]##{method}:#{line}"
        
        # Log with a message
        audit 'this message' # => "#{self.class}[id:#{id}]##{method}:#{line}\nthis message"
        
        # If you want to log to a different logfile than the current class default:
        audit log:'mylogger.log'

        # There are several options you can pass in
        audit log:'mylogger.log', body:'this message', level:'error'
      end

      # It works in class or static methods
      def self.foo
        audit # => "#{self}::#{method}:#{line}"
      end

    end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment