TwP-logging 0.9.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. data/History.txt +169 -0
  2. data/README.rdoc +102 -0
  3. data/Rakefile +42 -0
  4. data/data/bad_logging_1.rb +13 -0
  5. data/data/bad_logging_2.rb +21 -0
  6. data/data/logging.rb +42 -0
  7. data/data/logging.yaml +63 -0
  8. data/data/simple_logging.rb +13 -0
  9. data/lib/logging.rb +408 -0
  10. data/lib/logging/appender.rb +303 -0
  11. data/lib/logging/appenders/buffering.rb +167 -0
  12. data/lib/logging/appenders/console.rb +62 -0
  13. data/lib/logging/appenders/email.rb +75 -0
  14. data/lib/logging/appenders/file.rb +54 -0
  15. data/lib/logging/appenders/growl.rb +197 -0
  16. data/lib/logging/appenders/io.rb +69 -0
  17. data/lib/logging/appenders/rolling_file.rb +291 -0
  18. data/lib/logging/appenders/syslog.rb +201 -0
  19. data/lib/logging/config/configurator.rb +190 -0
  20. data/lib/logging/config/yaml_configurator.rb +195 -0
  21. data/lib/logging/layout.rb +119 -0
  22. data/lib/logging/layouts/basic.rb +34 -0
  23. data/lib/logging/layouts/pattern.rb +296 -0
  24. data/lib/logging/log_event.rb +51 -0
  25. data/lib/logging/logger.rb +490 -0
  26. data/lib/logging/repository.rb +172 -0
  27. data/lib/logging/root_logger.rb +61 -0
  28. data/lib/logging/stats.rb +278 -0
  29. data/lib/logging/utils.rb +130 -0
  30. data/logging.gemspec +41 -0
  31. data/test/appenders/test_buffered_io.rb +183 -0
  32. data/test/appenders/test_console.rb +66 -0
  33. data/test/appenders/test_email.rb +171 -0
  34. data/test/appenders/test_file.rb +93 -0
  35. data/test/appenders/test_growl.rb +128 -0
  36. data/test/appenders/test_io.rb +142 -0
  37. data/test/appenders/test_rolling_file.rb +207 -0
  38. data/test/appenders/test_syslog.rb +194 -0
  39. data/test/benchmark.rb +87 -0
  40. data/test/config/test_configurator.rb +70 -0
  41. data/test/config/test_yaml_configurator.rb +40 -0
  42. data/test/layouts/test_basic.rb +43 -0
  43. data/test/layouts/test_pattern.rb +177 -0
  44. data/test/setup.rb +74 -0
  45. data/test/test_appender.rb +166 -0
  46. data/test/test_layout.rb +110 -0
  47. data/test/test_log_event.rb +80 -0
  48. data/test/test_logger.rb +734 -0
  49. data/test/test_logging.rb +267 -0
  50. data/test/test_repository.rb +126 -0
  51. data/test/test_root_logger.rb +81 -0
  52. data/test/test_stats.rb +274 -0
  53. data/test/test_utils.rb +116 -0
  54. metadata +156 -0
@@ -0,0 +1,13 @@
1
+
2
+ Logging.configure {
3
+
4
+ logger(:root) {
5
+ level :info
6
+ appenders 'stdout'
7
+ }
8
+
9
+ appender('stdout') {
10
+ type 'Stdout'
11
+ }
12
+
13
+ } # logging configuration
data/lib/logging.rb ADDED
@@ -0,0 +1,408 @@
1
+
2
+ # Equivalent to a header guard in C/C++
3
+ # Used to prevent the class/module from being loaded more than once
4
+ unless defined? Logging
5
+
6
+ require 'thread'
7
+ begin require 'fastthread'; rescue LoadError; end
8
+
9
+ # TODO: Windows Log Service appender
10
+
11
+ # TODO: Option to buffer log messages at the appender level
12
+ # extend the concept found in the e-mail appender into the other IO
13
+ # appenders
14
+
15
+ #
16
+ #
17
+ module Logging
18
+
19
+ # :stopdoc:
20
+ VERSION = '0.9.7'
21
+ LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
22
+ PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
23
+ WIN32 = %r/djgpp|(cyg|ms|bcc)win|mingw/ =~ RUBY_PLATFORM
24
+ LEVELS = {}
25
+ LNAMES = []
26
+ # :startdoc:
27
+
28
+ class << self
29
+
30
+ # call-seq:
31
+ # Logging.configure( filename )
32
+ # Logging.configure { block }
33
+ #
34
+ # Configures the Logging framework using the configuration information
35
+ # found in the given file. The file extension should be either '.yaml'
36
+ # or '.yml' (XML configuration is not yet supported).
37
+ #
38
+ def configure( *args, &block )
39
+ if block
40
+ return ::Logging::Config::Configurator.process(&block)
41
+ end
42
+
43
+ filename = args.shift
44
+ raise ArgumentError, 'a filename was not given' if filename.nil?
45
+
46
+ case File.extname(filename)
47
+ when '.yaml', '.yml'
48
+ ::Logging::Config::YamlConfigurator.load(filename, *args)
49
+ else raise ArgumentError, 'unknown configuration file format' end
50
+ end
51
+
52
+ # call-seq:
53
+ # Logging.logger( device, age = 7, size = 1048576 )
54
+ # Logging.logger( device, age = 'weekly' )
55
+ #
56
+ # This convenience method returns a Logger instance configured to behave
57
+ # similarly to a core Ruby Logger instance.
58
+ #
59
+ # The _device_ is the logging destination. This can be a filename
60
+ # (String) or an IO object (STDERR, STDOUT, an open File, etc.). The
61
+ # _age_ is the number of old log files to keep or the frequency of
62
+ # rotation (+daily+, +weekly+, or +monthly+). The _size_ is the maximum
63
+ # logfile size and is only used when _age_ is a number.
64
+ #
65
+ # Using the same _device_ twice will result in the same Logger instance
66
+ # being returned. For example, if a Logger is created using STDOUT then
67
+ # the same Logger instance will be returned the next time STDOUT is
68
+ # used. A new Logger instance can be obtained by closing the previous
69
+ # logger instance.
70
+ #
71
+ # log1 = Logging.logger(STDOUT)
72
+ # log2 = Logging.logger(STDOUT)
73
+ # log1.object_id == log2.object_id #=> true
74
+ #
75
+ # log1.close
76
+ # log2 = Logging.logger(STDOUT)
77
+ # log1.object_id == log2.object_id #=> false
78
+ #
79
+ # The format of the log messages can be changed using a few optional
80
+ # parameters. The <tt>:pattern</tt> can be used to change the log
81
+ # message format. The <tt>:date_pattern</tt> can be used to change how
82
+ # timestamps are formatted.
83
+ #
84
+ # log = Logging.logger(STDOUT,
85
+ # :pattern => "[%d] %-5l : %m\n",
86
+ # :date_pattern => "%Y-%m-%d %H:%M:%S.%s")
87
+ #
88
+ # See the documentation for the Logging::Layouts::Pattern class for a
89
+ # full description of the :pattern and :date_pattern formatting strings.
90
+ #
91
+ def logger( *args )
92
+ opts = args.pop if args.last.instance_of?(Hash)
93
+ opts ||= Hash.new
94
+
95
+ dev = args.shift
96
+ keep = age = args.shift
97
+ size = args.shift
98
+
99
+ name = case dev
100
+ when String; dev
101
+ when File; dev.path
102
+ else dev.object_id.to_s end
103
+
104
+ repo = ::Logging::Repository.instance
105
+ return repo[name] if repo.has_logger? name
106
+
107
+ l_opts = {
108
+ :pattern => "%.1l, [%d #%p] %#{::Logging::MAX_LEVEL_LENGTH}l : %m\n",
109
+ :date_pattern => '%Y-%m-%dT%H:%M:%S.%s'
110
+ }
111
+ [:pattern, :date_pattern, :date_method].each do |o|
112
+ l_opts[o] = opts.delete(o) if opts.has_key? o
113
+ end
114
+ layout = ::Logging::Layouts::Pattern.new(l_opts)
115
+
116
+ a_opts = Hash.new
117
+ a_opts[:size] = size if size.instance_of?(Fixnum)
118
+ a_opts[:age] = age if age.instance_of?(String)
119
+ a_opts[:keep] = keep if keep.instance_of?(Fixnum)
120
+ a_opts[:filename] = dev if dev.instance_of?(String)
121
+ a_opts[:layout] = layout
122
+ a_opts.merge! opts
123
+
124
+ appender =
125
+ case dev
126
+ when String
127
+ ::Logging::Appenders::RollingFile.new(name, a_opts)
128
+ else
129
+ ::Logging::Appenders::IO.new(name, dev, a_opts)
130
+ end
131
+
132
+ logger = ::Logging::Logger.new(name)
133
+ logger.add_appenders appender
134
+ logger.additive = false
135
+
136
+ class << logger
137
+ def close
138
+ @appenders.each {|a| a.close}
139
+ h = ::Logging::Repository.instance.instance_variable_get :@h
140
+ h.delete(@name)
141
+ class << self; undef :close; end
142
+ end
143
+ end
144
+
145
+ logger
146
+ end
147
+
148
+ # call-seq:
149
+ # Logging.init( levels )
150
+ #
151
+ # Defines the levels available to the loggers. The _levels_ is an array
152
+ # of strings and symbols. Each element in the array is downcased and
153
+ # converted to a symbol; these symbols are used to create the logging
154
+ # methods in the loggers.
155
+ #
156
+ # The first element in the array is the lowest logging level. Setting the
157
+ # logging level to this value will enable all log messages. The last
158
+ # element in the array is the highest logging level. Setting the logging
159
+ # level to this value will disable all log messages except this highest
160
+ # level.
161
+ #
162
+ # This method should only be invoked once to configure the logging
163
+ # levels. It is automatically invoked with the default logging levels
164
+ # when the first logger is created.
165
+ #
166
+ # The levels "all" and "off" are reserved and will be ignored if passed
167
+ # to this method.
168
+ #
169
+ # Example:
170
+ #
171
+ # Logging.init :debug, :info, :warn, :error, :fatal
172
+ # log = Logging::Logger['my logger']
173
+ # log.level = :warn
174
+ # log.warn 'Danger! Danger! Will Robinson'
175
+ # log.info 'Just FYI' # => not logged
176
+ #
177
+ # or
178
+ #
179
+ # Logging.init %w(DEBUG INFO NOTICE WARNING ERR CRIT ALERT EMERG)
180
+ # log = Logging::Logger['syslog']
181
+ # log.level = :notice
182
+ # log.warning 'This is your first warning'
183
+ # log.info 'Just FYI' # => not logged
184
+ #
185
+ def init( *args )
186
+ args = %w(debug info warn error fatal) if args.empty?
187
+
188
+ args.flatten!
189
+ levels = ::Logging::LEVELS.clear
190
+ names = ::Logging::LNAMES.clear
191
+
192
+ id = 0
193
+ args.each do |lvl|
194
+ lvl = levelify lvl
195
+ unless levels.has_key?(lvl) or lvl == 'all' or lvl == 'off'
196
+ levels[lvl] = id
197
+ names[id] = lvl.upcase
198
+ id += 1
199
+ end
200
+ end
201
+
202
+ longest = names.inject {|x,y| (x.length > y.length) ? x : y}
203
+ longest = 'off' if longest.length < 3
204
+ module_eval "MAX_LEVEL_LENGTH = #{longest.length}", __FILE__, __LINE__
205
+
206
+ levels.keys
207
+ end
208
+
209
+ # call-seq:
210
+ # Logging.format_as( obj_format )
211
+ #
212
+ # Defines the default _obj_format_ method to use when converting objects
213
+ # into string representations for logging. _obj_format_ can be one of
214
+ # <tt>:string</tt>, <tt>:inspect</tt>, or <tt>:yaml</tt>. These
215
+ # formatting commands map to the following object methods
216
+ #
217
+ # * :string => to_s
218
+ # * :inspect => inspect
219
+ # * :yaml => to_yaml
220
+ #
221
+ # An +ArgumentError+ is raised if anything other than +:string+,
222
+ # +:inspect+, +:yaml+ is passed to this method.
223
+ #
224
+ def format_as( f )
225
+ f = f.intern if f.instance_of? String
226
+
227
+ unless [:string, :inspect, :yaml].include? f
228
+ raise ArgumentError, "unknown object format '#{f}'"
229
+ end
230
+
231
+ module_eval "OBJ_FORMAT = :#{f}", __FILE__, __LINE__
232
+ end
233
+
234
+ # call-seq:
235
+ # Logging.backtrace #=> true or false
236
+ # Logging.backtrace( value ) #=> true or false
237
+ #
238
+ # Without any arguments, returns the global exception backtrace logging
239
+ # value. When set to +true+ backtraces will be written to the logs; when
240
+ # set to +false+ backtraces will be suppressed.
241
+ #
242
+ # When an argument is given the global exception backtrace setting will
243
+ # be changed. Value values are <tt>"on"</tt>, <tt>:on<tt> and +true+ to
244
+ # turn on backtraces and <tt>"off"</tt>, <tt>:off</tt> and +false+ to
245
+ # turn off backtraces.
246
+ #
247
+ def backtrace( b = nil )
248
+ @backtrace = true unless defined? @backtrace
249
+ return @backtrace if b.nil?
250
+
251
+ @backtrace = case b
252
+ when :on, 'on', true; true
253
+ when :off, 'off', false; false
254
+ else
255
+ raise ArgumentError, "backtrace must be true or false"
256
+ end
257
+ end
258
+
259
+ # Returns the version string for the library.
260
+ #
261
+ def version
262
+ VERSION
263
+ end
264
+
265
+ # Returns the library path for the module. If any arguments are given,
266
+ # they will be joined to the end of the libray path using
267
+ # <tt>File.join</tt>.
268
+ #
269
+ def libpath( *args )
270
+ args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
271
+ end
272
+
273
+ # Returns the lpath for the module. If any arguments are given,
274
+ # they will be joined to the end of the path using
275
+ # <tt>File.join</tt>.
276
+ #
277
+ def path( *args )
278
+ args.empty? ? PATH : ::File.join(PATH, args.flatten)
279
+ end
280
+
281
+ # Utility method used to rquire all files ending in .rb that lie in the
282
+ # directory below this file that has the same name as the filename passed
283
+ # in. Optionally, a specific _directory_ name can be passed in such that
284
+ # the _filename_ does not have to be equivalent to the directory.
285
+ #
286
+ def require_all_libs_relative_to( fname, dir = nil )
287
+ dir ||= ::File.basename(fname, '.*')
288
+ search_me = ::File.expand_path(
289
+ ::File.join(::File.dirname(fname), dir, '*.rb'))
290
+
291
+ Dir.glob(search_me).sort.each {|rb| require rb}
292
+ end
293
+
294
+ # call-seq:
295
+ # show_configuration( io = STDOUT, logger = 'root' )
296
+ #
297
+ # This method is used to show the configuration of the logging
298
+ # framework. The information is written to the given _io_ stream
299
+ # (defaulting to stdout). Normally the configuration is dumped starting
300
+ # with the root logger, but any logger name can be given.
301
+ #
302
+ # Each line contains information for a single logger and it's appenders.
303
+ # A child logger is indented two spaces from it's parent logger. Each
304
+ # line contains the logger name, level, additivity, and trace settings.
305
+ # Here is a brief example:
306
+ #
307
+ # root ........................... *info -T
308
+ # LoggerA ...................... info +A -T
309
+ # LoggerA::LoggerB ........... info +A -T
310
+ # LoggerA::LoggerC ........... *debug +A -T
311
+ # LoggerD ...................... *warn -A +T
312
+ #
313
+ # The lines can be deciphered as follows:
314
+ #
315
+ # 1) name - the name of the logger
316
+ #
317
+ # 2) level - the logger level; if it is preceeded by an
318
+ # asterisk then the level was explicitly set for that
319
+ # logger (as opposed to being inherited from the parent
320
+ # logger)
321
+ #
322
+ # 3) additivity - a "+A" shows the logger is additive, and log events
323
+ # will be passed up to the parent logger; "-A" shows
324
+ # that the logger will *not* pass log events up to the
325
+ # parent logger
326
+ #
327
+ # 4) trace - a "+T" shows that the logger will include trace
328
+ # information in generated log events (this includes
329
+ # filename and line number of the log message; "-T"
330
+ # shows that the logger does not include trace
331
+ # information in the log events)
332
+ #
333
+ # If a logger has appenders then they are listed, on per line,
334
+ # immediately below the logger. Appender lines are pre-pended with a
335
+ # single dash:
336
+ #
337
+ # root ........................... *info -T
338
+ # - <Appenders::Stdout:0x8b02a4 name="stdout">
339
+ # LoggerA ...................... info +A -T
340
+ # LoggerA::LoggerB ........... info +A -T
341
+ # LoggerA::LoggerC ........... *debug +A -T
342
+ # LoggerD ...................... *warn -A +T
343
+ # - <Appenders::Stderr:0x8b04ca name="stderr">
344
+ #
345
+ # We can see in this configuration dump that all the loggers will append
346
+ # to stdout via the Stdout appender configured in the root logger. All
347
+ # the loggers are additive, and so their generated log events will be
348
+ # passed up to the root logger.
349
+ #
350
+ # The exception in this configuration is LoggerD. Its additivity is set
351
+ # to false. It uses its own appender to send messages to stderr.
352
+ #
353
+ def show_configuration( io = STDOUT, logger = 'root', indent = 0 )
354
+ logger = ::Logging::Logger[logger] unless ::Logging::Logger === logger
355
+
356
+ logger._dump_configuration(io, indent)
357
+
358
+ indent += 2
359
+ children = ::Logging::Repository.instance.children(logger.name)
360
+ children.sort {|a,b| a.name <=> b.name}.each do |child|
361
+ ::Logging.show_configuration(io, child, indent)
362
+ end
363
+
364
+ nil
365
+ end
366
+
367
+ # :stopdoc:
368
+ # Convert the given level into a connaconical form - a lowercase string.
369
+ def levelify( level )
370
+ case level
371
+ when String; level.downcase
372
+ when Symbol; level.to_s.downcase
373
+ else raise ArgumentError, "levels must be a String or Symbol" end
374
+ end
375
+
376
+ # Convert the given level into a level number.
377
+ def level_num( level )
378
+ l = levelify level
379
+ case l
380
+ when 'all'; 0
381
+ when 'off'; LEVELS.length
382
+ else begin; Integer(l); rescue ArgumentError; LEVELS[l] end end
383
+ end
384
+
385
+ # Internal logging method for use by the framework.
386
+ def log_internal( level = 1, &block )
387
+ ::Logging::Logger[::Logging].__send__(levelify(LNAMES[level]), &block)
388
+ end
389
+ # :startdoc:
390
+ end
391
+ end # module Logging
392
+
393
+ Logging.require_all_libs_relative_to(__FILE__)
394
+ Logging.require_all_libs_relative_to(__FILE__, 'logging/config')
395
+
396
+ # This exit handler will close all the appenders that exist in the system.
397
+ # This is needed for closing IO streams and connections to the syslog server
398
+ # or e-mail servers, etc.
399
+ #
400
+ at_exit {
401
+ Logging::Appender.instance_variable_get(:@appenders).values.each do |ap|
402
+ ap.close
403
+ end
404
+ }
405
+
406
+ end # unless defined?
407
+
408
+ # EOF
@@ -0,0 +1,303 @@
1
+
2
+ module Logging
3
+
4
+ # The +Appender+ class is provides methods for appending log events to a
5
+ # logging destination. The log events are formatted into strings using a
6
+ # Layout.
7
+ #
8
+ # All other Appenders inherit from this class which provides stub methods.
9
+ # Each subclass should provide a +write+ method that will write log
10
+ # messages to the logging destination.
11
+ #
12
+ # A private +sync+ method is provided for use by subclasses. It is used to
13
+ # synchronize writes to the logging destination, and can be used by
14
+ # subclasses to synchronize the closing or flushing of the logging
15
+ # destination.
16
+ #
17
+ class Appender
18
+
19
+ @appenders = Hash.new
20
+
21
+ class << self
22
+
23
+ # call-seq:
24
+ # Appender[name]
25
+ #
26
+ # Returns the appender instance stroed in the Appender hash under the
27
+ # key _name_, or +nil+ if no appender has been created using that name.
28
+ #
29
+ def []( name ) @appenders[name] end
30
+
31
+ # call-seq:
32
+ # Appender[name] = appender
33
+ #
34
+ # Stores the given _appender_ instance in the Appender hash under the
35
+ # key _name_.
36
+ #
37
+ def []=( name, val ) @appenders[name] = val end
38
+
39
+ # call-seq:
40
+ # Appenders.remove( name )
41
+ #
42
+ # Removes the appender instance stored in the Appender hash under the
43
+ # key _name_.
44
+ #
45
+ def remove( name ) @appenders.delete(name) end
46
+
47
+ # call-seq:
48
+ # Appender.stdout
49
+ #
50
+ # Returns an instance of the Stdout Appender. Unless the user explicitly
51
+ # creates a new Stdout Appender, the instance returned by this method
52
+ # will always be the same:
53
+ #
54
+ # Appender.stdout.object_id == Appender.stdout.object_id #=> true
55
+ #
56
+ def stdout( ) self['stdout'] || ::Logging::Appenders::Stdout.new end
57
+
58
+ # call-seq:
59
+ # Appender.stderr
60
+ #
61
+ # Returns an instance of the Stderr Appender. Unless the user explicitly
62
+ # creates a new Stderr Appender, the instance returned by this method
63
+ # will always be the same:
64
+ #
65
+ # Appender.stderr.object_id == Appender.stderr.object_id #=> true
66
+ #
67
+ def stderr( ) self['stderr'] || ::Logging::Appenders::Stderr.new end
68
+
69
+ end # class << self
70
+
71
+ attr_reader :name, :layout, :level
72
+
73
+ # call-seq:
74
+ # Appender.new( name )
75
+ # Appender.new( name, :layout => layout )
76
+ #
77
+ # Creates a new appender using the given name. If no Layout is specified,
78
+ # then a Basic layout will be used. Any logging header supplied by the
79
+ # layout will be written to the logging destination when the Appender is
80
+ # created.
81
+ #
82
+ # Options:
83
+ #
84
+ # :layout => the layout to use when formatting log events
85
+ # :level => the level at which to log
86
+ #
87
+ def initialize( name, opts = {} )
88
+ @name = name.to_s
89
+ @closed = false
90
+
91
+ self.layout = opts.getopt(:layout, ::Logging::Layouts::Basic.new)
92
+ self.level = opts.getopt(:level)
93
+
94
+ @mutex = Mutex.new
95
+ header = @layout.header
96
+
97
+ unless header.nil? || header.empty?
98
+ begin
99
+ sync {write(header)}
100
+ rescue StandardError => err
101
+ ::Logging.log_internal(-2) {err}
102
+ end
103
+ end
104
+
105
+ ::Logging::Appender[@name] = self
106
+ end
107
+
108
+ # call-seq:
109
+ # append( event )
110
+ #
111
+ # Write the given _event_ to the logging destination. The log event will
112
+ # be processed through the Layout associated with the Appender.
113
+ #
114
+ def append( event )
115
+ if @closed
116
+ raise RuntimeError,
117
+ "appender '<#{self.class.name}: #{@name}>' is closed"
118
+ end
119
+
120
+ # only append if the event level is less than or equal to the configured
121
+ # appender level
122
+ unless @level > event.level
123
+ begin
124
+ sync {write(event)}
125
+ rescue StandardError => err
126
+ ::Logging.log_internal(-2) {err}
127
+ end
128
+ end
129
+
130
+ self
131
+ end
132
+
133
+ # call-seq:
134
+ # appender << string
135
+ #
136
+ # Write the given _string_ to the logging destination "as is" -- no
137
+ # layout formatting will be performed.
138
+ #
139
+ def <<( str )
140
+ if @closed
141
+ raise RuntimeError,
142
+ "appender '<#{self.class.name}: #{@name}>' is closed"
143
+ end
144
+
145
+ unless @level >= ::Logging::LEVELS.length
146
+ begin
147
+ sync {write(str)}
148
+ rescue StandardError => err
149
+ ::Logging.log_internal(-2) {err}
150
+ end
151
+ end
152
+ self
153
+ end
154
+
155
+ # call-seq:
156
+ # level = :all
157
+ #
158
+ # Set the level for this appender; log events below this level will be
159
+ # ignored by this appender. The level can be either a +String+, a
160
+ # +Symbol+, or a +Fixnum+. An +ArgumentError+ is raised if this is not
161
+ # the case.
162
+ #
163
+ # There are two special levels -- "all" and "off". The former will
164
+ # enable recording of all log events. The latter will disable the
165
+ # recording of all events.
166
+ #
167
+ # Example:
168
+ #
169
+ # appender.level = :debug
170
+ # appender.level = "INFO"
171
+ # appender.level = 4
172
+ # appender.level = 'off'
173
+ # appender.level = :all
174
+ #
175
+ # These prodcue an +ArgumentError+
176
+ #
177
+ # appender.level = Object
178
+ # appender.level = -1
179
+ # appender.level = 1_000_000_000_000
180
+ #
181
+ def level=( level )
182
+ lvl = case level
183
+ when String, Symbol; ::Logging::level_num(level)
184
+ when Fixnum; level
185
+ when nil; 0
186
+ else
187
+ raise ArgumentError,
188
+ "level must be a String, Symbol, or Integer"
189
+ end
190
+ if lvl.nil? or lvl < 0 or lvl > ::Logging::LEVELS.length
191
+ raise ArgumentError, "unknown level was given '#{level}'"
192
+ end
193
+
194
+ @level = lvl
195
+ end
196
+
197
+ # call-seq
198
+ # appender.layout = Logging::Layouts::Basic.new
199
+ #
200
+ # Sets the layout to be used by this appender.
201
+ #
202
+ def layout=( layout )
203
+ unless layout.kind_of? ::Logging::Layout
204
+ raise TypeError,
205
+ "#{layout.inspect} is not a kind of 'Logging::Layout'"
206
+ end
207
+ @layout = layout
208
+ end
209
+
210
+ # call-seq:
211
+ # close( footer = true )
212
+ #
213
+ # Close the appender and writes the layout footer to the logging
214
+ # destination if the _footer_ flag is set to +true+. Log events will
215
+ # no longer be written to the logging destination after the appender
216
+ # is closed.
217
+ #
218
+ def close( footer = true )
219
+ return self if @closed
220
+ ::Logging::Appender.remove(@name)
221
+ @closed = true
222
+
223
+ sync {flush}
224
+
225
+ if footer
226
+ footer = @layout.footer
227
+ unless footer.nil? || footer.empty?
228
+ begin
229
+ sync {write(footer)}
230
+ rescue StandardError => err
231
+ ::Logging.log_internal(-2) {err}
232
+ end
233
+ end
234
+ end
235
+ self
236
+ end
237
+
238
+ # call-seq:
239
+ # closed?
240
+ #
241
+ # Returns +true+ if the appender has been closed; returns +false+
242
+ # otherwise. When an appender is closed, no more log events can be
243
+ # written to the logging destination.
244
+ #
245
+ def closed?
246
+ @closed
247
+ end
248
+
249
+ # call-seq:
250
+ # flush
251
+ #
252
+ # Call +flush+ to force an appender to write out any buffered log events.
253
+ # Similar to IO#flush, so use in a similar fashion.
254
+ #
255
+ def flush
256
+ self
257
+ end
258
+
259
+ # call-seq:
260
+ # inspect => string
261
+ #
262
+ # Returns a string representation of the appender.
263
+ #
264
+ def inspect
265
+ "<%s:0x%x name=\"%s\">" % [
266
+ self.class.name.sub(%r/^Logging::/, ''),
267
+ self.object_id,
268
+ self.name
269
+ ]
270
+ end
271
+
272
+
273
+ private
274
+
275
+ # call-seq:
276
+ # write( event )
277
+ #
278
+ # Writes the given _event_ to the logging destination. Subclasses should
279
+ # provide an implementation of this method. The _event_ can be either a
280
+ # LogEvent or a String. If a LogEvent, then it will be formatted using
281
+ # the layout given to the appender when it was created.
282
+ #
283
+ def write( event )
284
+ nil
285
+ end
286
+
287
+ # call-seq:
288
+ # sync { block }
289
+ #
290
+ # Obtains an exclusive lock, runs the block, and releases the lock when
291
+ # the block completes. This method is re-entrant so that a single thread
292
+ # can call +sync+ multiple times without hanging the thread.
293
+ #
294
+ def sync
295
+ @mutex.synchronize {yield}
296
+ end
297
+
298
+ end # class Appender
299
+ end # module Logging
300
+
301
+ Logging.require_all_libs_relative_to(__FILE__, 'appenders')
302
+
303
+ # EOF