lib/syslogger.rb in syslogger-1.6.5 vs lib/syslogger.rb in syslogger-1.6.6

- old
+ new

@@ -1,35 +1,43 @@ +require 'forwardable' require 'syslog' require 'logger' -require 'thread' class Syslogger + extend Forwardable MUTEX = Mutex.new - attr_reader :level, :ident, :options, :facility, :max_octets - attr_accessor :formatter + attr_reader :level, :options, :facility + attr_accessor :ident, :formatter, :max_octets MAPPING = { Logger::DEBUG => Syslog::LOG_DEBUG, Logger::INFO => Syslog::LOG_INFO, Logger::WARN => Syslog::LOG_WARNING, Logger::ERROR => Syslog::LOG_ERR, Logger::FATAL => Syslog::LOG_CRIT, Logger::UNKNOWN => Syslog::LOG_ALERT }.freeze - # + LEVELS = %w[debug info warn error fatal unknown].freeze + # Initializes default options for the logger + # # <tt>ident</tt>:: the name of your program [default=$0]. + # # <tt>options</tt>:: syslog options [default=<tt>Syslog::LOG_PID | Syslog::LOG_CONS</tt>]. + # # Correct values are: # LOG_CONS : writes the message on the console if an error occurs when sending the message; # LOG_NDELAY : no delay before sending the message; # LOG_PERROR : messages will also be written on STDERR; # LOG_PID : adds the process number to the message (just after the program name) - # <tt>facility</tt>:: the syslog facility [default=nil] Correct values include: + # + # <tt>facility</tt>:: the syslog facility [default=nil] + # + # Correct values include: # Syslog::LOG_DAEMON # Syslog::LOG_USER # Syslog::LOG_SYSLOG # Syslog::LOG_LOCAL2 # Syslog::LOG_NEWS @@ -40,50 +48,42 @@ # logger.level = Logger::INFO # use Logger levels # logger.warn "warning message" # logger.debug "debug message" # logger.info "my_subapp" { "Some lazily computed message" } # - def initialize(ident = $0, options = Syslog::LOG_PID | Syslog::LOG_CONS, facility = nil) + def initialize(ident = $PROGRAM_NAME, options = Syslog::LOG_PID | Syslog::LOG_CONS, facility = nil) @ident = ident @options = options || (Syslog::LOG_PID | Syslog::LOG_CONS) @facility = facility @level = Logger::INFO - @formatter = proc do |_, _, _, msg| - msg - end + @formatter = SimpleFormatter.new end - %w{debug info warn error fatal unknown}.each do |logger_method| + LEVELS.each do |logger_method| # Accepting *args as message could be nil. # Default params not supported in ruby 1.8.7 define_method logger_method.to_sym do |*args, &block| severity = Logger.const_get(logger_method.upcase) - return true if @level > severity + return true if level > severity + add(severity, nil, args.first, &block) end - unless logger_method == 'unknown' - define_method "#{logger_method}?".to_sym do - @level <= Logger.const_get(logger_method.upcase) - end + next if logger_method == 'unknown'.freeze + + define_method "#{logger_method}?".to_sym do + level <= Logger.const_get(logger_method.upcase) end end - # Log a message at the Logger::INFO level. Useful for use with Rack::CommonLogger + # Log a message at the Logger::INFO level. def write(msg) add(Logger::INFO, msg) end + alias << write + alias puts write - # Logs a message at the Logger::INFO level. - def <<(msg) - add(Logger::INFO, msg) - end - - def puts(msg) - add(Logger::INFO, msg) - end - # Low level method to add a message. # +severity+:: the level of the message. One of Logger::DEBUG, Logger::INFO, Logger::WARN, Logger::ERROR, Logger::FATAL, Logger::UNKNOWN # +message+:: the message string. # If nil, the method will call the block and use the result as the message string. # If both are nil or no block is given, it will use the progname as per the behaviour of both the standard Ruby logger, and the Rails BufferedLogger. @@ -91,60 +91,31 @@ def add(severity, message = nil, progname = nil, &block) if message.nil? && block.nil? && !progname.nil? message, progname = progname, nil end progname ||= @ident - mask = Syslog::LOG_UPTO(MAPPING[@level]) + mask = Syslog::LOG_UPTO(MAPPING[level]) communication = message || block && block.call formatted_communication = clean(formatter.call(severity, Time.now, progname, communication)) # Call Syslog syslog_add(progname, severity, mask, formatted_communication) end - # Set the max octets of the messages written to the log - def max_octets=(max_octets) - @max_octets = max_octets - end - # Sets the minimum level for messages to be written in the log. # +level+:: one of <tt>Logger::DEBUG</tt>, <tt>Logger::INFO</tt>, <tt>Logger::WARN</tt>, <tt>Logger::ERROR</tt>, <tt>Logger::FATAL</tt>, <tt>Logger::UNKNOWN</tt> def level=(level) @level = sanitize_level(level) end - # Sets the ident string passed along to Syslog - def ident=(ident) - @ident = ident - end - # Tagging code borrowed from ActiveSupport gem def tagged(*tags) - new_tags = push_tags(*tags) - yield self - ensure - pop_tags(new_tags.size) + formatter.tagged(*tags) { yield self } end - def push_tags(*tags) - tags.flatten.reject { |i| i.respond_to?(:empty?) ? i.empty? : !i }.tap do |new_tags| - current_tags.concat(new_tags).uniq! - end - end + def_delegators :formatter, :current_tags, :push_tags, :pop_tags, :clear_tags! - def pop_tags(size = 1) - current_tags.pop size - end - - def clear_tags! - current_tags.clear - end - - def current_tags - Thread.current[:syslogger_tagged_logging_tags] ||= [] - end - protected def sanitize_level(new_level) begin new_level = Logger.const_get(new_level.to_s.upcase) @@ -161,42 +132,78 @@ # Borrowed from SyslogLogger. def clean(message) message = message.to_s.dup message.strip! # remove whitespace - message.gsub!(/\n/, '\\n') # escape newlines - message.gsub!(/%/, '%%') # syslog(3) freaks on % (printf) - message.gsub!(/\e\[[^m]*m/, '') # remove useless ansi color codes + message.gsub!(/\n/, '\\n'.freeze) # escape newlines + message.gsub!(/%/, '%%'.freeze) # syslog(3) freaks on % (printf) + message.gsub!(/\e\[[^m]*m/, ''.freeze) # remove useless ansi color codes message end private - def tags_text - tags = current_tags - if tags.any? - clean(tags.collect { |tag| "[#{tag}] " }.join) << ' ' - end - end - def syslog_add(progname, severity, mask, formatted_communication) MUTEX.synchronize do Syslog.open(progname, @options, @facility) do |s| s.mask = mask - if self.max_octets - buffer = "#{tags_text}" + if max_octets + buffer = '' formatted_communication.bytes do |byte| buffer.concat(byte) # if the last byte we added is potentially part of an escape, we'll go ahead and add another byte - if buffer.bytesize >= self.max_octets && !['%'.ord,'\\'.ord].include?(byte) + if buffer.bytesize >= max_octets && !['%'.ord, '\\'.ord].include?(byte) s.log(MAPPING[severity], buffer) buffer = '' end end s.log(MAPPING[severity], buffer) unless buffer.empty? else - s.log(MAPPING[severity], "#{tags_text}#{formatted_communication}") + s.log(MAPPING[severity], formatted_communication) end end + end + end + + # Borrowed from ActiveSupport. + # See: https://github.com/rails/rails/blob/master/activesupport/lib/active_support/tagged_logging.rb + class SimpleFormatter < Logger::Formatter + # This method is invoked when a log event occurs. + def call(_severity, _timestamp, _progname, msg) + "#{tags_text}#{msg}" + end + + def tagged(*tags) + new_tags = push_tags(*tags) + yield self + ensure + pop_tags(new_tags.size) + end + + def push_tags(*tags) + tags.flatten.reject { |i| i.respond_to?(:empty?) ? i.empty? : !i }.tap do |new_tags| + current_tags.concat(new_tags).uniq! + end + end + + def pop_tags(size = 1) + current_tags.pop size + end + + def clear_tags! + current_tags.clear + end + + # Fix: https://github.com/crohr/syslogger/issues/29 + # See: https://github.com/rails/rails/blob/master/activesupport/lib/active_support/tagged_logging.rb#L47 + def current_tags + # We use our object ID here to avoid conflicting with other instances + thread_key = @thread_key ||= "syslogger_tagged_logging_tags:#{object_id}".freeze + Thread.current[thread_key] ||= [] + end + + def tags_text + tags = current_tags + tags.collect { |tag| "[#{tag}] " }.join if tags.any? end end end