logging 1.7.2 → 1.8.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.
data/.gitignore CHANGED
@@ -10,3 +10,4 @@ doc/
10
10
  pkg/
11
11
  tmp/
12
12
  .rbx
13
+ .rvmrc
@@ -1,3 +1,13 @@
1
+ == 1.8.0 / 2012-09-13
2
+
3
+ Enhancements
4
+ - Appenders handle string encodings [issue #46]
5
+ - Support for diagnostic contexts [issues #23, #32, #42]
6
+ - Enable JSON formatting of log message [issue #34]
7
+
8
+ Bug Fixes
9
+ - Fix clash with ActiveSupport autoloader (chewie) [issue #39]
10
+
1
11
  == 1.7.2 / 2012-04-03
2
12
 
3
13
  Bug Fixes
@@ -2,7 +2,7 @@
2
2
  by Tim Pease {<img src="https://secure.travis-ci.org/TwP/logging.png">}[http://travis-ci.org/TwP/logging]
3
3
 
4
4
  * {Homepage}[http://rubygems.org/gems/logging]
5
- * {Github Project}[http://github.com/TwP/logging]
5
+ * {Github Project}[https://github.com/TwP/logging]
6
6
  * email tim dot pease at gmail dot com
7
7
 
8
8
  == DESCRIPTION
@@ -78,17 +78,18 @@ the recommended way of accomplishing this.
78
78
  There are many more examples in the "examples" folder of the logging
79
79
  package. The recommended reading order is the following:
80
80
 
81
- * {simple.rb}[http://github.com/TwP/logging/blob/master/examples/simple.rb]
82
- * {loggers.rb}[http://github.com/TwP/logging/blob/master/examples/loggers.rb]
83
- * {classes.rb}[http://github.com/TwP/logging/blob/master/examples/classes.rb]
84
- * {hierarchies.rb}[http://github.com/TwP/logging/blob/master/examples/hierarchies.rb]
85
- * {names.rb}[http://github.com/TwP/logging/blob/master/examples/names.rb]
86
- * {appenders.rb}[http://github.com/TwP/logging/blob/master/examples/appenders.rb]
87
- * {layouts.rb}[http://github.com/TwP/logging/blob/master/examples/layouts.rb]
88
- * {formatting.rb}[http://github.com/TwP/logging/blob/master/examples/formatting.rb]
89
- * {colorization.rb}[http://github.com/TwP/logging/blob/master/examples/colorization.rb]
90
- * {consolidation.rb}[http://github.com/TwP/logging/blob/master/examples/consolidation.rb]
91
- * {fork.rb}[http://github.com/TwP/logging/blob/master/examples/fork.rb]
81
+ * {simple.rb}[https://github.com/TwP/logging/blob/master/examples/simple.rb]
82
+ * {loggers.rb}[https://github.com/TwP/logging/blob/master/examples/loggers.rb]
83
+ * {classes.rb}[https://github.com/TwP/logging/blob/master/examples/classes.rb]
84
+ * {hierarchies.rb}[https://github.com/TwP/logging/blob/master/examples/hierarchies.rb]
85
+ * {names.rb}[https://github.com/TwP/logging/blob/master/examples/names.rb]
86
+ * {appenders.rb}[https://github.com/TwP/logging/blob/master/examples/appenders.rb]
87
+ * {layouts.rb}[https://github.com/TwP/logging/blob/master/examples/layouts.rb]
88
+ * {formatting.rb}[https://github.com/TwP/logging/blob/master/examples/formatting.rb]
89
+ * {colorization.rb}[https://github.com/TwP/logging/blob/master/examples/colorization.rb]
90
+ * {consolidation.rb}[https://github.com/TwP/logging/blob/master/examples/consolidation.rb]
91
+ * {fork.rb}[https://github.com/TwP/logging/blob/master/examples/fork.rb]
92
+ * {mdc.rb}[https://github.com/TwP/logging/blob/master/examples/mdc.rb]
92
93
 
93
94
  == NOTES
94
95
 
data/Rakefile CHANGED
@@ -22,9 +22,10 @@ Bones {
22
22
  use_gmail
23
23
 
24
24
  depend_on 'little-plugger'
25
+ depend_on 'multi_json'
25
26
 
26
- depend_on 'flexmock', :development => true
27
- depend_on 'bones-git', :development => true
27
+ depend_on 'flexmock', '~> 1.0', :development => true
28
+ depend_on 'bones-git', :development => true
28
29
  #depend_on 'bones-rcov', :development => true
29
30
  }
30
31
 
@@ -5,14 +5,14 @@
5
5
  # "format_as" option and a global "backtrace" option.
6
6
  #
7
7
  # The format_as option allows objects to be converted to a string using the
8
- # standard "to_s" method, the "inspect" method, or the "to_yaml" method
9
- # (this is independent of the YAML layout). The format_as option can be
10
- # overridden by each layout as desired.
8
+ # standard "to_s" method, the "inspect" method, the "to_json" method, or the
9
+ # "to_yaml" method (this is independent of the YAML layout). The format_as
10
+ # option can be overridden by each layout as desired.
11
11
  #
12
- # Logging.format_as :string # or :inspect or :yaml
12
+ # Logging.format_as :string # or :inspect or :json or :yaml
13
13
  #
14
14
  # Exceptions are treated differently by the logging framework. The Exception
15
- # class is printed along with the message. Optionally, exception backtraces
15
+ # class is printed along with the message. Optionally, the exception backtrace
16
16
  # can be included in the logging output; this option is enabled by default.
17
17
  #
18
18
  # Logging.backtrace false
@@ -0,0 +1,83 @@
1
+ # :stopdoc:
2
+ #
3
+ # A diagnostic context allows us to attach state information to every log
4
+ # message. This is useful for applications that serve client requests -
5
+ # information about the client can be included in the log messages for the
6
+ # duration of the request processing. This allows you to identify related log
7
+ # messages in concurrent system.
8
+ #
9
+ # The Mapped Diagnostic Context tracks state information in a collection of
10
+ # key/value pairs. In this example we are creating a few threads that will log
11
+ # quotes from famous people. Each thread has its own diagnostic context
12
+ # containing the name of the famous person.
13
+ #
14
+ # Our PatternLayout is configured to attach the "first" and the "last" name of
15
+ # our famous person to each log message.
16
+ #
17
+
18
+ require 'logging'
19
+
20
+ # log the first and last names of the celebrity with each quote
21
+ Logging.appenders.stdout(
22
+ :layout => Logging.layouts.pattern(:pattern => '%X{first} %X{last}: %m\n')
23
+ )
24
+
25
+ log = Logging.logger['User']
26
+ log.add_appenders 'stdout'
27
+ log.level = :debug
28
+
29
+ Logging.mdc['first'] = 'John'
30
+ Logging.mdc['last'] = 'Doe'
31
+
32
+ # in this first thread we will log some quotes by Allan Rickman
33
+ t1 = Thread.new {
34
+ Logging.mdc['first'] = 'Allan'
35
+ Logging.mdc['last'] = 'Rickman'
36
+
37
+ [ %q{I've never been able to plan my life. I just lurch from indecision to indecision.},
38
+ %q{If only life could be a little more tender and art a little more robust.},
39
+ %q{I do take my work seriously and the way to do that is not to take yourself too seriously.},
40
+ %q{I'm a quite serious actor who doesn't mind being ridiculously comic.}
41
+ ].each { |quote|
42
+ sleep rand
43
+ log.info quote
44
+ }
45
+ }
46
+
47
+ # in this second thread we will log some quotes by William Butler Yeats
48
+ t2 = Thread.new {
49
+ Logging.mdc['first'] = 'William'
50
+ Logging.mdc['middle'] = 'Butler'
51
+ Logging.mdc['last'] = 'Yeats'
52
+
53
+ [ %q{Tread softly because you tread on my dreams.},
54
+ %q{The best lack all conviction, while the worst are full of passionate intensity.},
55
+ %q{Education is not the filling of a pail, but the lighting of a fire.},
56
+ %q{Do not wait to strike till the iron is hot; but make it hot by striking.},
57
+ %q{People who lean on logic and philosophy and rational exposition end by starving the best part of the mind.}
58
+ ].each { |quote|
59
+ sleep rand
60
+ log.info quote
61
+ }
62
+ }
63
+
64
+ # and in this third thread we will log some quotes by Bono
65
+ t3 = Thread.new {
66
+ Logging.mdc.clear # otherwise we inherit the last name "Doe"
67
+ Logging.mdc['first'] = 'Bono'
68
+
69
+ [ %q{Music can change the world because it can change people.},
70
+ %q{The less you know, the more you believe.}
71
+ ].each { |quote|
72
+ sleep rand
73
+ log.info quote
74
+ }
75
+ }
76
+
77
+ t1.join
78
+ t2.join
79
+ t3.join
80
+
81
+ log.info %q{and now we are done}
82
+
83
+ # :startdoc:
@@ -9,6 +9,7 @@ require 'yaml'
9
9
  require 'stringio'
10
10
  require 'fileutils'
11
11
  require 'little-plugger'
12
+ require 'multi_json'
12
13
 
13
14
  HAVE_SYSLOG = require? 'syslog'
14
15
 
@@ -319,6 +320,7 @@ module Logging
319
320
  # * :string => to_s
320
321
  # * :inspect => inspect
321
322
  # * :yaml => to_yaml
323
+ # * :json => MultiJson.encode(obj)
322
324
  #
323
325
  # An +ArgumentError+ is raised if anything other than +:string+,
324
326
  # +:inspect+, +:yaml+ is passed to this method.
@@ -326,7 +328,7 @@ module Logging
326
328
  def format_as( f )
327
329
  f = f.intern if f.instance_of? String
328
330
 
329
- unless [:string, :inspect, :yaml].include? f
331
+ unless [:string, :inspect, :yaml, :json].include? f
330
332
  raise ArgumentError, "unknown object format '#{f}'"
331
333
  end
332
334
 
@@ -508,6 +510,7 @@ module Logging
508
510
  ::Logging::Repository.reset
509
511
  ::Logging::Appenders.reset
510
512
  ::Logging::ColorScheme.reset
513
+ ::Logging.clear_diagnostic_contexts(true)
511
514
  LEVELS.clear
512
515
  LNAMES.clear
513
516
  remove_instance_variable :@backtrace if defined? @backtrace
@@ -534,6 +537,7 @@ module Logging
534
537
  require libpath('logging/appenders')
535
538
  require libpath('logging/layouts')
536
539
  require libpath('logging/proxy')
540
+ require libpath('logging/diagnostic_context')
537
541
 
538
542
  require libpath('logging/config/configurator')
539
543
  require libpath('logging/config/yaml_configurator')
@@ -31,6 +31,7 @@ class Appender
31
31
  #
32
32
  # :layout => the layout to use when formatting log events
33
33
  # :level => the level at which to log
34
+ # :encoding => encoding to use when writing messages (defaults to UTF-8)
34
35
  #
35
36
  def initialize( name, opts = {} )
36
37
  ::Logging.init unless ::Logging.initialized?
@@ -40,6 +41,7 @@ class Appender
40
41
 
41
42
  self.layout = opts.getopt(:layout, ::Logging::Layouts::Basic.new)
42
43
  self.level = opts.getopt(:level)
44
+ self.encoding = opts.fetch(:encoding, self.encoding)
43
45
 
44
46
  @mutex = ReentrantMutex.new
45
47
 
@@ -231,6 +233,32 @@ class Appender
231
233
  ]
232
234
  end
233
235
 
236
+ # Returns the current Encoding for the appender or nil if an encoding has
237
+ # not been set.
238
+ #
239
+ def encoding
240
+ return @encoding if defined? @encoding
241
+ @encoding = Object.const_defined?(:Encoding) ? Encoding.default_external : nil
242
+ end
243
+
244
+ # Set the appender encoding to the given value. The value can either be an
245
+ # Encoding instance or a String or Symbol referring to a valid encoding.
246
+ #
247
+ # This method only applies to Ruby 1.9 or later. The encoding will always be
248
+ # nil for older Rubies.
249
+ #
250
+ # value - The encoding as a String, Symbol, or Encoding instance.
251
+ #
252
+ # Raises ArgumentError if the value is not a valid encoding.
253
+ #
254
+ def encoding=( value )
255
+ if value.nil?
256
+ @encoding = nil
257
+ else
258
+ @encoding = Object.const_defined?(:Encoding) ? Encoding.find(value.to_s) : nil
259
+ end
260
+ end
261
+
234
262
 
235
263
  private
236
264
 
@@ -240,7 +240,10 @@ module Logging::Appenders
240
240
  if @auto_flushing == 1
241
241
  canonical_write(str)
242
242
  else
243
- sync { @buffer << str }
243
+ sync {
244
+ str = str.force_encoding(encoding) if encoding and str.encoding != encoding
245
+ @buffer << str
246
+ }
244
247
  @periodic_flusher.signal if @periodic_flusher
245
248
  flush if @buffer.length >= @auto_flushing || immediate?(event)
246
249
  end
@@ -33,6 +33,8 @@ module Logging::Appenders
33
33
  opts = Hash === args.last ? args.pop : {}
34
34
  name = args.empty? ? 'stdout' : args.shift
35
35
 
36
+ opts[:encoding] = STDOUT.external_encoding if STDOUT.respond_to? :external_encoding
37
+
36
38
  super(name, STDOUT, opts)
37
39
  end
38
40
  end # Stdout
@@ -70,6 +72,8 @@ module Logging::Appenders
70
72
  opts = Hash === args.last ? args.pop : {}
71
73
  name = args.empty? ? 'stderr' : args.shift
72
74
 
75
+ opts[:encoding] = STDERR.external_encoding if STDERR.respond_to? :external_encoding
76
+
73
77
  super(name, STDERR, opts)
74
78
  end
75
79
  end # Stderr
@@ -158,6 +158,8 @@ module Logging::Appenders
158
158
  rfc822msg << "Subject: #{@subject}\n"
159
159
  rfc822msg << "Date: #{Time.new.rfc822}\n"
160
160
  rfc822msg << "Message-Id: <#{"%.8f" % Time.now.to_f}@#{@domain}>\n\n"
161
+
162
+ rfc822msg = rfc822msg.force_encoding(encoding) if encoding and rfc822msg.encoding != encoding
161
163
  rfc822msg << str
162
164
 
163
165
  ### send email
@@ -54,6 +54,9 @@ module Logging::Appenders
54
54
  self.class.assert_valid_logfile(@fn)
55
55
  @mode = opts.getopt(:truncate) ? 'w' : 'a'
56
56
 
57
+ self.encoding = opts.fetch(:encoding, self.encoding)
58
+ @mode = "#{@mode}:#{self.encoding}" if self.encoding
59
+
57
60
  super(name, ::File.new(@fn, @mode), opts)
58
61
  end
59
62
 
@@ -70,6 +70,7 @@ module Logging::Appenders
70
70
  #
71
71
  def canonical_write( str )
72
72
  return self if @io.nil?
73
+ str = str.force_encoding(encoding) if encoding and str.encoding != encoding
73
74
  @io.syswrite str
74
75
  self
75
76
  rescue StandardError => err
@@ -142,7 +142,8 @@ module Logging::Appenders
142
142
 
143
143
  # we are opening the file in read/write mode so that a shared lock can
144
144
  # be used on the file descriptor => http://pubs.opengroup.org/onlinepubs/009695399/functions/fcntl.html
145
- super(name, ::File.new(@fn, 'a+'), opts)
145
+ @mode = encoding ? "a+:#{encoding}" : 'a+'
146
+ super(name, ::File.new(@fn, @mode), opts)
146
147
 
147
148
  # setup the file roller
148
149
  @roller =
@@ -177,7 +178,7 @@ module Logging::Appenders
177
178
  flush
178
179
  @io.close rescue nil
179
180
  end
180
- @io = ::File.new(@fn, 'a+')
181
+ @io = ::File.new(@fn, @mode)
181
182
  }
182
183
  super
183
184
  self
@@ -193,7 +194,8 @@ module Logging::Appenders
193
194
  def canonical_write( str )
194
195
  return self if @io.nil?
195
196
 
196
- @io.flock_sh { @io.syswrite(str) }
197
+ str = str.force_encoding(encoding) if encoding and str.encoding != encoding
198
+ @io.flock_sh { @io.syswrite str }
197
199
 
198
200
  if roll_required?
199
201
  @io.flock? {
@@ -60,7 +60,7 @@ module Logging::Appenders
60
60
  alias :reset :clear
61
61
 
62
62
  %w[read readline readlines].each do|m|
63
- class_eval <<-CODE
63
+ class_eval <<-CODE, __FILE__, __LINE__+1
64
64
  def #{m}( *args )
65
65
  sync {
66
66
  begin
@@ -203,9 +203,8 @@ module Logging
203
203
  # Return a normalized representation of a color setting.
204
204
  #
205
205
  def to_constant( v )
206
- ColorScheme.const_get(v.to_s.upcase)
207
- rescue NameError
208
- return nil
206
+ v = v.to_s.upcase
207
+ ColorScheme.const_get(v) if (ColorScheme.const_defined?(v, false) rescue ColorScheme.const_defined?(v))
209
208
  end
210
209
 
211
210
  # Embed in a String to clear all previous ANSI sequences. This *MUST* be
@@ -0,0 +1,330 @@
1
+
2
+ module Logging
3
+
4
+ # A Mapped Diagnostic Context, or MDC in short, is an instrument used to
5
+ # distinguish interleaved log output from different sources. Log output is
6
+ # typically interleaved when a server handles multiple clients
7
+ # near-simultaneously.
8
+ #
9
+ # Interleaved log output can still be meaningful if each log entry from
10
+ # different contexts had a distinctive stamp. This is where MDCs come into
11
+ # play.
12
+ #
13
+ # The MDC provides a hash of contextual messages that are identified by
14
+ # unique keys. These unique keys are set by the application and appended
15
+ # to log messages to identify groups of log events. One use of the Mapped
16
+ # Diagnostic Context is to store HTTP request headers associated with a Rack
17
+ # request. These headers can be included with all log messages emitted while
18
+ # generating the HTTP response.
19
+ #
20
+ # When configured to do so, PatternLayout instances will automatically
21
+ # retrieve the mapped diagnostic context for the current thread with out any
22
+ # user intervention. This context information can be used to track user
23
+ # sessions in a Rails application, for example.
24
+ #
25
+ # Note that MDCs are managed on a per thread basis. MDC operations such as
26
+ # `[]`, `[]=`, and `clear` affect the MDC of the current thread only. MDCs
27
+ # of other threads remain unaffected.
28
+ #
29
+ # By default, when a new thread is created it will inherit the context of
30
+ # its parent thread. However, the `inherit` method may be used to inherit
31
+ # context for any other thread in the application.
32
+ #
33
+ module MappedDiagnosticContext
34
+ extend self
35
+
36
+ # The name used to retrieve the MDC from thread-local storage.
37
+ NAME = 'logging.mapped-diagnostic-context'.freeze
38
+
39
+ # Public: Put a context value as identified with the key parameter into
40
+ # the current thread's context map.
41
+ #
42
+ # key - The String identifier for the context.
43
+ # value - The String value to store.
44
+ #
45
+ # Returns the value.
46
+ #
47
+ def []=( key, value )
48
+ context.store(key.to_s, value)
49
+ end
50
+
51
+ # Public: Get the context value identified with the key parameter.
52
+ #
53
+ # key - The String identifier for the context.
54
+ #
55
+ # Returns the value associated with the key or nil if there is no value
56
+ # present.
57
+ #
58
+ def []( key )
59
+ context.fetch(key.to_s, nil)
60
+ end
61
+
62
+ # Public: Remove the context value identified with the key parameter.
63
+ #
64
+ # key - The String identifier for the context.
65
+ #
66
+ # Returns the value associated with the key or nil if there is no value
67
+ # present.
68
+ #
69
+ def delete( key )
70
+ context.delete(key.to_s)
71
+ end
72
+
73
+ # Public: Clear all mapped diagnostic information if any. This method is
74
+ # useful in cases where the same thread can be potentially used over and
75
+ # over in different unrelated contexts.
76
+ #
77
+ # Returns the MappedDiagnosticContext.
78
+ #
79
+ def clear
80
+ context.clear if Thread.current[NAME]
81
+ self
82
+ end
83
+
84
+ # Public: Inherit the diagnostic context of another thread. In the vast
85
+ # majority of cases the other thread will the parent that spawned the
86
+ # current thread. The diagnostic context from the parent thread is cloned
87
+ # before being inherited; the two diagnostic contexts can be changed
88
+ # independently.
89
+ #
90
+ # Returns the MappedDiagnosticContext.
91
+ #
92
+ def inherit( obj )
93
+ case obj
94
+ when Hash
95
+ Thread.current[NAME] = obj.dup
96
+ when Thread
97
+ return if Thread.current == obj
98
+ Thread.exclusive {
99
+ Thread.current[NAME] = obj[NAME].dup if obj[NAME]
100
+ }
101
+ end
102
+
103
+ self
104
+ end
105
+
106
+ # Returns the Hash acting as the storage for this NestedDiagnosticContext.
107
+ # A new storage Hash is created for each Thread running in the
108
+ # application.
109
+ #
110
+ def context
111
+ Thread.current[NAME] ||= Hash.new
112
+ end
113
+ end # MappedDiagnosticContext
114
+
115
+
116
+ # A Nested Diagnostic Context, or NDC in short, is an instrument to
117
+ # distinguish interleaved log output from different sources. Log output is
118
+ # typically interleaved when a server handles multiple clients
119
+ # near-simultaneously.
120
+ #
121
+ # Interleaved log output can still be meaningful if each log entry from
122
+ # different contexts had a distinctive stamp. This is where NDCs come into
123
+ # play.
124
+ #
125
+ # The NDC is a stack of contextual messages that are pushed and popped by
126
+ # the client as different contexts are encountered in the application. When a
127
+ # new context is entered, the client will `push` a new message onto the NDC
128
+ # stack. This message appears in all log messages. When this context is
129
+ # exited, the client will call `pop` to remove the message.
130
+ #
131
+ # * Contexts can be nested
132
+ # * When entering a context, call `Logging.ndc.push`
133
+ # * When leaving a context, call `Logging.ndc.pop`
134
+ # * Configure the PatternLayout to log context information
135
+ #
136
+ # There is no penalty for forgetting to match each push operation with a
137
+ # corresponding pop, except the obvious mismatch between the real
138
+ # application context and the context set in the NDC.
139
+ #
140
+ # When configured to do so, PatternLayout instance will automatically
141
+ # retrieve the nested diagnostic context for the current thread with out any
142
+ # user intervention. This context information can be used to track user
143
+ # sessions in a Rails application, for example.
144
+ #
145
+ # Note that NDCs are managed on a per thread basis. NDC operations such as
146
+ # `push`, `pop`, and `clear` affect the NDC of the current thread only. NDCs
147
+ # of other threads remain unaffected.
148
+ #
149
+ # By default, when a new thread is created it will inherit the context of
150
+ # its parent thread. However, the `inherit` method may be used to inherit
151
+ # context for any other thread in the application.
152
+ #
153
+ module NestedDiagnosticContext
154
+ extend self
155
+
156
+ # The name used to retrieve the NDC from thread-local storage.
157
+ NAME = 'logging.nested-diagnostic-context'.freeze
158
+
159
+ # Public: Push new diagnostic context information for the current thread.
160
+ # The contents of the message parameter is determined solely by the
161
+ # client.
162
+ #
163
+ # message - The message String to add to the current context.
164
+ #
165
+ # Returns the current NestedDiagnosticContext.
166
+ #
167
+ def push( message )
168
+ context.push(message)
169
+ self
170
+ end
171
+ alias :<< :push
172
+
173
+ # Public: Clients should call this method before leaving a diagnostic
174
+ # context. The returned value is the last pushed message. If no
175
+ # context is available then `nil` is returned.
176
+ #
177
+ # Returns the last pushed diagnostic message String or nil if no messages
178
+ # exist.
179
+ #
180
+ def pop
181
+ context.pop
182
+ end
183
+
184
+ # Public: Looks at the last diagnostic context at the top of this NDC
185
+ # without removing it. The returned value is the last pushed message. If
186
+ # no context is available then `nil` is returned.
187
+ #
188
+ # Returns the last pushed diagnostic message String or nil if no messages
189
+ # exist.
190
+ #
191
+ def peek
192
+ context.last
193
+ end
194
+
195
+ # Public: Clear all nested diagnostic information if any. This method is
196
+ # useful in cases where the same thread can be potentially used over and
197
+ # over in different unrelated contexts.
198
+ #
199
+ # Returns the NestedDiagnosticContext.
200
+ #
201
+ def clear
202
+ context.clear if Thread.current[NAME]
203
+ self
204
+ end
205
+
206
+ # Public: Inherit the diagnostic context of another thread. In the vast
207
+ # majority of cases the other thread will the parent that spawned the
208
+ # current thread. The diagnostic context from the parent thread is cloned
209
+ # before being inherited; the two diagnostic contexts can be changed
210
+ # independently.
211
+ #
212
+ # Returns the NestedDiagnosticContext.
213
+ #
214
+ def inherit( obj )
215
+ case obj
216
+ when Array
217
+ Thread.current[NAME] = obj.dup
218
+ when Thread
219
+ return if Thread.current == obj
220
+ Thread.exclusive {
221
+ Thread.current[NAME] = obj[NAME].dup if obj[NAME]
222
+ }
223
+ end
224
+
225
+ self
226
+ end
227
+
228
+ # Returns the Array acting as the storage stack for this
229
+ # NestedDiagnosticContext. A new storage Array is created for each Thread
230
+ # running in the application.
231
+ #
232
+ def context
233
+ Thread.current[NAME] ||= Array.new
234
+ end
235
+ end # NestedDiagnosticContext
236
+
237
+
238
+ # Public: Accessor method for getting the current Thread's
239
+ # MappedDiagnosticContext.
240
+ #
241
+ # Returns MappedDiagnosticContext
242
+ #
243
+ def self.mdc() MappedDiagnosticContext end
244
+
245
+ # Public: Accessor method for getting the current Thread's
246
+ # NestedDiagnosticContext.
247
+ #
248
+ # Returns NestedDiagnosticContext
249
+ #
250
+ def self.ndc() NestedDiagnosticContext end
251
+
252
+ # Public: Convenience method that will clear both the Mapped Diagnostic
253
+ # Context and the Nested Diagnostic Context of the current thread. If the
254
+ # `all` flag passed to this method is true, then the diagnostic contexts for
255
+ # _every_ thread in the application will be cleared.
256
+ #
257
+ # all - Boolean flag used to clear the context of every Thread (default is false)
258
+ #
259
+ # Returns the Logging module.
260
+ #
261
+ def self.clear_diagnostic_contexts( all = false )
262
+ if all
263
+ Thread.exclusive {
264
+ Thread.list.each { |thread|
265
+ thread[MappedDiagnosticContext::NAME].clear if thread[MappedDiagnosticContext::NAME]
266
+ thread[NestedDiagnosticContext::NAME].clear if thread[NestedDiagnosticContext::NAME]
267
+ }
268
+ }
269
+ else
270
+ MappedDiagnosticContext.clear
271
+ NestedDiagnosticContext.clear
272
+ end
273
+
274
+ self
275
+ end
276
+
277
+ end # module Logging
278
+
279
+
280
+ # :stopdoc:
281
+ class Thread
282
+ class << self
283
+
284
+ %w[new start fork].each do |m|
285
+ class_eval <<-__, __FILE__, __LINE__
286
+ alias :_orig_#{m} :#{m}
287
+ private :_orig_#{m}
288
+ def #{m}( *a, &b )
289
+ create_with_logging_context(:_orig_#{m}, *a ,&b)
290
+ end
291
+ __
292
+ end
293
+
294
+ private
295
+
296
+ # In order for the diagnostic contexts to behave properly we need to
297
+ # inherit state from the parent thread. The only way I have found to do
298
+ # this in Ruby is to override `new` and capture the contexts from the
299
+ # parent Thread at the time the child Thread is created. The code below does
300
+ # just this. If there is a more idiomatic way of accomplishing this in Ruby,
301
+ # please let me know!
302
+ #
303
+ # Also, great care is taken in this code to ensure that a reference to the
304
+ # parent thread does not exist in the binding associated with the block
305
+ # being executed in the child thread. The same is true for the parent
306
+ # thread's mdc and ndc. If any of those references end up in the binding,
307
+ # then they cannot be garbage collected until the child thread exits.
308
+ #
309
+ def create_with_logging_context( m, *a, &b )
310
+ p_mdc, p_ndc = nil
311
+
312
+ if Thread.current[Logging::MappedDiagnosticContext::NAME]
313
+ p_mdc = Thread.current[Logging::MappedDiagnosticContext::NAME].dup
314
+ end
315
+
316
+ if Thread.current[Logging::NestedDiagnosticContext::NAME]
317
+ p_ndc = Thread.current[Logging::NestedDiagnosticContext::NAME].dup
318
+ end
319
+
320
+ self.send(m, p_mdc, p_ndc, *a) { |mdc, ndc, *args|
321
+ Logging::MappedDiagnosticContext.inherit(mdc)
322
+ Logging::NestedDiagnosticContext.inherit(ndc)
323
+ b.call(*args)
324
+ }
325
+ end
326
+
327
+ end
328
+ end # Thread
329
+ # :startdoc:
330
+