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 +1 -0
- data/History.txt +10 -0
- data/README.rdoc +13 -12
- data/Rakefile +3 -2
- data/examples/formatting.rb +5 -5
- data/examples/mdc.rb +83 -0
- data/lib/logging.rb +5 -1
- data/lib/logging/appender.rb +28 -0
- data/lib/logging/appenders/buffering.rb +4 -1
- data/lib/logging/appenders/console.rb +4 -0
- data/lib/logging/appenders/email.rb +2 -0
- data/lib/logging/appenders/file.rb +3 -0
- data/lib/logging/appenders/io.rb +1 -0
- data/lib/logging/appenders/rolling_file.rb +5 -3
- data/lib/logging/appenders/string_io.rb +1 -1
- data/lib/logging/color_scheme.rb +2 -3
- data/lib/logging/diagnostic_context.rb +330 -0
- data/lib/logging/layout.rb +20 -4
- data/lib/logging/layouts/parseable.rb +45 -30
- data/lib/logging/layouts/pattern.rb +61 -12
- data/test/appenders/test_buffered_io.rb +21 -0
- data/test/appenders/test_file.rb +20 -0
- data/test/appenders/test_syslog.rb +6 -9
- data/test/layouts/test_json.rb +70 -12
- data/test/layouts/test_pattern.rb +35 -0
- data/test/layouts/test_yaml.rb +49 -6
- data/test/test_layout.rb +7 -0
- data/test/test_logging.rb +6 -1
- data/test/test_mapped_diagnostic_context.rb +78 -0
- data/test/test_nested_diagnostic_context.rb +83 -0
- data/version.txt +1 -1
- metadata +57 -19
- data/.rvmrc +0 -47
- data/a.rb +0 -10
- data/tmp.txt +0 -3
data/.gitignore
CHANGED
data/History.txt
CHANGED
@@ -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
|
data/README.rdoc
CHANGED
@@ -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}[
|
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}[
|
82
|
-
* {loggers.rb}[
|
83
|
-
* {classes.rb}[
|
84
|
-
* {hierarchies.rb}[
|
85
|
-
* {names.rb}[
|
86
|
-
* {appenders.rb}[
|
87
|
-
* {layouts.rb}[
|
88
|
-
* {formatting.rb}[
|
89
|
-
* {colorization.rb}[
|
90
|
-
* {consolidation.rb}[
|
91
|
-
* {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',
|
27
|
-
depend_on 'bones-git',
|
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
|
|
data/examples/formatting.rb
CHANGED
@@ -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,
|
9
|
-
# (this is independent of the YAML layout). The format_as
|
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
|
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
|
data/examples/mdc.rb
ADDED
@@ -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:
|
data/lib/logging.rb
CHANGED
@@ -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')
|
data/lib/logging/appender.rb
CHANGED
@@ -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 {
|
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
|
|
data/lib/logging/appenders/io.rb
CHANGED
@@ -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
|
-
|
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,
|
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
|
-
|
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? {
|
data/lib/logging/color_scheme.rb
CHANGED
@@ -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
|
-
|
207
|
-
|
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
|
+
|