logging 1.7.2 → 1.8.0

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