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
@@ -9,7 +9,7 @@ module Logging::Appenders
|
|
9
9
|
# Accessor / Factory for the Syslog appender.
|
10
10
|
#
|
11
11
|
def self.syslog( *args )
|
12
|
-
|
12
|
+
fail ArgumentError, '::Logging::Appenders::Syslog needs a name as first argument.' if args.empty?
|
13
13
|
::Logging::Appenders::Syslog.new(*args)
|
14
14
|
end
|
15
15
|
|
@@ -188,7 +188,7 @@ module Logging::Appenders
|
|
188
188
|
end
|
189
189
|
return if message.empty?
|
190
190
|
|
191
|
-
@syslog.log(pri, '%s', message)
|
191
|
+
@mutex.synchronize { @syslog.log(pri, '%s', message) }
|
192
192
|
self
|
193
193
|
end
|
194
194
|
|
@@ -205,11 +205,10 @@ module Logging::Appenders
|
|
205
205
|
level = level.to_s.upcase
|
206
206
|
self.class.const_get level
|
207
207
|
else
|
208
|
-
raise ArgumentError, "
|
208
|
+
raise ArgumentError, "unknown level '#{level}'"
|
209
209
|
end
|
210
210
|
end
|
211
211
|
|
212
212
|
end # Syslog
|
213
213
|
end # Logging::Appenders
|
214
214
|
end # HAVE_SYSLOG
|
215
|
-
|
data/lib/logging/color_scheme.rb
CHANGED
@@ -40,7 +40,7 @@ module Logging
|
|
40
40
|
# Store a color scheme by name.
|
41
41
|
#
|
42
42
|
def []=( name, value )
|
43
|
-
raise ArgumentError, "Silly! That's not a ColorSchmeme!" unless ColorScheme
|
43
|
+
raise ArgumentError, "Silly! That's not a ColorSchmeme!" unless value.is_a?(ColorScheme)
|
44
44
|
@color_schemes[name.to_s] = value
|
45
45
|
end
|
46
46
|
|
@@ -110,7 +110,7 @@ module Logging
|
|
110
110
|
# Returns nil or the Hash removed from the stack.
|
111
111
|
#
|
112
112
|
def pop
|
113
|
-
return unless Thread.current
|
113
|
+
return unless Thread.current.thread_variable_get(STACK_NAME)
|
114
114
|
return unless stack.length > 1
|
115
115
|
clear_context
|
116
116
|
stack.pop
|
@@ -125,7 +125,7 @@ module Logging
|
|
125
125
|
#
|
126
126
|
def clear
|
127
127
|
clear_context
|
128
|
-
Thread.current
|
128
|
+
Thread.current.thread_variable_set(STACK_NAME, nil)
|
129
129
|
self
|
130
130
|
end
|
131
131
|
|
@@ -140,15 +140,14 @@ module Logging
|
|
140
140
|
def inherit( obj )
|
141
141
|
case obj
|
142
142
|
when Hash
|
143
|
-
Thread.current
|
143
|
+
Thread.current.thread_variable_set(STACK_NAME, [obj.dup])
|
144
144
|
when Thread
|
145
145
|
return if Thread.current == obj
|
146
|
-
|
147
|
-
if obj
|
148
|
-
|
149
|
-
Thread.current[STACK_NAME] = [hash]
|
146
|
+
DIAGNOSTIC_MUTEX.synchronize do
|
147
|
+
if hash = obj.thread_variable_get(STACK_NAME)
|
148
|
+
Thread.current.thread_variable_set(STACK_NAME, [flatten(hash)])
|
150
149
|
end
|
151
|
-
|
150
|
+
end
|
152
151
|
end
|
153
152
|
|
154
153
|
self
|
@@ -159,12 +158,18 @@ module Logging
|
|
159
158
|
# application.
|
160
159
|
#
|
161
160
|
def context
|
162
|
-
c = Thread.current
|
163
|
-
return c unless c.nil?
|
161
|
+
c = Thread.current.thread_variable_get(NAME)
|
164
162
|
|
165
|
-
|
163
|
+
if c.nil?
|
164
|
+
c = if Thread.current.thread_variable_get(STACK_NAME)
|
165
|
+
flatten(stack)
|
166
|
+
else
|
167
|
+
Hash.new
|
168
|
+
end
|
169
|
+
Thread.current.thread_variable_set(NAME, c)
|
170
|
+
end
|
166
171
|
|
167
|
-
|
172
|
+
return c
|
168
173
|
end
|
169
174
|
|
170
175
|
# Returns the stack of Hash objects that are storing the diagnostic
|
@@ -172,7 +177,12 @@ module Logging
|
|
172
177
|
# one Hash.
|
173
178
|
#
|
174
179
|
def stack
|
175
|
-
Thread.current
|
180
|
+
s = Thread.current.thread_variable_get(STACK_NAME)
|
181
|
+
if s.nil?
|
182
|
+
s = [{}]
|
183
|
+
Thread.current.thread_variable_set(STACK_NAME, s)
|
184
|
+
end
|
185
|
+
return s
|
176
186
|
end
|
177
187
|
|
178
188
|
# Returns the most current Hash from the stack of contexts.
|
@@ -184,7 +194,7 @@ module Logging
|
|
184
194
|
# Remove the flattened context.
|
185
195
|
#
|
186
196
|
def clear_context
|
187
|
-
Thread.current
|
197
|
+
Thread.current.thread_variable_set(NAME, nil)
|
188
198
|
self
|
189
199
|
end
|
190
200
|
|
@@ -199,7 +209,7 @@ module Logging
|
|
199
209
|
# Raises an ArgumentError if hash is not a Hash.
|
200
210
|
#
|
201
211
|
def sanitize( hash, target = {} )
|
202
|
-
unless Hash
|
212
|
+
unless hash.is_a?(Hash)
|
203
213
|
raise ArgumentError, "Expecting a Hash but received a #{hash.class.name}"
|
204
214
|
end
|
205
215
|
|
@@ -320,7 +330,7 @@ module Logging
|
|
320
330
|
# Returns the NestedDiagnosticContext.
|
321
331
|
#
|
322
332
|
def clear
|
323
|
-
Thread.current
|
333
|
+
Thread.current.thread_variable_set(NAME, nil)
|
324
334
|
self
|
325
335
|
end
|
326
336
|
|
@@ -335,12 +345,12 @@ module Logging
|
|
335
345
|
def inherit( obj )
|
336
346
|
case obj
|
337
347
|
when Array
|
338
|
-
Thread.current
|
348
|
+
Thread.current.thread_variable_set(NAME, obj.dup)
|
339
349
|
when Thread
|
340
350
|
return if Thread.current == obj
|
341
|
-
|
342
|
-
Thread.current
|
343
|
-
|
351
|
+
DIAGNOSTIC_MUTEX.synchronize do
|
352
|
+
Thread.current.thread_variable_set(NAME, obj.thread_variable_get(NAME).dup) if obj.thread_variable_get(NAME)
|
353
|
+
end
|
344
354
|
end
|
345
355
|
|
346
356
|
self
|
@@ -351,7 +361,12 @@ module Logging
|
|
351
361
|
# running in the application.
|
352
362
|
#
|
353
363
|
def context
|
354
|
-
Thread.current
|
364
|
+
c = Thread.current.thread_variable_get(NAME)
|
365
|
+
if c.nil?
|
366
|
+
c = Array.new
|
367
|
+
Thread.current.thread_variable_set(NAME, c)
|
368
|
+
end
|
369
|
+
return c
|
355
370
|
end
|
356
371
|
end # NestedDiagnosticContext
|
357
372
|
|
@@ -381,13 +396,13 @@ module Logging
|
|
381
396
|
#
|
382
397
|
def self.clear_diagnostic_contexts( all = false )
|
383
398
|
if all
|
384
|
-
|
385
|
-
Thread.list.each
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
399
|
+
DIAGNOSTIC_MUTEX.synchronize do
|
400
|
+
Thread.list.each do |t|
|
401
|
+
t.thread_variable_set(MappedDiagnosticContext::NAME, nil) if t.thread_variable?(MappedDiagnosticContext::NAME)
|
402
|
+
t.thread_variable_set(NestedDiagnosticContext::NAME, nil) if t.thread_variable?(NestedDiagnosticContext::NAME)
|
403
|
+
t.thread_variable_set(MappedDiagnosticContext::STACK_NAME, nil) if t.thread_variable?(MappedDiagnosticContext::STACK_NAME)
|
404
|
+
end
|
405
|
+
end
|
391
406
|
else
|
392
407
|
MappedDiagnosticContext.clear
|
393
408
|
NestedDiagnosticContext.clear
|
@@ -396,60 +411,72 @@ module Logging
|
|
396
411
|
self
|
397
412
|
end
|
398
413
|
|
399
|
-
|
400
|
-
|
414
|
+
DIAGNOSTIC_MUTEX = Mutex.new
|
415
|
+
end
|
401
416
|
|
402
417
|
# :stopdoc:
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
end
|
413
|
-
__
|
414
|
-
end
|
415
|
-
|
416
|
-
private
|
418
|
+
Logging::INHERIT_CONTEXT =
|
419
|
+
if ENV.key?("LOGGING_INHERIT_CONTEXT")
|
420
|
+
case ENV["LOGGING_INHERIT_CONTEXT"].downcase
|
421
|
+
when 'false', 'no', '0'; false
|
422
|
+
when false, nil; false
|
423
|
+
else true end
|
424
|
+
else
|
425
|
+
true
|
426
|
+
end
|
417
427
|
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
# parent Thread at the time the child Thread is created. The code below does
|
422
|
-
# just this. If there is a more idiomatic way of accomplishing this in Ruby,
|
423
|
-
# please let me know!
|
424
|
-
#
|
425
|
-
# Also, great care is taken in this code to ensure that a reference to the
|
426
|
-
# parent thread does not exist in the binding associated with the block
|
427
|
-
# being executed in the child thread. The same is true for the parent
|
428
|
-
# thread's mdc and ndc. If any of those references end up in the binding,
|
429
|
-
# then they cannot be garbage collected until the child thread exits.
|
430
|
-
#
|
431
|
-
def create_with_logging_context( m, *a, &b )
|
432
|
-
mdc, ndc = nil
|
428
|
+
if Logging::INHERIT_CONTEXT
|
429
|
+
class Thread
|
430
|
+
class << self
|
433
431
|
|
434
|
-
|
435
|
-
|
432
|
+
%w[new start fork].each do |m|
|
433
|
+
class_eval <<-__, __FILE__, __LINE__
|
434
|
+
alias_method :_orig_#{m}, :#{m}
|
435
|
+
private :_orig_#{m}
|
436
|
+
def #{m}( *a, &b )
|
437
|
+
create_with_logging_context(:_orig_#{m}, *a ,&b)
|
438
|
+
end
|
439
|
+
__
|
436
440
|
end
|
437
441
|
|
438
|
-
|
439
|
-
|
442
|
+
private
|
443
|
+
|
444
|
+
# In order for the diagnostic contexts to behave properly we need to
|
445
|
+
# inherit state from the parent thread. The only way I have found to do
|
446
|
+
# this in Ruby is to override `new` and capture the contexts from the
|
447
|
+
# parent Thread at the time the child Thread is created. The code below does
|
448
|
+
# just this. If there is a more idiomatic way of accomplishing this in Ruby,
|
449
|
+
# please let me know!
|
450
|
+
#
|
451
|
+
# Also, great care is taken in this code to ensure that a reference to the
|
452
|
+
# parent thread does not exist in the binding associated with the block
|
453
|
+
# being executed in the child thread. The same is true for the parent
|
454
|
+
# thread's mdc and ndc. If any of those references end up in the binding,
|
455
|
+
# then they cannot be garbage collected until the child thread exits.
|
456
|
+
#
|
457
|
+
def create_with_logging_context( m, *a, &b )
|
458
|
+
mdc, ndc = nil
|
459
|
+
|
460
|
+
if Thread.current.thread_variable_get(Logging::MappedDiagnosticContext::STACK_NAME)
|
461
|
+
mdc = Logging::MappedDiagnosticContext.context.dup
|
462
|
+
end
|
463
|
+
|
464
|
+
if Thread.current.thread_variable_get(Logging::NestedDiagnosticContext::NAME)
|
465
|
+
ndc = Logging::NestedDiagnosticContext.context.dup
|
466
|
+
end
|
467
|
+
|
468
|
+
# This calls the actual `Thread#new` method to create the Thread instance.
|
469
|
+
# If your memory profiling tool says this method is leaking memory, then
|
470
|
+
# you are leaking Thread instances somewhere.
|
471
|
+
self.send(m, *a) { |*args|
|
472
|
+
Logging::MappedDiagnosticContext.inherit(mdc)
|
473
|
+
Logging::NestedDiagnosticContext.inherit(ndc)
|
474
|
+
b.call(*args)
|
475
|
+
}
|
440
476
|
end
|
441
477
|
|
442
|
-
# This calls the actual `Thread#new` method to create the Thread instance.
|
443
|
-
# If your memory profiling tool says this method is leaking memory, then
|
444
|
-
# you are leaking Thread instances somewhere.
|
445
|
-
self.send(m, *a) { |*args|
|
446
|
-
Logging::MappedDiagnosticContext.inherit(mdc)
|
447
|
-
Logging::NestedDiagnosticContext.inherit(ndc)
|
448
|
-
b.call(*args)
|
449
|
-
}
|
450
478
|
end
|
451
|
-
|
452
479
|
end
|
453
|
-
end
|
480
|
+
end
|
454
481
|
# :startdoc:
|
455
482
|
|
data/lib/logging/layout.rb
CHANGED
@@ -41,13 +41,86 @@ class Layout
|
|
41
41
|
when :inspect, :yaml, :json; f
|
42
42
|
else :string end
|
43
43
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
44
|
+
self.backtrace = opts.fetch(:backtrace, ::Logging.backtrace)
|
45
|
+
self.utc_offset = opts.fetch(:utc_offset, ::Logging.utc_offset)
|
46
|
+
self.cause_depth = opts.fetch(:cause_depth, ::Logging.cause_depth)
|
47
|
+
end
|
48
|
+
|
49
|
+
# call-seq:
|
50
|
+
# layout.backtrace = true
|
51
|
+
#
|
52
|
+
# Set the backtrace flag to the given value. This can be set to `true` or
|
53
|
+
# `false`.
|
54
|
+
#
|
55
|
+
def backtrace=( value )
|
56
|
+
@backtrace = case value
|
57
|
+
when :on, 'on', true; true
|
58
|
+
when :off, 'off', false; false
|
59
|
+
else
|
60
|
+
raise ArgumentError, "backtrace must be `true` or `false`"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Returns the backtrace setting.
|
65
|
+
attr_reader :backtrace
|
66
|
+
alias :backtrace? :backtrace
|
67
|
+
|
68
|
+
# Set the UTC offset used when formatting time values. If left unset, the
|
69
|
+
# default local time zone will be used for time values. This method accepts
|
70
|
+
# the `utc_offset` format supported by the `Time#localtime` method in Ruby.
|
71
|
+
#
|
72
|
+
# Passing "UTC" or `0` as the UTC offset will cause all times to be reported
|
73
|
+
# in the UTC timezone.
|
74
|
+
#
|
75
|
+
# layout.utc_offset = "-07:00" # Mountain Standard Time in North America
|
76
|
+
# layout.utc_offset = "+01:00" # Central European Time
|
77
|
+
# layout.utc_offset = "UTC" # UTC
|
78
|
+
# layout.utc_offset = 0 # UTC
|
79
|
+
#
|
80
|
+
def utc_offset=( value )
|
81
|
+
@utc_offset = case value
|
82
|
+
when nil; nil
|
83
|
+
when "UTC", "GMT", 0; 0
|
84
|
+
else
|
85
|
+
Time.now.localtime(value)
|
86
|
+
value
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Returns the UTC offset.
|
91
|
+
attr_reader :utc_offset
|
92
|
+
|
93
|
+
#
|
94
|
+
#
|
95
|
+
def cause_depth=( value )
|
96
|
+
if value.nil?
|
97
|
+
@cause_depth = ::Logging::DEFAULT_CAUSE_DEPTH
|
98
|
+
else
|
99
|
+
value = Integer(value)
|
100
|
+
@cause_depth = value < 0 ? ::Logging::DEFAULT_CAUSE_DEPTH : value
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Returns the exception cause depth formatting limit.
|
105
|
+
attr_reader :cause_depth
|
106
|
+
|
107
|
+
# Internal: Helper method that applies the UTC offset to the given `time`
|
108
|
+
# instance. A new Time is returned that is equivalent to the original `time`
|
109
|
+
# but pinned to the timezone given by the UTC offset.
|
110
|
+
#
|
111
|
+
# If a UTC offset has not been set, then the original `time` instance is
|
112
|
+
# returned unchanged.
|
113
|
+
#
|
114
|
+
def apply_utc_offset( time )
|
115
|
+
return time if utc_offset.nil?
|
116
|
+
|
117
|
+
time = time.dup
|
118
|
+
if utc_offset == 0
|
119
|
+
time.utc
|
120
|
+
else
|
121
|
+
time.localtime(utc_offset)
|
122
|
+
end
|
123
|
+
time
|
51
124
|
end
|
52
125
|
|
53
126
|
# call-seq:
|
@@ -84,11 +157,10 @@ class Layout
|
|
84
157
|
case obj
|
85
158
|
when String; obj
|
86
159
|
when Exception
|
87
|
-
|
88
|
-
if
|
89
|
-
|
90
|
-
|
91
|
-
str
|
160
|
+
lines = ["<#{obj.class.name}> #{obj.message}"]
|
161
|
+
lines.concat(obj.backtrace) if backtrace? && obj.backtrace
|
162
|
+
format_cause(obj, lines)
|
163
|
+
lines.join("\n\t")
|
92
164
|
when nil; "<#{obj.class.name}> nil"
|
93
165
|
else
|
94
166
|
str = "<#{obj.class.name}> "
|
@@ -101,6 +173,64 @@ class Layout
|
|
101
173
|
end
|
102
174
|
end
|
103
175
|
|
176
|
+
# Internal: Format any nested exceptions found in the given exception `e`
|
177
|
+
# while respecting the maximum `cause_depth`. The lines array is used to
|
178
|
+
# capture all the output lines form the nested exceptions; the array is later
|
179
|
+
# joined by the `format_obj` method.
|
180
|
+
#
|
181
|
+
# e - Exception to format
|
182
|
+
# lines - Array of output lines
|
183
|
+
#
|
184
|
+
# Returns the input `lines` Array
|
185
|
+
def format_cause(e, lines)
|
186
|
+
return lines if cause_depth == 0
|
187
|
+
|
188
|
+
cause_depth.times do
|
189
|
+
break unless e.respond_to?(:cause) && e.cause
|
190
|
+
|
191
|
+
cause = e.cause
|
192
|
+
lines << "--- Caused by ---"
|
193
|
+
lines << "<#{cause.class.name}> #{cause.message}"
|
194
|
+
lines.concat(format_cause_backtrace(e, cause)) if backtrace? && cause.backtrace
|
195
|
+
|
196
|
+
e = cause
|
197
|
+
end
|
198
|
+
|
199
|
+
if e.respond_to?(:cause) && e.cause
|
200
|
+
lines << "--- Further #cause backtraces were omitted ---"
|
201
|
+
end
|
202
|
+
|
203
|
+
lines
|
204
|
+
end
|
205
|
+
|
206
|
+
# Internal: Format the backtrace of the nested `cause` but remove the common
|
207
|
+
# exception lines from the parent exception. This helps keep the backtraces a
|
208
|
+
# wee bit shorter and more comprehensible.
|
209
|
+
#
|
210
|
+
# e - parent exception
|
211
|
+
# cause - the nested exception generating the returned backtrace
|
212
|
+
#
|
213
|
+
# Returns an Array of backtracke lines.
|
214
|
+
def format_cause_backtrace(e, cause)
|
215
|
+
# Find where the cause's backtrace differs from the parent exception's.
|
216
|
+
backtrace = Array(e.backtrace)
|
217
|
+
cause_backtrace = Array(cause.backtrace)
|
218
|
+
index = -1
|
219
|
+
min_index = [backtrace.size, cause_backtrace.size].min * -1
|
220
|
+
just_in_case = -5000
|
221
|
+
|
222
|
+
while index > min_index && backtrace[index] == cause_backtrace[index] && index >= just_in_case
|
223
|
+
index -= 1
|
224
|
+
end
|
225
|
+
|
226
|
+
# Add on a few common frames to make it clear where the backtraces line up.
|
227
|
+
index += 3
|
228
|
+
index = -1 if index >= 0
|
229
|
+
|
230
|
+
cause_backtrace[0..index]
|
231
|
+
end
|
232
|
+
|
233
|
+
|
104
234
|
# Attempt to format the _obj_ using yaml, but fall back to inspect style
|
105
235
|
# formatting if yaml fails.
|
106
236
|
#
|
@@ -126,7 +256,5 @@ class Layout
|
|
126
256
|
rescue StandardError
|
127
257
|
obj.inspect
|
128
258
|
end
|
129
|
-
|
130
|
-
end
|
131
|
-
end # module Logging
|
132
|
-
|
259
|
+
end
|
260
|
+
end
|