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.
- checksums.yaml +5 -5
- data/.gitignore +1 -0
- data/.travis.yml +8 -5
- data/History.txt +59 -0
- data/LICENSE +22 -0
- data/README.md +20 -41
- data/Rakefile +2 -2
- data/examples/appenders.rb +1 -1
- data/examples/layouts.rb +1 -1
- data/examples/lazy.rb +1 -1
- data/examples/mdc.rb +2 -2
- data/examples/rails4.rb +21 -0
- data/examples/reusing_layouts.rb +51 -0
- data/lib/logging.rb +99 -9
- data/lib/logging/appender.rb +13 -34
- data/lib/logging/appenders/buffering.rb +130 -59
- data/lib/logging/appenders/console.rb +68 -57
- data/lib/logging/appenders/file.rb +43 -22
- data/lib/logging/appenders/io.rb +22 -16
- data/lib/logging/appenders/rolling_file.rb +60 -26
- data/lib/logging/appenders/string_io.rb +1 -1
- data/lib/logging/appenders/syslog.rb +3 -4
- data/lib/logging/color_scheme.rb +1 -1
- data/lib/logging/diagnostic_context.rb +100 -73
- data/lib/logging/layout.rb +144 -16
- data/lib/logging/layouts/parseable.rb +50 -12
- data/lib/logging/layouts/pattern.rb +8 -9
- data/lib/logging/log_event.rb +19 -12
- data/lib/logging/logger.rb +117 -95
- data/lib/logging/proxy.rb +1 -1
- data/lib/logging/rails_compat.rb +4 -13
- data/lib/logging/version.rb +1 -1
- data/logging.gemspec +31 -32
- data/script/console +8 -0
- data/test/appenders/{test_periodic_flushing.rb → test_async_flushing.rb} +67 -14
- data/test/appenders/test_buffered_io.rb +19 -18
- data/test/appenders/test_console.rb +55 -12
- data/test/appenders/test_file.rb +48 -28
- data/test/appenders/test_rolling_file.rb +18 -12
- data/test/appenders/test_syslog.rb +6 -0
- data/test/benchmark.rb +42 -18
- data/test/layouts/test_json.rb +14 -1
- data/test/layouts/test_nested_exceptions.rb +124 -0
- data/test/layouts/test_pattern.rb +16 -3
- data/test/layouts/test_yaml.rb +15 -1
- data/test/performance.rb +66 -0
- data/test/setup.rb +26 -30
- data/test/test_appender.rb +2 -4
- data/test/test_layout.rb +49 -0
- data/test/test_log_event.rb +10 -2
- data/test/test_logger.rb +20 -3
- data/test/test_logging.rb +75 -4
- data/test/test_mapped_diagnostic_context.rb +15 -6
- data/test/test_nested_diagnostic_context.rb +6 -1
- metadata +23 -17
data/lib/logging/appender.rb
CHANGED
@@ -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
|
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
|
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
|
-
#
|
253
|
+
# to_s => string
|
256
254
|
#
|
257
255
|
# Returns a string representation of the appender.
|
258
256
|
#
|
259
|
-
def
|
260
|
-
"<%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
|
268
|
-
#
|
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 =
|
276
|
+
@encoding = Encoding.default_external
|
287
277
|
else
|
288
|
-
@encoding =
|
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
|
-
@
|
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
|
48
|
-
#
|
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 @
|
54
|
-
@
|
55
|
-
@
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
207
|
+
raise ArgumentError.new("unrecognized flush period: #{period.inspect}") if num.nil?
|
203
208
|
num
|
204
|
-
else
|
209
|
+
else
|
210
|
+
raise ArgumentError.new("unrecognized flush period: #{period.inspect}")
|
211
|
+
end
|
205
212
|
|
206
|
-
|
207
|
-
|
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
|
-
|
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
|
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
|
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
|
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
|
-
|
260
|
-
|
287
|
+
str = str.force_encoding(encoding) if encoding && str.encoding != encoding
|
288
|
+
@mutex.synchronize {
|
261
289
|
@buffer << str
|
262
290
|
}
|
263
|
-
@
|
264
|
-
|
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
|
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
|
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
|
-
#
|
343
|
+
# AsyncFlusher already exists, it will be stopped and a new one will be
|
297
344
|
# created.
|
298
345
|
#
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
@
|
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
|
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
|
-
@
|
311
|
-
@
|
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
|
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
|
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
|
325
|
-
# method on the given
|
326
|
-
# every
|
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
|
-
|
403
|
+
_try_to_sleep
|
349
404
|
@appender.flush
|
350
405
|
rescue => err
|
351
|
-
::Logging.log_internal {"
|
406
|
+
::Logging.log_internal {"AsyncFlusher for appender #{@appender.inspect} encountered an error"}
|
352
407
|
::Logging.log_internal_error(err)
|
353
408
|
end
|
354
|
-
}
|
409
|
+
}}
|
355
410
|
|
356
411
|
self
|
357
412
|
end
|
358
413
|
|
359
|
-
# Stop the
|
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
|
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
|
427
|
+
# called, the async flusher will never perform the flush action on
|
371
428
|
# the appender.
|
372
429
|
#
|
373
|
-
|
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
|
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
|
482
|
+
end
|
410
483
|
# :startdoc:
|
411
|
-
|
412
|
-
|
413
|
-
end # Logging::Appenders
|
414
|
-
|
484
|
+
end
|
485
|
+
end
|