logging 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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