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
@@ -62,7 +62,7 @@ module Logging::Appenders
62
62
  %w[read readline readlines].each do|m|
63
63
  class_eval <<-CODE, __FILE__, __LINE__+1
64
64
  def #{m}( *args )
65
- sync {
65
+ @mutex.synchronize {
66
66
  begin
67
67
  @sio.seek @pos
68
68
  rv = @sio.#{m}(*args)
@@ -9,7 +9,7 @@ module Logging::Appenders
9
9
  # Accessor / Factory for the Syslog appender.
10
10
  #
11
11
  def self.syslog( *args )
12
- return ::Logging::Appenders::Syslog if args.empty?
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, "unkonwn level '#{level}'"
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
-
@@ -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 === value
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[STACK_NAME]
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[STACK_NAME] = nil
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[STACK_NAME] = [obj.dup]
143
+ Thread.current.thread_variable_set(STACK_NAME, [obj.dup])
144
144
  when Thread
145
145
  return if Thread.current == obj
146
- Thread.exclusive {
147
- if obj[STACK_NAME]
148
- hash = flatten(obj[STACK_NAME])
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[NAME]
163
- return c unless c.nil?
161
+ c = Thread.current.thread_variable_get(NAME)
164
162
 
165
- return Thread.current[NAME] = {} unless Thread.current[STACK_NAME]
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
- Thread.current[NAME] = flatten(stack)
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[STACK_NAME] ||= [{}]
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[NAME] = nil
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 === 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[NAME] = nil
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[NAME] = obj.dup
348
+ Thread.current.thread_variable_set(NAME, obj.dup)
339
349
  when Thread
340
350
  return if Thread.current == obj
341
- Thread.exclusive {
342
- Thread.current[NAME] = obj[NAME].dup if obj[NAME]
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[NAME] ||= Array.new
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
- Thread.exclusive {
385
- Thread.list.each { |thread|
386
- thread[MappedDiagnosticContext::NAME] = nil if thread[MappedDiagnosticContext::NAME]
387
- thread[NestedDiagnosticContext::NAME] = nil if thread[NestedDiagnosticContext::NAME]
388
- thread[MappedDiagnosticContext::STACK_NAME] = nil if thread[MappedDiagnosticContext::STACK_NAME]
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
- end # module Logging
400
-
414
+ DIAGNOSTIC_MUTEX = Mutex.new
415
+ end
401
416
 
402
417
  # :stopdoc:
403
- class Thread
404
- class << self
405
-
406
- %w[new start fork].each do |m|
407
- class_eval <<-__, __FILE__, __LINE__
408
- alias_method :_orig_#{m}, :#{m}
409
- private :_orig_#{m}
410
- def #{m}( *a, &b )
411
- create_with_logging_context(:_orig_#{m}, *a ,&b)
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
- # In order for the diagnostic contexts to behave properly we need to
419
- # inherit state from the parent thread. The only way I have found to do
420
- # this in Ruby is to override `new` and capture the contexts from the
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
- if Thread.current[Logging::MappedDiagnosticContext::STACK_NAME]
435
- mdc = Logging::MappedDiagnosticContext.context.dup
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
- if Thread.current[Logging::NestedDiagnosticContext::NAME]
439
- ndc = Logging::NestedDiagnosticContext.context.dup
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 # Thread
480
+ end
454
481
  # :startdoc:
455
482
 
@@ -41,13 +41,86 @@ class Layout
41
41
  when :inspect, :yaml, :json; f
42
42
  else :string end
43
43
 
44
- b = opts.fetch(:backtrace, ::Logging.backtrace)
45
- @backtrace = case b
46
- when :on, 'on', true; true
47
- when :off, 'off', false; false
48
- else
49
- raise ArgumentError, "backtrace must be true or false"
50
- end
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
- str = "<#{obj.class.name}> #{obj.message}"
88
- if @backtrace && !obj.backtrace.nil?
89
- str << "\n\t" << obj.backtrace.join("\n\t")
90
- end
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 # class Layout
131
- end # module Logging
132
-
259
+ end
260
+ end