logging 2.0.0 → 2.3.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.
Files changed (55) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -0
  3. data/.travis.yml +8 -5
  4. data/History.txt +59 -0
  5. data/LICENSE +22 -0
  6. data/README.md +20 -41
  7. data/Rakefile +2 -2
  8. data/examples/appenders.rb +1 -1
  9. data/examples/layouts.rb +1 -1
  10. data/examples/lazy.rb +1 -1
  11. data/examples/mdc.rb +2 -2
  12. data/examples/rails4.rb +21 -0
  13. data/examples/reusing_layouts.rb +51 -0
  14. data/lib/logging.rb +99 -9
  15. data/lib/logging/appender.rb +13 -34
  16. data/lib/logging/appenders/buffering.rb +130 -59
  17. data/lib/logging/appenders/console.rb +68 -57
  18. data/lib/logging/appenders/file.rb +43 -22
  19. data/lib/logging/appenders/io.rb +22 -16
  20. data/lib/logging/appenders/rolling_file.rb +60 -26
  21. data/lib/logging/appenders/string_io.rb +1 -1
  22. data/lib/logging/appenders/syslog.rb +3 -4
  23. data/lib/logging/color_scheme.rb +1 -1
  24. data/lib/logging/diagnostic_context.rb +100 -73
  25. data/lib/logging/layout.rb +144 -16
  26. data/lib/logging/layouts/parseable.rb +50 -12
  27. data/lib/logging/layouts/pattern.rb +8 -9
  28. data/lib/logging/log_event.rb +19 -12
  29. data/lib/logging/logger.rb +117 -95
  30. data/lib/logging/proxy.rb +1 -1
  31. data/lib/logging/rails_compat.rb +4 -13
  32. data/lib/logging/version.rb +1 -1
  33. data/logging.gemspec +31 -32
  34. data/script/console +8 -0
  35. data/test/appenders/{test_periodic_flushing.rb → test_async_flushing.rb} +67 -14
  36. data/test/appenders/test_buffered_io.rb +19 -18
  37. data/test/appenders/test_console.rb +55 -12
  38. data/test/appenders/test_file.rb +48 -28
  39. data/test/appenders/test_rolling_file.rb +18 -12
  40. data/test/appenders/test_syslog.rb +6 -0
  41. data/test/benchmark.rb +42 -18
  42. data/test/layouts/test_json.rb +14 -1
  43. data/test/layouts/test_nested_exceptions.rb +124 -0
  44. data/test/layouts/test_pattern.rb +16 -3
  45. data/test/layouts/test_yaml.rb +15 -1
  46. data/test/performance.rb +66 -0
  47. data/test/setup.rb +26 -30
  48. data/test/test_appender.rb +2 -4
  49. data/test/test_layout.rb +49 -0
  50. data/test/test_log_event.rb +10 -2
  51. data/test/test_logger.rb +20 -3
  52. data/test/test_logging.rb +75 -4
  53. data/test/test_mapped_diagnostic_context.rb +15 -6
  54. data/test/test_nested_diagnostic_context.rb +6 -1
  55. metadata +23 -17
@@ -9,11 +9,6 @@ module Logging
9
9
  # Each subclass should provide a +write+ method that will write log
10
10
  # messages to the logging destination.
11
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
12
  class Appender
18
13
 
19
14
  attr_reader :name, :layout, :level, :filters
@@ -114,7 +109,7 @@ class Appender
114
109
  #
115
110
  # Set the level for this appender; log events below this level will be
116
111
  # ignored by this appender. The level can be either a +String+, a
117
- # +Symbol+, or a +Fixnum+. An +ArgumentError+ is raised if this is not
112
+ # +Symbol+, or an +Integer+. An +ArgumentError+ is raised if this is not
118
113
  # the case.
119
114
  #
120
115
  # There are two special levels -- "all" and "off". The former will
@@ -138,7 +133,7 @@ class Appender
138
133
  def level=( level )
139
134
  lvl = case level
140
135
  when String, Symbol; ::Logging::level_num(level)
141
- when Fixnum; level
136
+ when Integer; level
142
137
  when nil; 0
143
138
  else
144
139
  raise ArgumentError,
@@ -251,26 +246,21 @@ class Appender
251
246
  self
252
247
  end
253
248
 
249
+ # Save off the original `to_s` for use in tests
250
+ alias_method :_to_s, :to_s
251
+
254
252
  # call-seq:
255
- # inspect => string
253
+ # to_s => string
256
254
  #
257
255
  # Returns a string representation of the appender.
258
256
  #
259
- def inspect
260
- "<%s:0x%x name=\"%s\">" % [
261
- self.class.name.sub(%r/^Logging::/, ''),
262
- self.object_id,
263
- self.name
264
- ]
257
+ def to_s
258
+ "<%s name=\"%s\">" % [self.class.name.sub(%r/^Logging::/, ''), self.name]
265
259
  end
266
260
 
267
- # Returns the current Encoding for the appender or nil if an encoding has
268
- # not been set.
269
- #
270
- def encoding
271
- return @encoding if defined? @encoding
272
- @encoding = Object.const_defined?(:Encoding) ? Encoding.default_external : nil
273
- end
261
+ # Returns the current Encoding for the appender. The default external econding
262
+ # will be used if none is explicitly set.
263
+ attr_reader :encoding
274
264
 
275
265
  # Set the appender encoding to the given value. The value can either be an
276
266
  # Encoding instance or a String or Symbol referring to a valid encoding.
@@ -283,9 +273,9 @@ class Appender
283
273
  # Raises ArgumentError if the value is not a valid encoding.
284
274
  def encoding=( value )
285
275
  if value.nil?
286
- @encoding = nil
276
+ @encoding = Encoding.default_external
287
277
  else
288
- @encoding = Object.const_defined?(:Encoding) ? Encoding.find(value.to_s) : nil
278
+ @encoding = Encoding.find(value.to_s)
289
279
  end
290
280
  end
291
281
 
@@ -329,17 +319,6 @@ private
329
319
  nil
330
320
  end
331
321
 
332
- # call-seq:
333
- # sync { block }
334
- #
335
- # Obtains an exclusive lock, runs the block, and releases the lock when
336
- # the block completes. This method is re-entrant so that a single thread
337
- # can call +sync+ multiple times without hanging the thread.
338
- #
339
- def sync( &block )
340
- @mutex.synchronize(&block)
341
- end
342
-
343
322
  end # class Appender
344
323
  end # module Logging
345
324
 
@@ -27,6 +27,10 @@ module Logging::Appenders
27
27
  # flush_period.
28
28
  attr_reader :flush_period
29
29
 
30
+ # When set, the buffer will be flushed using an asynchronous Thread. That
31
+ # is, the main program thread will not be blocked during writes.
32
+ attr_reader :async
33
+
30
34
  # Messages will be written in chunks. This controls the number of messages
31
35
  # to pull from the buffer for each write operation. The default is to pull
32
36
  # all messages from the buffer at once.
@@ -39,20 +43,21 @@ module Logging::Appenders
39
43
  @buffer = []
40
44
  @immediate = []
41
45
  @auto_flushing = 1
42
- @flush_period = @periodic_flusher = nil
46
+ @async = false
47
+ @flush_period = @async_flusher = nil
43
48
 
44
49
  super(*args, &block)
45
50
  end
46
51
 
47
- # Close the message buffer by flushing all log events to the appender. If a
48
- # periodic flusher thread is running, shut it down and allow it to exit.
52
+ # Close the message buffer by flushing all log events to the appender. If an
53
+ # async flusher thread is running, shut it down and allow it to exit.
49
54
  #
50
55
  def close( *args )
51
56
  flush
52
57
 
53
- if @periodic_flusher
54
- @periodic_flusher.stop
55
- @periodic_flusher = nil
58
+ if @async_flusher
59
+ @async_flusher.stop
60
+ @async_flusher = nil
56
61
  Thread.pass
57
62
  end
58
63
 
@@ -60,11 +65,11 @@ module Logging::Appenders
60
65
  end
61
66
 
62
67
  # Reopen the connection to the underlying logging destination. In addition
63
- # if the appender is configured for periodic flushing, then the flushing
68
+ # if the appender is configured for asynchronous flushing, then the flushing
64
69
  # thread will be stopped and restarted.
65
70
  #
66
71
  def reopen
67
- _setup_periodic_flusher
72
+ _setup_async_flusher
68
73
  super
69
74
  end
70
75
 
@@ -74,7 +79,7 @@ module Logging::Appenders
74
79
  return self if @buffer.empty?
75
80
 
76
81
  ary = nil
77
- sync {
82
+ @mutex.synchronize {
78
83
  ary = @buffer.dup
79
84
  @buffer.clear
80
85
  }
@@ -95,7 +100,7 @@ module Logging::Appenders
95
100
  # Clear the underlying buffer of all log events. These events will not be
96
101
  # appended to the logging destination; they will be lost.
97
102
  def clear!
98
- sync { @buffer.clear }
103
+ @mutex.synchronize { @buffer.clear }
99
104
  end
100
105
 
101
106
  # Configure the levels that will trigger an immediate flush of the
@@ -194,21 +199,43 @@ module Logging::Appenders
194
199
  # manually if so desired.
195
200
  #
196
201
  def flush_period=( period )
197
- period =
202
+ @flush_period =
198
203
  case period
199
204
  when Integer, Float, nil; period
200
- when String;
205
+ when String
201
206
  num = _parse_hours_minutes_seconds(period) || _parse_numeric(period)
202
- num = ArgumentError.new("unrecognized flush period: #{period.inspect}") if num.nil?
207
+ raise ArgumentError.new("unrecognized flush period: #{period.inspect}") if num.nil?
203
208
  num
204
- else ArgumentError.new("unrecognized flush period: #{period.inspect}") end
209
+ else
210
+ raise ArgumentError.new("unrecognized flush period: #{period.inspect}")
211
+ end
205
212
 
206
- raise period if Exception === period
207
- @flush_period = period
213
+ if !@flush_period.nil? && @flush_period <= 0
214
+ raise ArgumentError,
215
+ "flush_period must be greater than zero: #{period.inspect}"
216
+ end
217
+
218
+ _setup_async_flusher
219
+ end
220
+
221
+ # Returns `true` if an asynchronous flush period has been defined for the
222
+ # appender.
223
+ def flush_period?
224
+ !@flush_period.nil?
225
+ end
208
226
 
209
- _setup_periodic_flusher
227
+ # Enable or disable asynchronous logging via a dedicated logging Thread.
228
+ # Pass in `true` to enable and `false` to disable.
229
+ #
230
+ # bool - A boolean value
231
+ #
232
+ def async=( bool )
233
+ @async = bool ? true : false
234
+ _setup_async_flusher
210
235
  end
211
236
 
237
+ alias_method :async?, :async
238
+
212
239
  protected
213
240
 
214
241
  # Configure the buffering using the arguments found in the give options
@@ -223,12 +250,12 @@ module Logging::Appenders
223
250
  self.immediate_at = opts.fetch(:immediate_at, '')
224
251
  self.auto_flushing = opts.fetch(:auto_flushing, true)
225
252
  self.flush_period = opts.fetch(:flush_period, nil)
253
+ self.async = opts.fetch(:async, false)
226
254
  self.write_size = opts.fetch(:write_size, DEFAULT_BUFFER_SIZE)
227
255
  end
228
256
 
229
- # Returns true if the _event_ level matches one of the configured
230
- # immediate logging levels. Otherwise returns false.
231
- #
257
+ # Returns `true` if the `event` level matches one of the configured
258
+ # immediate logging levels. Otherwise returns `false`.
232
259
  def immediate?( event )
233
260
  return false unless event.respond_to? :level
234
261
  @immediate[event.level]
@@ -240,14 +267,15 @@ module Logging::Appenders
240
267
  # call-seq:
241
268
  # write( event )
242
269
  #
243
- # Writes the given _event_ to the logging destination. The _event_ can
270
+ # Writes the given `event` to the logging destination. The `event` can
244
271
  # be either a LogEvent or a String. If a LogEvent, then it will be
245
272
  # formatted using the layout given to the appender when it was created.
246
273
  #
247
- # The _event_ will be formatted and then buffered until the
274
+ # The `event` will be formatted and then buffered until the
248
275
  # "auto_flushing" level has been reached. At this time the canonical_write
249
276
  # method will be used to log all events stored in the buffer.
250
277
  #
278
+ # Returns this appender instance
251
279
  def write( event )
252
280
  str = event.instance_of?(::Logging::LogEvent) ?
253
281
  layout.format(event) : event.to_s
@@ -256,12 +284,21 @@ module Logging::Appenders
256
284
  if @auto_flushing == 1
257
285
  canonical_write(str)
258
286
  else
259
- sync {
260
- str = str.force_encoding(encoding) if encoding && str.encoding != encoding
287
+ str = str.force_encoding(encoding) if encoding && str.encoding != encoding
288
+ @mutex.synchronize {
261
289
  @buffer << str
262
290
  }
263
- @periodic_flusher.signal if @periodic_flusher
264
- flush if @buffer.length >= @auto_flushing || immediate?(event)
291
+ flush_now = @buffer.length >= @auto_flushing || immediate?(event)
292
+
293
+ if flush_now
294
+ if async?
295
+ @async_flusher.signal(flush_now)
296
+ else
297
+ self.flush
298
+ end
299
+ elsif @async_flusher && flush_period?
300
+ @async_flusher.signal
301
+ end
265
302
  end
266
303
 
267
304
  self
@@ -270,9 +307,14 @@ module Logging::Appenders
270
307
  # Attempt to parse an hours/minutes/seconds value from the string and return
271
308
  # an integer number of seconds.
272
309
  #
310
+ # str - The input String to parse for time values.
311
+ #
312
+ # Examples
313
+ #
273
314
  # _parse_hours_minutes_seconds("14:12:42") #=> 51162
274
315
  # _parse_hours_minutes_seconds("foo") #=> nil
275
316
  #
317
+ # Returns a Numeric or `nil`
276
318
  def _parse_hours_minutes_seconds( str )
277
319
  m = %r/^\s*(\d{2,}):(\d{2}):(\d{2}(?:\.\d+)?)\s*$/.match(str)
278
320
  return if m.nil?
@@ -281,49 +323,60 @@ module Logging::Appenders
281
323
  end
282
324
 
283
325
  # Convert the string into a numeric value. If the string does not
284
- # represent a valid Integer or Float then +nil+ is returned.
326
+ # represent a valid Integer or Float then `nil` is returned.
327
+ #
328
+ # str - The input String to parse for Numeric values.
329
+ #
330
+ # Examples
285
331
  #
286
332
  # _parse_numeric("10") #=> 10
287
333
  # _parse_numeric("1.0") #=> 1.0
288
334
  # _parse_numeric("foo") #=> nil
289
335
  #
336
+ # Returns a Numeric or `nil`
290
337
  def _parse_numeric( str )
291
338
  Integer(str) rescue (Float(str) rescue nil)
292
339
  end
293
340
 
294
- # Using the flush_period, create a new PeriodicFlusher attached to this
341
+ # Using the flush_period, create a new AsyncFlusher attached to this
295
342
  # appender. If the flush_period is nil, then no action will be taken. If a
296
- # PeriodicFlusher already exists, it will be stopped and a new one will be
343
+ # AsyncFlusher already exists, it will be stopped and a new one will be
297
344
  # created.
298
345
  #
299
- def _setup_periodic_flusher
300
- # stop and remove any existing periodic flusher instance
301
- if @periodic_flusher
302
- @periodic_flusher.stop
303
- @periodic_flusher = nil
346
+ # Returns `nil`
347
+ def _setup_async_flusher
348
+ # stop and remove any existing async flusher instance
349
+ if @async_flusher
350
+ @async_flusher.stop
351
+ @async_flusher = nil
304
352
  Thread.pass
305
353
  end
306
354
 
307
- # create a new periodic flusher if we have a valid flush period
308
- if @flush_period
355
+ # create a new async flusher if we have a valid flush period
356
+ if @flush_period || async?
309
357
  @auto_flushing = DEFAULT_BUFFER_SIZE unless @auto_flushing > 1
310
- @periodic_flusher = PeriodicFlusher.new(self, @flush_period)
311
- @periodic_flusher.start
358
+ @async_flusher = AsyncFlusher.new(self, @flush_period)
359
+ @async_flusher.start
360
+ Thread.pass
312
361
  end
362
+
363
+ nil
313
364
  end
314
365
 
315
366
  # :stopdoc:
316
367
 
317
- # The PeriodicFlusher contains an internal run loop that will periodically
368
+ # The AsyncFlusher contains an internal run loop that will periodically
318
369
  # wake up and flush any log events contained in the message buffer of the
319
- # owning appender instance. The PeriodicFlusher relies on a _signal_ from
370
+ # owning appender instance. The AsyncFlusher relies on a `signal` from
320
371
  # the appender in order to wakeup and perform the flush on the appender.
321
- #
322
- class PeriodicFlusher
372
+ class AsyncFlusher
323
373
 
324
- # Create a new PeriodicFlusher instance that will call the +flush+
325
- # method on the given _appender_. The +flush+ method will be called
326
- # every _period_ seconds, but only when the message buffer is non-empty.
374
+ # Create a new AsyncFlusher instance that will call the `flush`
375
+ # method on the given `appender`. The `flush` method will be called
376
+ # every `period` seconds, but only when the message buffer is non-empty.
377
+ #
378
+ # appender - The Appender instance to periodically `flush`
379
+ # period - The Numeric sleep period or `nil`
327
380
  #
328
381
  def initialize( appender, period )
329
382
  @appender = appender
@@ -334,10 +387,12 @@ module Logging::Appenders
334
387
  @thread = nil
335
388
  @waiting = nil
336
389
  @signaled = false
390
+ @immediate = 0
337
391
  end
338
392
 
339
393
  # Start the periodic flusher's internal run loop.
340
394
  #
395
+ # Returns this flusher instance
341
396
  def start
342
397
  return if @thread
343
398
 
@@ -345,57 +400,75 @@ module Logging::Appenders
345
400
  begin
346
401
  break if Thread.current[:stop]
347
402
  _wait_for_signal
348
- sleep @period unless Thread.current[:stop]
403
+ _try_to_sleep
349
404
  @appender.flush
350
405
  rescue => err
351
- ::Logging.log_internal {"PeriodicFlusher for appender #{@appender.inspect} encountered an error"}
406
+ ::Logging.log_internal {"AsyncFlusher for appender #{@appender.inspect} encountered an error"}
352
407
  ::Logging.log_internal_error(err)
353
408
  end
354
- }; @thread = nil }
409
+ }}
355
410
 
356
411
  self
357
412
  end
358
413
 
359
- # Stop the periodic flusher's internal run loop.
414
+ # Stop the async flusher's internal run loop.
360
415
  #
416
+ # Returns this flusher instance
361
417
  def stop
362
418
  return if @thread.nil?
363
419
  @thread[:stop] = true
364
420
  signal if waiting?
421
+ @thread = nil
365
422
  self
366
423
  end
367
424
 
368
- # Signal the periodic flusher. This will wake up the run loop if it is
425
+ # Signal the async flusher. This will wake up the run loop if it is
369
426
  # currently waiting for something to do. If the signal method is never
370
- # called, the periodic flusher will never perform the flush action on
427
+ # called, the async flusher will never perform the flush action on
371
428
  # the appender.
372
429
  #
373
- def signal
430
+ # immediate - Set to `true` if the sleep period should be skipped
431
+ #
432
+ # Returns this flusher instance
433
+ def signal( immediate = nil )
374
434
  return if Thread.current == @thread # don't signal ourselves
375
435
  return if @signaled # don't need to signal again
376
436
 
377
437
  @mutex.synchronize {
378
438
  @signaled = true
439
+ @immediate += 1 if immediate
379
440
  @cv.signal
380
441
  }
381
442
  self
382
443
  end
383
444
 
384
- # Returns +true+ if the flusher is waiting for a signal. Returns +false+
445
+ # Returns `true` if the flusher is waiting for a signal. Returns `false`
385
446
  # if the flusher is somewhere in the processing loop.
386
- #
387
447
  def waiting?
388
448
  @waiting
389
449
  end
390
450
 
451
+ # Returns `true` if the flusher should immeidately write the buffer to the
452
+ # IO destination.
453
+ def immediate?
454
+ @immediate > 0
455
+ end
456
+
391
457
  private
392
458
 
459
+ def _try_to_sleep
460
+ return if Thread.current[:stop]
461
+ return if immediate?
462
+ sleep @period unless @period.nil?
463
+ end
464
+
393
465
  def _wait_for_signal
394
466
  @mutex.synchronize {
395
467
  begin
396
468
  # wait on the condition variable only if we have NOT been signaled
397
469
  unless @signaled
398
470
  @waiting = true
471
+ @immediate -= 1 if immediate?
399
472
  @cv.wait(@mutex)
400
473
  @waiting = false
401
474
  end
@@ -406,9 +479,7 @@ module Logging::Appenders
406
479
  ensure
407
480
  @waiting = false
408
481
  end
409
- end # class PeriodicFlusher
482
+ end
410
483
  # :startdoc:
411
-
412
- end # Buffering
413
- end # Logging::Appenders
414
-
484
+ end
485
+ end