diff options
-rw-r--r-- | ChangeLog | 7 | ||||
-rw-r--r-- | NEWS | 3 | ||||
-rw-r--r-- | ext/syslog/lib/syslog/logger.rb | 174 | ||||
-rw-r--r-- | test/syslog/test_syslog_logger.rb | 507 |
4 files changed, 691 insertions, 0 deletions
@@ -1,3 +1,10 @@ +Fri May 18 06:14:07 2012 Eric Hodel <drbrain@segment7.net> + + * ext/syslog/lib/syslog/logger.rb: Added Syslog::Logger which was + ported from the SyslogLogger gem. [ruby-trunk - Feature #5096] + * NEWS: ditto. + * test/syslog/test_syslog_logger.rb: ditto. + Fri May 18 01:28:21 2012 Aaron Patterson <aaron@tenderlovemaking.com> * ext/psych/parser.c (transcode_string): fix encoding index names. @@ -96,6 +96,9 @@ with all sufficient information, see the ChangeLog file. * Shellwords#shelljoin() accepts non-string objects in the given array, each of which is stringified using to_s. +* syslog + * Added Syslog::Logger which provides a Logger API atop Syslog. + * lib/tmpdir.rb * incompatible changes: * Dir.mktmpdir uses FileUtils.remove_entry instead of diff --git a/ext/syslog/lib/syslog/logger.rb b/ext/syslog/lib/syslog/logger.rb new file mode 100644 index 0000000000..10c6b590c6 --- /dev/null +++ b/ext/syslog/lib/syslog/logger.rb @@ -0,0 +1,174 @@ +require 'syslog' +require 'logger' + +## +# Syslog::Logger is a Logger work-alike that logs via syslog instead of to a +# file. You can use Syslog::Logger to aggregate logs between multiple +# machines. +# +# By default, Syslog::Logger uses the program name 'ruby', but this can be +# changed via the first argument to Syslog::Logger.new. +# +# NOTE! You can only set the Syslog::Logger program name when you initialize +# Syslog::Logger for the first time. This is a limitation of the way +# Syslog::Logger uses syslog (and in some ways, a limitation of the way +# syslog(3) works). Attempts to change Syslog::Logger's program name after +# the first initialization will be ignored. +# +# === Example +# +# The following will log to syslogd on your local machine: +# +# require 'syslog/logger' +# +# log = Syslog::Logger.new 'my_program' +# log.info 'this line will be logged via syslog(3)' +# +# You may need to perform some syslog.conf setup first. For a BSD machine add +# the following lines to /etc/syslog.conf: +# +# !my_program +# *.* /var/log/my_program.log +# +# Then touch /var/log/my_program.log and signal syslogd with a HUP +# (killall -HUP syslogd, on FreeBSD). +# +# If you wish to have logs automatically roll over and archive, see the +# newsyslog.conf(5) and newsyslog(8) man pages. + +class Syslog::Logger + + ## + # The version of Syslog::Logger you are using. + + VERSION = '2.0' + + ## + # Maps Logger warning types to syslog(3) warning types. + # + # Messages from ruby applications are not considered as critical as messages + # from other system daemons using syslog(3), so most messages are reduced by + # one level. For example, a fatal message for ruby's Logger is considered + # an error for syslog(3). + + LEVEL_MAP = { + ::Logger::UNKNOWN => Syslog::LOG_ALERT, + ::Logger::FATAL => Syslog::LOG_ERR, + ::Logger::ERROR => Syslog::LOG_WARNING, + ::Logger::WARN => Syslog::LOG_NOTICE, + ::Logger::INFO => Syslog::LOG_INFO, + ::Logger::DEBUG => Syslog::LOG_DEBUG, + } + + ## + # Returns the internal Syslog object that is initialized when the + # first instance is created. + + def self.syslog + @@syslog + end + + ## + # Specifies the internal Syslog object to be used. + + def self.syslog= syslog + @@syslog = syslog + end + + ## + # Builds a methods for level +meth+. + + def self.make_methods meth + level = ::Logger.const_get(meth.upcase) + eval <<-EOM, nil, __FILE__, __LINE__ + 1 + def #{meth}(message = nil, &block) + add(#{level}, message, &block) + end + + def #{meth}? + @level <= #{level} + end + EOM + end + + ## + # :method: unknown + # + # Logs a +message+ at the unknown (syslog alert) log level, or logs the + # message returned from the block. + + ## + # :method: fatal + # + # Logs a +message+ at the fatal (syslog err) log level, or logs the message + # returned from the block. + + ## + # :method: error + # + # Logs a +message+ at the error (syslog warning) log level, or logs the + # message returned from the block. + + ## + # :method: warn + # + # Logs a +message+ at the warn (syslog notice) log level, or logs the + # message returned from the block. + + ## + # :method: info + # + # Logs a +message+ at the info (syslog info) log level, or logs the message + # returned from the block. + + ## + # :method: debug + # + # Logs a +message+ at the debug (syslog debug) log level, or logs the + # message returned from the block. + + Logger::Severity::constants.each do |severity| + make_methods severity.downcase + end + + ## + # Log level for Logger compatibility. + + attr_accessor :level + + ## + # Fills in variables for Logger compatibility. If this is the first + # instance of Syslog::Logger, +program_name+ may be set to change the logged + # program name. + # + # Due to the way syslog works, only one program name may be chosen. + + def initialize program_name = 'ruby' + @level = ::Logger::DEBUG + + @@syslog ||= Syslog.open(program_name) + end + + ## + # Almost duplicates Logger#add. +progname+ is ignored. + + def add severity, message = nil, progname = nil, &block + severity ||= ::Logger::UNKNOWN + @level <= severity and + @@syslog.log LEVEL_MAP[severity], '%s', clean(message || block.call) + true + end + + private + + ## + # Clean up messages so they're nice and pretty. + + def clean message + message = message.to_s.strip + message.gsub!(/\e\[[0-9;]*m/, '') # remove useless ansi color codes + return message + end + +end + diff --git a/test/syslog/test_syslog_logger.rb b/test/syslog/test_syslog_logger.rb new file mode 100644 index 0000000000..9224296540 --- /dev/null +++ b/test/syslog/test_syslog_logger.rb @@ -0,0 +1,507 @@ +require 'test/unit' +require 'tempfile' +require 'syslog/logger' + +# These tests ensure Syslog::Logger works like Logger + +class TestSyslogRootLogger < Test::Unit::TestCase + + module MockSyslog + LEVEL_LABEL_MAP = {} + + class << self + + @line = nil + + %w[ALERT ERR WARNING NOTICE INFO DEBUG].each do |name| + level = Syslog.const_get("LOG_#{name}") + LEVEL_LABEL_MAP[level] = name + + eval <<-EOM + def #{name.downcase}(format, *args) + log(#{level}, format, *args) + end + EOM + end + + def log(level, format, *args) + @line = "#{LEVEL_LABEL_MAP[level]} - \#{format % args}" + end + + attr_reader :line + attr_reader :program_name + + def open(program_name) + @program_name = program_name + end + + def reset + @line = '' + end + + end + end + + Syslog::Logger.syslog = MockSyslog + + LEVEL_LABEL_MAP = { + Logger::DEBUG => 'DEBUG', + Logger::INFO => 'INFO', + Logger::WARN => 'WARN', + Logger::ERROR => 'ERROR', + Logger::FATAL => 'FATAL', + Logger::UNKNOWN => 'ANY', + } + + def setup + @logger = Logger.new(nil) + end + + class Log + attr_reader :line, :label, :datetime, :pid, :severity, :progname, :msg + def initialize(line) + @line = line + /\A(\w+), \[([^#]*)#(\d+)\]\s+(\w+) -- (\w*): ([\x0-\xff]*)/ =~ @line + @label, @datetime, @pid, @severity, @progname, @msg = $1, $2, $3, $4, $5, $6 + end + end + + def log_add(severity, msg, progname = nil, &block) + log(:add, severity, msg, progname, &block) + end + + def log(msg_id, *arg, &block) + Log.new(log_raw(msg_id, *arg, &block)) + end + + def log_raw(msg_id, *arg, &block) + logdev = Tempfile.new(File.basename(__FILE__) + '.log') + @logger.instance_eval { @logdev = Logger::LogDevice.new(logdev) } + assert_equal true, @logger.__send__(msg_id, *arg, &block) + logdev.open + msg = logdev.read + logdev.close + msg + end + + def test_initialize + assert_equal Logger::DEBUG, @logger.level + end + + def test_add + msg = log_add nil, 'unknown level message' # nil == unknown + assert_equal LEVEL_LABEL_MAP[Logger::UNKNOWN], msg.severity + + msg = log_add Logger::FATAL, 'fatal level message' + assert_equal LEVEL_LABEL_MAP[Logger::FATAL], msg.severity + + msg = log_add Logger::ERROR, 'error level message' + assert_equal LEVEL_LABEL_MAP[Logger::ERROR], msg.severity + + msg = log_add Logger::WARN, 'warn level message' + assert_equal LEVEL_LABEL_MAP[Logger::WARN], msg.severity + + msg = log_add Logger::INFO, 'info level message' + assert_equal LEVEL_LABEL_MAP[Logger::INFO], msg.severity + + msg = log_add Logger::DEBUG, 'debug level message' + assert_equal LEVEL_LABEL_MAP[Logger::DEBUG], msg.severity + end + + def test_add_level_unknown + @logger.level = Logger::UNKNOWN + + msg = log_add nil, 'unknown level message' # nil == unknown + assert_equal LEVEL_LABEL_MAP[Logger::UNKNOWN], msg.severity + + msg = log_add Logger::FATAL, 'fatal level message' + assert_equal '', msg.line + + msg = log_add Logger::ERROR, 'error level message' + assert_equal '', msg.line + + msg = log_add Logger::WARN, 'warn level message' + assert_equal '', msg.line + + msg = log_add Logger::INFO, 'info level message' + assert_equal '', msg.line + + msg = log_add Logger::DEBUG, 'debug level message' + assert_equal '', msg.line + end + + def test_add_level_fatal + @logger.level = Logger::FATAL + + msg = log_add nil, 'unknown level message' # nil == unknown + assert_equal LEVEL_LABEL_MAP[Logger::UNKNOWN], msg.severity + + msg = log_add Logger::FATAL, 'fatal level message' + assert_equal LEVEL_LABEL_MAP[Logger::FATAL], msg.severity + + msg = log_add Logger::ERROR, 'error level message' + assert_equal '', msg.line + + msg = log_add Logger::WARN, 'warn level message' + assert_equal '', msg.line + + msg = log_add Logger::INFO, 'info level message' + assert_equal '', msg.line + + msg = log_add Logger::DEBUG, 'debug level message' + assert_equal '', msg.line + end + + def test_add_level_error + @logger.level = Logger::ERROR + + msg = log_add nil, 'unknown level message' # nil == unknown + assert_equal LEVEL_LABEL_MAP[Logger::UNKNOWN], msg.severity + + msg = log_add Logger::FATAL, 'fatal level message' + assert_equal LEVEL_LABEL_MAP[Logger::FATAL], msg.severity + + msg = log_add Logger::ERROR, 'error level message' + assert_equal LEVEL_LABEL_MAP[Logger::ERROR], msg.severity + + msg = log_add Logger::WARN, 'warn level message' + assert_equal '', msg.line + + msg = log_add Logger::INFO, 'info level message' + assert_equal '', msg.line + + msg = log_add Logger::DEBUG, 'debug level message' + assert_equal '', msg.line + end + + def test_add_level_warn + @logger.level = Logger::WARN + + msg = log_add nil, 'unknown level message' # nil == unknown + assert_equal LEVEL_LABEL_MAP[Logger::UNKNOWN], msg.severity + + msg = log_add Logger::FATAL, 'fatal level message' + assert_equal LEVEL_LABEL_MAP[Logger::FATAL], msg.severity + + msg = log_add Logger::ERROR, 'error level message' + assert_equal LEVEL_LABEL_MAP[Logger::ERROR], msg.severity + + msg = log_add Logger::WARN, 'warn level message' + assert_equal LEVEL_LABEL_MAP[Logger::WARN], msg.severity + + msg = log_add Logger::INFO, 'info level message' + assert_equal '', msg.line + + msg = log_add Logger::DEBUG, 'debug level message' + assert_equal '', msg.line + end + + def test_add_level_info + @logger.level = Logger::INFO + + msg = log_add nil, 'unknown level message' # nil == unknown + assert_equal LEVEL_LABEL_MAP[Logger::UNKNOWN], msg.severity + + msg = log_add Logger::FATAL, 'fatal level message' + assert_equal LEVEL_LABEL_MAP[Logger::FATAL], msg.severity + + msg = log_add Logger::ERROR, 'error level message' + assert_equal LEVEL_LABEL_MAP[Logger::ERROR], msg.severity + + msg = log_add Logger::WARN, 'warn level message' + assert_equal LEVEL_LABEL_MAP[Logger::WARN], msg.severity + + msg = log_add Logger::INFO, 'info level message' + assert_equal LEVEL_LABEL_MAP[Logger::INFO], msg.severity + + msg = log_add Logger::DEBUG, 'debug level message' + assert_equal '', msg.line + end + + def test_add_level_debug + @logger.level = Logger::DEBUG + + msg = log_add nil, 'unknown level message' # nil == unknown + assert_equal LEVEL_LABEL_MAP[Logger::UNKNOWN], msg.severity + + msg = log_add Logger::FATAL, 'fatal level message' + assert_equal LEVEL_LABEL_MAP[Logger::FATAL], msg.severity + + msg = log_add Logger::ERROR, 'error level message' + assert_equal LEVEL_LABEL_MAP[Logger::ERROR], msg.severity + + msg = log_add Logger::WARN, 'warn level message' + assert_equal LEVEL_LABEL_MAP[Logger::WARN], msg.severity + + msg = log_add Logger::INFO, 'info level message' + assert_equal LEVEL_LABEL_MAP[Logger::INFO], msg.severity + + msg = log_add Logger::DEBUG, 'debug level message' + assert_equal LEVEL_LABEL_MAP[Logger::DEBUG], msg.severity + end + + def test_unknown + msg = log :unknown, 'unknown level message' + assert_equal LEVEL_LABEL_MAP[Logger::UNKNOWN], msg.severity + + @logger.level = Logger::UNKNOWN + msg = log :unknown, 'unknown level message' + assert_equal LEVEL_LABEL_MAP[Logger::UNKNOWN], msg.severity + + @logger.level = Logger::FATAL + msg = log :unknown, 'unknown level message' + assert_equal LEVEL_LABEL_MAP[Logger::UNKNOWN], msg.severity + + @logger.level = Logger::ERROR + msg = log :unknown, 'unknown level message' + assert_equal LEVEL_LABEL_MAP[Logger::UNKNOWN], msg.severity + + @logger.level = Logger::WARN + msg = log :unknown, 'unknown level message' + assert_equal LEVEL_LABEL_MAP[Logger::UNKNOWN], msg.severity + + @logger.level = Logger::INFO + msg = log :unknown, 'unknown level message' + assert_equal LEVEL_LABEL_MAP[Logger::UNKNOWN], msg.severity + + @logger.level = Logger::DEBUG + msg = log :unknown, 'unknown level message' + assert_equal LEVEL_LABEL_MAP[Logger::UNKNOWN], msg.severity + end + + def test_fatal + msg = log :fatal, 'fatal level message' + assert_equal LEVEL_LABEL_MAP[Logger::FATAL], msg.severity + + @logger.level = Logger::UNKNOWN + msg = log :fatal, 'fatal level message' + assert_equal '', msg.line + + @logger.level = Logger::FATAL + msg = log :fatal, 'fatal level message' + assert_equal LEVEL_LABEL_MAP[Logger::FATAL], msg.severity + + @logger.level = Logger::ERROR + msg = log :fatal, 'fatal level message' + assert_equal LEVEL_LABEL_MAP[Logger::FATAL], msg.severity + + @logger.level = Logger::WARN + msg = log :fatal, 'fatal level message' + assert_equal LEVEL_LABEL_MAP[Logger::FATAL], msg.severity + + @logger.level = Logger::INFO + msg = log :fatal, 'fatal level message' + assert_equal LEVEL_LABEL_MAP[Logger::FATAL], msg.severity + + @logger.level = Logger::DEBUG + msg = log :fatal, 'fatal level message' + assert_equal LEVEL_LABEL_MAP[Logger::FATAL], msg.severity + end + + def test_fatal_eh + @logger.level = Logger::FATAL + assert_equal true, @logger.fatal? + + @logger.level = Logger::UNKNOWN + assert_equal false, @logger.fatal? + end + + def test_error + msg = log :error, 'error level message' + assert_equal LEVEL_LABEL_MAP[Logger::ERROR], msg.severity + + @logger.level = Logger::UNKNOWN + msg = log :error, 'error level message' + assert_equal '', msg.line + + @logger.level = Logger::FATAL + msg = log :error, 'error level message' + assert_equal '', msg.line + + @logger.level = Logger::ERROR + msg = log :error, 'error level message' + assert_equal LEVEL_LABEL_MAP[Logger::ERROR], msg.severity + + @logger.level = Logger::WARN + msg = log :error, 'error level message' + assert_equal LEVEL_LABEL_MAP[Logger::ERROR], msg.severity + + @logger.level = Logger::INFO + msg = log :error, 'error level message' + assert_equal LEVEL_LABEL_MAP[Logger::ERROR], msg.severity + + @logger.level = Logger::DEBUG + msg = log :error, 'error level message' + assert_equal LEVEL_LABEL_MAP[Logger::ERROR], msg.severity + end + + def test_error_eh + @logger.level = Logger::ERROR + assert_equal true, @logger.error? + + @logger.level = Logger::FATAL + assert_equal false, @logger.error? + end + + def test_warn + msg = log :warn, 'warn level message' + assert_equal LEVEL_LABEL_MAP[Logger::WARN], msg.severity + + @logger.level = Logger::UNKNOWN + msg = log :warn, 'warn level message' + assert_equal '', msg.line + + @logger.level = Logger::FATAL + msg = log :warn, 'warn level message' + assert_equal '', msg.line + + @logger.level = Logger::ERROR + msg = log :warn, 'warn level message' + assert_equal '', msg.line + + @logger.level = Logger::WARN + msg = log :warn, 'warn level message' + assert_equal LEVEL_LABEL_MAP[Logger::WARN], msg.severity + + @logger.level = Logger::INFO + msg = log :warn, 'warn level message' + assert_equal LEVEL_LABEL_MAP[Logger::WARN], msg.severity + + @logger.level = Logger::DEBUG + msg = log :warn, 'warn level message' + assert_equal LEVEL_LABEL_MAP[Logger::WARN], msg.severity + end + + def test_warn_eh + @logger.level = Logger::WARN + assert_equal true, @logger.warn? + + @logger.level = Logger::ERROR + assert_equal false, @logger.warn? + end + + def test_info + msg = log :info, 'info level message' + assert_equal LEVEL_LABEL_MAP[Logger::INFO], msg.severity + + @logger.level = Logger::UNKNOWN + msg = log :info, 'info level message' + assert_equal '', msg.line + + @logger.level = Logger::FATAL + msg = log :info, 'info level message' + assert_equal '', msg.line + + @logger.level = Logger::ERROR + msg = log :info, 'info level message' + assert_equal '', msg.line + + @logger.level = Logger::WARN + msg = log :info, 'info level message' + assert_equal '', msg.line + + @logger.level = Logger::INFO + msg = log :info, 'info level message' + assert_equal LEVEL_LABEL_MAP[Logger::INFO], msg.severity + + @logger.level = Logger::DEBUG + msg = log :info, 'info level message' + assert_equal LEVEL_LABEL_MAP[Logger::INFO], msg.severity + end + + def test_info_eh + @logger.level = Logger::INFO + assert_equal true, @logger.info? + + @logger.level = Logger::WARN + assert_equal false, @logger.info? + end + + def test_debug + msg = log :debug, 'debug level message' + assert_equal LEVEL_LABEL_MAP[Logger::DEBUG], msg.severity + + @logger.level = Logger::UNKNOWN + msg = log :debug, 'debug level message' + assert_equal '', msg.line + + @logger.level = Logger::FATAL + msg = log :debug, 'debug level message' + assert_equal '', msg.line + + @logger.level = Logger::ERROR + msg = log :debug, 'debug level message' + assert_equal '', msg.line + + @logger.level = Logger::WARN + msg = log :debug, 'debug level message' + assert_equal '', msg.line + + @logger.level = Logger::INFO + msg = log :debug, 'debug level message' + assert_equal '', msg.line + + @logger.level = Logger::DEBUG + msg = log :debug, 'debug level message' + assert_equal LEVEL_LABEL_MAP[Logger::DEBUG], msg.severity + end + + def test_debug_eh + @logger.level = Logger::DEBUG + assert_equal true, @logger.debug? + + @logger.level = Logger::INFO + assert_equal false, @logger.debug? + end + +end + +class TestSyslogLogger < TestSyslogRootLogger + + def setup + super + @logger = Syslog::Logger.new + end + + SEVERITY_MAP = {}.tap { |map| + level2severity = Syslog::Logger::LEVEL_MAP.invert + + MockSyslog::LEVEL_LABEL_MAP.each { |level, name| + map[name] = TestSyslogRootLogger::LEVEL_LABEL_MAP[level2severity[level]] + } + } + + class Log + attr_reader :line, :label, :datetime, :pid, :severity, :progname, :msg + def initialize(line) + @line = line + return unless /\A(\w+) - (.*)\Z/ =~ @line + severity, @msg = $1, $2 + @severity = SEVERITY_MAP[severity] + end + end + + def log_add(severity, msg, progname = nil, &block) + log(:add, severity, msg, progname, &block) + end + + def log(msg_id, *arg, &block) + Log.new(log_raw(msg_id, *arg, &block)) + end + + def log_raw(msg_id, *arg, &block) + assert_equal true, @logger.__send__(msg_id, *arg, &block) + msg = MockSyslog.line + MockSyslog.reset + return msg + end + + def test_unknown_eh + @logger.level = Logger::UNKNOWN + assert_equal true, @logger.unknown? + + @logger.level = Logger::UNKNOWN + 1 + assert_equal false, @logger.unknown? + end + +end |