logging 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,52 @@
1
+ # $Id: log_event.rb 13 2007-01-15 17:19:37Z tim_pease $
2
+
3
+ module Logging
4
+
5
+ #
6
+ # This class defines a logging event.
7
+ #
8
+ class LogEvent
9
+
10
+ # :stopdoc:
11
+
12
+ # Regular expression used to parse out caller information
13
+ #
14
+ # * $1 == filename
15
+ # * $2 == line number
16
+ # * $3 == method name (might be nil)
17
+ CALLER_RGXP = %r/([\.\/\(\)\w]+):(\d+)(?::in `(\w+)')?/o
18
+ # :startdoc:
19
+
20
+ #
21
+ # call-seq:
22
+ # LogEvent.new( logger, level, [data], trace )
23
+ #
24
+ # Creates a new log event with the given _logger_ name, numeric _level_,
25
+ # array of _data_ from the user to be logged, and boolean _trace_ flag.
26
+ # If the _trace_ flag is set to +true+ then Kernel::caller will be
27
+ # invoked to get the execution trace of the logging method.
28
+ #
29
+ def initialize( logger, level, data, trace )
30
+ @logger = logger
31
+ @level = level
32
+ @data = data
33
+ @file = @line = @method = ''
34
+
35
+ if trace
36
+ t = Kernel.caller(3)[0]
37
+ break if t.nil?
38
+
39
+ m = CALLER_RGXP.match(t)
40
+ @file = m[1]
41
+ @line = m[2]
42
+ @method = m[3] unless m[3].nil?
43
+ end
44
+ end
45
+
46
+ attr_accessor :logger, :level, :data
47
+ attr_reader :file, :line, :method
48
+
49
+ end # class LogEvent
50
+ end # module Logging
51
+
52
+ #EOF
@@ -0,0 +1,345 @@
1
+ # $Id: logger.rb 15 2007-01-15 19:03:45Z tim_pease $
2
+
3
+ require 'sync'
4
+ require 'logging'
5
+ require 'logging/appender'
6
+ require 'logging/log_event'
7
+ require 'logging/repository'
8
+
9
+
10
+ module Logging
11
+
12
+ #
13
+ # The +Logger+ class is the primary interface to the +Logging+ framework.
14
+ # It provides the logging methods that will be called from user methods,
15
+ # and it generates logging events that are sent to the appenders (the
16
+ # appenders take care of sending the log events to the logging
17
+ # destinations -- files, sockets, etc).
18
+ #
19
+ # +Logger+ instances are obtained from the +Repository+ and should
20
+ # not be directly created by users.
21
+ #
22
+ # Example:
23
+ #
24
+ # log = Logging::Logger['my logger']
25
+ # log.add( Logging::Appenders::StdOut.new ) # append to STDOUT
26
+ # log.level = :info # log 'info' and above
27
+ #
28
+ # log.info 'starting foo operation'
29
+ # ...
30
+ # log.info 'finishing foo operation'
31
+ # ...
32
+ # log.fatal 'unknown exception', exception
33
+ #
34
+ class Logger
35
+
36
+ @mutex = Sync.new # :nodoc:
37
+
38
+ class << self
39
+
40
+ #
41
+ # call-seq:
42
+ # Logger.root
43
+ #
44
+ # Returns the root logger.
45
+ #
46
+ def root
47
+ ::Logging::Repository.instance[:root]
48
+ end
49
+
50
+ # :stopdoc:
51
+
52
+ #
53
+ # Overrides the new method such that only one Logger will be created
54
+ # for any given logger name.
55
+ #
56
+ def new( *args )
57
+ return super if args.empty?
58
+
59
+ repo = ::Logging::Repository.instance
60
+ name = repo.to_key(args.shift)
61
+
62
+ @mutex.synchronize(:EX) do
63
+ logger = repo[name]
64
+ if logger.nil?
65
+ logger = super(name, *args)
66
+ repo[name] = logger
67
+ end
68
+ logger
69
+ end
70
+ end
71
+ alias :[] :new
72
+
73
+ #
74
+ # This is where the actual logging methods are defined. Two methods
75
+ # are created for each log level. The first is a query method used to
76
+ # determine if that perticular logging level is enabled. The second is
77
+ # the actual logging method that accepts a list of objects to be
78
+ # logged or a block. If a block is given, then the object returned
79
+ # from the block will be logged.
80
+ #
81
+ # Example
82
+ #
83
+ # log = Logging::Logger['my logger']
84
+ # log.level = :warn
85
+ #
86
+ # log.info? # => false
87
+ # log.warn? # => true
88
+ # log.warn 'this is your last warning'
89
+ # log.fatal 'I die!', exception
90
+ #
91
+ # log.debug do
92
+ # # expensive method to construct log message
93
+ # msg
94
+ # end
95
+ #
96
+ def define_log_methods( logger )
97
+ ::Logging::LEVELS.each do |name,num|
98
+ code = "undef :#{name} if method_defined? :#{name}\n"
99
+ code << "undef :#{name}? if method_defined? :#{name}?\n"
100
+
101
+ if logger.level > num
102
+ code << <<-CODE
103
+ def #{name}?( ) false end
104
+ def #{name}( *args ) false end
105
+ CODE
106
+ else
107
+ code << <<-CODE
108
+ def #{name}?( ) true end
109
+ def #{name}( *args )
110
+ args.push yield if block_given?
111
+ log_event(::Logging::LogEvent.new(@name, #{num}, args, @trace)) unless args.empty?
112
+ true
113
+ end
114
+ CODE
115
+ end
116
+
117
+ logger.meta_eval code
118
+ end
119
+ end
120
+ # :startdoc:
121
+
122
+ end # class << self
123
+
124
+ attr_reader :level, :name, :parent
125
+ attr_accessor :additive, :trace
126
+
127
+ #
128
+ # call-seq:
129
+ # Logger.new( name )
130
+ # Logger[name]
131
+ #
132
+ # Returns the logger identified by _name_.
133
+ #
134
+ # When _name_ is a +String+ or a +Symbol+ it will be used "as is" to
135
+ # retrieve the logger. When _name_ is a +Class+ the class name will be
136
+ # used to retrieve the logger. When _name_ is an object the name of the
137
+ # object's class will be used to retrieve the logger.
138
+ #
139
+ # Example:
140
+ #
141
+ # obj = MyClass.new
142
+ #
143
+ # log1 = Logger.new(obj)
144
+ # log2 = Logger.new(MyClass)
145
+ # log3 = Logger['MyClass']
146
+ #
147
+ # log1.object_id == log2.object_id # => true
148
+ # log2.object_id == log3.object_id # => true
149
+ #
150
+ def initialize( name )
151
+ case name
152
+ when String:
153
+ raise(ArgumentError, "logger must have a name") if name.empty?
154
+ else raise(ArgumentError, "logger name must be a String") end
155
+
156
+ repo = ::Logging::Repository.instance
157
+ @name = name
158
+ @parent = repo.parent(name)
159
+ @appenders = []
160
+ @additive = true
161
+ @trace = false
162
+ self.level = @parent.level
163
+
164
+ repo.children(name).each {|c| c.parent = self}
165
+ end
166
+
167
+ #
168
+ # call-seq:
169
+ # log <=> other
170
+ #
171
+ # Compares this logger by name to another logger. The normal return codes
172
+ # for +String+ objects apply.
173
+ #
174
+ def <=>( other )
175
+ case other
176
+ when self: 0
177
+ when ::Logging::RootLogger: 1
178
+ when ::Logging::Logger: @name <=> other.name
179
+ else raise ArgumentError, 'expecting a Logger instance' end
180
+ end
181
+
182
+ #
183
+ # call-seq:
184
+ # log << "message"
185
+ #
186
+ # Log the given message without any formatting and without performing any
187
+ # level checks. The message is logged to all appenders. The message is
188
+ # passed up the logger tree if this logger's additivity is +true+.
189
+ #
190
+ def <<( msg )
191
+ @appenders.each {|a| a << msg}
192
+ @parent << msg if @additive
193
+ end
194
+
195
+ #
196
+ # call-seq:
197
+ # level = :all
198
+ #
199
+ # Set the level for this logger. The level can be either a +String+, a
200
+ # +Symbol+, or a +Fixnum+. An +ArgumentError+ is raised if this is not
201
+ # the case.
202
+ #
203
+ # There are two special levels -- "all" and "off". The former will
204
+ # enable log messages from this logger. The latter will disable all log
205
+ # messages from this logger.
206
+ #
207
+ # Setting the logger level to +nil+ will cause the parent's logger level
208
+ # to be used.
209
+ #
210
+ # Example:
211
+ #
212
+ # log.level = :debug
213
+ # log.level = "INFO"
214
+ # log.level = 4
215
+ # log.level = 'off'
216
+ # log.level = :all
217
+ #
218
+ # These prodcue an +ArgumentError+
219
+ #
220
+ # log.level = Object
221
+ # log.level = -1
222
+ # log.level = 1_000_000_000_000
223
+ #
224
+ def level=( level )
225
+ lvl = case level
226
+ when String, Symbol: ::Logging::level_num(level)
227
+ when Fixnum: level
228
+ when nil: @parent.level
229
+ else
230
+ raise ArgumentError,
231
+ "level must be a String, Symbol, or Integer"
232
+ end
233
+ if lvl.nil? or lvl < 0 or lvl > ::Logging::LEVELS.length
234
+ raise ArgumentError, "unknown level was given '#{level}'"
235
+ end
236
+
237
+ @level = lvl
238
+ ::Logging::Logger.define_log_methods(self)
239
+ @level
240
+ end
241
+
242
+ #
243
+ # call-seq:
244
+ # appenders = app
245
+ #
246
+ # Clears the current list of appenders and replaces them with _app_,
247
+ # where _app_ can be either a single appender or an array of appenders.
248
+ #
249
+ def appenders=( args )
250
+ @appenders.clear
251
+ add(*args) unless args.nil?
252
+ end
253
+
254
+ #
255
+ # call-seq:
256
+ # add( appenders )
257
+ #
258
+ # Add the given _appenders_ to the list of appenders, where _appenders_
259
+ # can be either a single appender or an array of appenders.
260
+ #
261
+ def add( *args )
262
+ args.each do |arg|
263
+ unless arg.kind_of? ::Logging::Appender
264
+ raise TypeError,
265
+ "#{arg.inspect} is not a kind of 'Logging::Appender'"
266
+ end
267
+ @appenders << arg unless @appenders.include? arg
268
+ end
269
+ end
270
+
271
+ #
272
+ # call-seq:
273
+ # remove( appenders )
274
+ #
275
+ # Remove the given _appenders_ from the list of appenders. The appenders
276
+ # to remove can be identified either by name using a +String+ or by
277
+ # passing the appender instance. _appenders_ can be a single appender or
278
+ # an array of appenders.
279
+ #
280
+ def remove( *args )
281
+ args.each do |arg|
282
+ @appenders.delete_if do |a|
283
+ case arg
284
+ when String: arg == a.name
285
+ when ::Logging::Appender: arg.object_id == a.object_id
286
+ else
287
+ raise TypeError, "#{arg.inspect} is not a 'Logging::Appender'"
288
+ end
289
+ end
290
+ end
291
+ end
292
+
293
+ #
294
+ # call-seq:
295
+ # clear
296
+ #
297
+ # Remove all appenders from this logger.
298
+ #
299
+ def clear( ) @appenders.clear end
300
+
301
+
302
+ protected
303
+ #
304
+ # call-seq:
305
+ # parent = ParentLogger
306
+ #
307
+ # Set the parent logger for this logger. This method will be invoked by
308
+ # the +Repository+ class when a parent or child is added to the
309
+ # hierarchy.
310
+ #
311
+ def parent=( parent ) @parent = parent end
312
+
313
+ #
314
+ # call-seq:
315
+ # log_event( event )
316
+ #
317
+ # Send the given _event_ to the appenders for logging, and pass the
318
+ # _event_ up to the parent if additive mode is enabled. The log level has
319
+ # already been checked before this method is called.
320
+ #
321
+ def log_event( event )
322
+ @appenders.each {|a| a.append(event)}
323
+ @parent.log_event(event) if @additive
324
+ end
325
+
326
+ # :stopdoc:
327
+
328
+ #
329
+ # call-seq:
330
+ # meta_eval( code )
331
+ #
332
+ # Evaluates the given string of _code_ if the singleton class of this
333
+ # Logger object.
334
+ #
335
+ def meta_eval( code )
336
+ meta = class << self; self end
337
+ meta.class_eval code
338
+ end
339
+ public :meta_eval
340
+ # :startdoc:
341
+
342
+ end # class Logger
343
+ end # module Logging
344
+
345
+ # EOF
@@ -0,0 +1,148 @@
1
+ # $Id: repository.rb 13 2007-01-15 17:19:37Z tim_pease $
2
+
3
+ require 'singleton'
4
+ require 'logging/root_logger'
5
+
6
+
7
+ module Logging
8
+
9
+ #
10
+ # The Repository is a hash that stores references to all Loggers
11
+ # that have been created. It provides methods to determine parent/child
12
+ # relationships between Loggers and to retrieve Loggers from the hash.
13
+ #
14
+ class Repository
15
+ include Singleton
16
+
17
+ PATH_DELIMITER = '::' # :nodoc:
18
+
19
+ #
20
+ # nodoc:
21
+ #
22
+ # This is a singleton class -- use the +instance+ method to obtain the
23
+ # +Repository+ instance.
24
+ #
25
+ def initialize
26
+ @h = {:root => ::Logging::RootLogger.new}
27
+ end
28
+
29
+ #
30
+ # call-seq:
31
+ # instance[name]
32
+ #
33
+ # Returns the +Logger+ named _name_.
34
+ #
35
+ # When _name_ is a +String+ or a +Symbol+ it will be used "as is" to
36
+ # retrieve the logger. When _name_ is a +Class+ the class name will be
37
+ # used to retrieve the logger. When _name_ is an object the name of the
38
+ # object's class will be used to retrieve the logger.
39
+ #
40
+ # Example:
41
+ #
42
+ # repo = Repository.instance
43
+ # obj = MyClass.new
44
+ #
45
+ # log1 = repo[obj]
46
+ # log2 = repo[MyClass]
47
+ # log3 = repo['MyClass']
48
+ #
49
+ # log1.object_id == log2.object_id # => true
50
+ # log2.object_id == log3.object_id # => true
51
+ #
52
+ def []( key ) @h[to_key(key)] end
53
+
54
+ #
55
+ # call-seq:
56
+ # instance[name] = logger
57
+ #
58
+ # Stores the _logger_ under the given _name_.
59
+ #
60
+ # When _name_ is a +String+ or a +Symbol+ it will be used "as is" to
61
+ # store the logger. When _name_ is a +Class+ the class name will be
62
+ # used to store the logger. When _name_ is an object the name of the
63
+ # object's class will be used to store the logger.
64
+ #
65
+ def []=( key, val ) @h[to_key(key)] = val end
66
+
67
+ #
68
+ # call-seq:
69
+ # fetch( name )
70
+ #
71
+ # Returns the +Logger+ named _name_. An +IndexError+ will be raised if
72
+ # the logger does not exist.
73
+ #
74
+ # When _name_ is a +String+ or a +Symbol+ it will be used "as is" to
75
+ # retrieve the logger. When _name_ is a +Class+ the class name will be
76
+ # used to retrieve the logger. When _name_ is an object the name of the
77
+ # object's class will be used to retrieve the logger.
78
+ #
79
+ def fetch( key ) @h.fetch(to_key(key)) end
80
+
81
+ #
82
+ # call-seq:
83
+ # parent( key )
84
+ #
85
+ # Returns the parent logger for the logger identified by _key_ where
86
+ # _key_ follows the same identification rules described in
87
+ # +Repository#[]+. A parent is returned regardless of the
88
+ # existence of the logger referenced by _key_.
89
+ #
90
+ def parent( key )
91
+ key = to_key(key)
92
+ a = key.split PATH_DELIMITER
93
+
94
+ p = @h[:root]
95
+ while a.slice!(-1) and !a.empty?
96
+ k = a.join PATH_DELIMITER
97
+ if @h.has_key? k then p = @h[k]; break end
98
+ end
99
+ p
100
+ end
101
+
102
+ #
103
+ # call-seq:
104
+ # children( key )
105
+ #
106
+ # Returns an array of the children loggers for the logger identified by
107
+ # _key_ where _key_ follows the same identification rules described in
108
+ # +Repository#[]+. Children are returned regardless of the
109
+ # existence of the logger referenced by _key_.
110
+ #
111
+ def children( key )
112
+ key = to_key(key)
113
+ depth = key.split(PATH_DELIMITER).length
114
+ rgxp = Regexp.new "^#{key}#{PATH_DELIMITER}"
115
+
116
+ a = @h.keys.map do |k|
117
+ if k =~ rgxp
118
+ l = @h[k]
119
+ d = l.parent.name.split(PATH_DELIMITER).length
120
+ if d <= depth then l else nil end
121
+ end
122
+ end
123
+ a.compact.sort
124
+ end
125
+
126
+ #
127
+ # call-seq:
128
+ # to_key( key )
129
+ #
130
+ # Takes the given _key_ and converts it into a form that can be used to
131
+ # retrieve a logger from the +Repository+ hash.
132
+ #
133
+ # When _key_ is a +String+ or a +Symbol+ it will be returned "as is".
134
+ # When _key_ is a +Class+ the class name will be returned. When _key_ is
135
+ # an object the name of the object's class will be returned.
136
+ #
137
+ def to_key( key )
138
+ case key
139
+ when Symbol, String: key
140
+ when Class: key.name
141
+ when Object: key.class.name
142
+ end
143
+ end
144
+
145
+ end # class Repository
146
+ end # module Logging
147
+
148
+ # EOF