1require 'syslog'
2require 'logger'
3
4##
5# Syslog::Logger is a Logger work-alike that logs via syslog instead of to a
6# file.  You can use Syslog::Logger to aggregate logs between multiple
7# machines.
8#
9# By default, Syslog::Logger uses the program name 'ruby', but this can be
10# changed via the first argument to Syslog::Logger.new.
11#
12# NOTE! You can only set the Syslog::Logger program name when you initialize
13# Syslog::Logger for the first time.  This is a limitation of the way
14# Syslog::Logger uses syslog (and in some ways, a limitation of the way
15# syslog(3) works).  Attempts to change Syslog::Logger's program name after
16# the first initialization will be ignored.
17#
18# === Example
19#
20# The following will log to syslogd on your local machine:
21#
22#   require 'syslog/logger'
23#
24#   log = Syslog::Logger.new 'my_program'
25#   log.info 'this line will be logged via syslog(3)'
26#
27# You may need to perform some syslog.conf setup first.  For a BSD machine add
28# the following lines to /etc/syslog.conf:
29#
30#  !my_program
31#  *.*                                             /var/log/my_program.log
32#
33# Then touch /var/log/my_program.log and signal syslogd with a HUP
34# (killall -HUP syslogd, on FreeBSD).
35#
36# If you wish to have logs automatically roll over and archive, see the
37# newsyslog.conf(5) and newsyslog(8) man pages.
38
39class Syslog::Logger
40  # Default formatter for log messages.
41  class Formatter
42    def call severity, time, progname, msg
43      clean msg
44    end
45
46    private
47
48    ##
49    # Clean up messages so they're nice and pretty.
50
51    def clean message
52      message = message.to_s.strip
53      message.gsub!(/\e\[[0-9;]*m/, '') # remove useless ansi color codes
54      return message
55    end
56  end
57
58  ##
59  # The version of Syslog::Logger you are using.
60
61  VERSION = '2.0'
62
63  ##
64  # Maps Logger warning types to syslog(3) warning types.
65  #
66  # Messages from ruby applications are not considered as critical as messages
67  # from other system daemons using syslog(3), so most messages are reduced by
68  # one level.  For example, a fatal message for ruby's Logger is considered
69  # an error for syslog(3).
70
71  LEVEL_MAP = {
72    ::Logger::UNKNOWN => Syslog::LOG_ALERT,
73    ::Logger::FATAL   => Syslog::LOG_ERR,
74    ::Logger::ERROR   => Syslog::LOG_WARNING,
75    ::Logger::WARN    => Syslog::LOG_NOTICE,
76    ::Logger::INFO    => Syslog::LOG_INFO,
77    ::Logger::DEBUG   => Syslog::LOG_DEBUG,
78  }
79
80  ##
81  # Returns the internal Syslog object that is initialized when the
82  # first instance is created.
83
84  def self.syslog
85    @@syslog
86  end
87
88  ##
89  # Specifies the internal Syslog object to be used.
90
91  def self.syslog= syslog
92    @@syslog = syslog
93  end
94
95  ##
96  # Builds a methods for level +meth+.
97
98  def self.make_methods meth
99    level = ::Logger.const_get(meth.upcase)
100    eval <<-EOM, nil, __FILE__, __LINE__ + 1
101      def #{meth}(message = nil, &block)
102        add(#{level}, message, &block)
103      end
104
105      def #{meth}?
106        @level <= #{level}
107      end
108    EOM
109  end
110
111  ##
112  # :method: unknown
113  #
114  # Logs a +message+ at the unknown (syslog alert) log level, or logs the
115  # message returned from the block.
116
117  ##
118  # :method: fatal
119  #
120  # Logs a +message+ at the fatal (syslog err) log level, or logs the message
121  # returned from the block.
122
123  ##
124  # :method: error
125  #
126  # Logs a +message+ at the error (syslog warning) log level, or logs the
127  # message returned from the block.
128
129  ##
130  # :method: warn
131  #
132  # Logs a +message+ at the warn (syslog notice) log level, or logs the
133  # message returned from the block.
134
135  ##
136  # :method: info
137  #
138  # Logs a +message+ at the info (syslog info) log level, or logs the message
139  # returned from the block.
140
141  ##
142  # :method: debug
143  #
144  # Logs a +message+ at the debug (syslog debug) log level, or logs the
145  # message returned from the block.
146
147  Logger::Severity::constants.each do |severity|
148    make_methods severity.downcase
149  end
150
151  ##
152  # Log level for Logger compatibility.
153
154  attr_accessor :level
155
156  # Logging formatter, as a +Proc+ that will take four arguments and
157  # return the formatted message. The arguments are:
158  #
159  # +severity+:: The Severity of the log message.
160  # +time+:: A Time instance representing when the message was logged.
161  # +progname+:: The #progname configured, or passed to the logger method.
162  # +msg+:: The _Object_ the user passed to the log message; not necessarily a
163  #         String.
164  #
165  # The block should return an Object that can be written to the logging
166  # device via +write+.  The default formatter is used when no formatter is
167  # set.
168  attr_accessor :formatter
169
170  ##
171  # Fills in variables for Logger compatibility.  If this is the first
172  # instance of Syslog::Logger, +program_name+ may be set to change the logged
173  # program name.
174  #
175  # Due to the way syslog works, only one program name may be chosen.
176
177  def initialize program_name = 'ruby'
178    @level = ::Logger::DEBUG
179    @formatter = Formatter.new
180
181    @@syslog ||= Syslog.open(program_name)
182  end
183
184  ##
185  # Almost duplicates Logger#add.  +progname+ is ignored.
186
187  def add severity, message = nil, progname = nil, &block
188    severity ||= ::Logger::UNKNOWN
189    @level <= severity and
190      @@syslog.log LEVEL_MAP[severity], '%s', formatter.call(severity, Time.now, progname, (message || block.call))
191    true
192  end
193end
194
195