logging 2.0.0 → 2.3.0

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