require 'active_support/buffered_logger' require 'active_support/core_ext/logger' require 'fileutils' module LogjamAgent class BufferedLogger < ActiveSupport::BufferedLogger attr_accessor :formatter def initialize(*args) super(*args) # stupid bug in the buffered logger code @log.write "\n" @formatter = lambda{|_, _, _, message| message} end def request Thread.current[:logjam_request] end def request=(request) Thread.current[:logjam_request] = request end def start_request(app, env, initial_fields={}) self.request = Request.new(app, env, self, initial_fields) end def finish_request(additional_fields={}) if request = self.request request.fields.merge!(additional_fields) self.request = nil request.forward end end def add(severity, message = nil, progname = nil, &block) return if @level > severity request = self.request if message.is_a?(Exception) request.add_exception(message.class.to_s) if request message = format_exception(message) else message = (message || (block && block.call) || '').to_s if request && severity >= Logger::ERROR && (ex = detect_logged_exception(message)) request.add_exception(ex) end end time = Time.now buffer << formatter.call(severity, time, progname, message) << "\n" auto_flush request.add_line(severity, time, message) if request message end def logdev=(log_device) raise "cannot connect logger to new log device" unless log_device.respond_to?(:write) @log = log_device end @@exception_classes = [] def self.auto_detect_exception(exception_class) # but ignore Exception classes created with Class.new (timeout.rb, my old friend) if (class_name = exception_class.to_s) =~ /^[\w:]+$/ @@exception_classes << class_name end end @@exception_matcher = nil def self.reset_exception_matcher @@exception_matcher = Regexp.new(@@exception_classes.map{|e| Regexp.escape(e)}.join("|")) end def self.auto_detect_logged_exceptions determine_loaded_exception_classes Exception.class_eval <<-"EOS" def self.inherited(subclass) logger_class = ::LogjamAgent::BufferedLogger logger_class.auto_detect_exception(subclass) logger_class.reset_exception_matcher end EOS end private def detect_logged_exception(message) (matcher = @@exception_matcher) && message[matcher] end def self.determine_loaded_exception_classes ObjectSpace.each_object(Class) do |klass| auto_detect_exception(klass) if klass < Exception end reset_exception_matcher end def format_exception(exception) msg = "#{exception.class} : #{exception.message}" if backtrace = exception.backtrace backtrace = Rails.backtrace_cleaner.clean(backtrace, :all) if defined?(Rails) msg << "\n #{backtrace.join("\n ")}" else msg end end end end