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