logsly 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -3
  3. data/lib/logsly/colors.rb +2 -2
  4. data/lib/logsly/logging182/appender.rb +290 -0
  5. data/lib/logsly/logging182/appenders/buffering.rb +398 -0
  6. data/lib/logsly/logging182/appenders/console.rb +81 -0
  7. data/lib/logsly/logging182/appenders/email.rb +178 -0
  8. data/lib/logsly/logging182/appenders/file.rb +85 -0
  9. data/lib/logsly/logging182/appenders/growl.rb +200 -0
  10. data/lib/logsly/logging182/appenders/io.rb +84 -0
  11. data/lib/logsly/logging182/appenders/rolling_file.rb +338 -0
  12. data/lib/logsly/logging182/appenders/string_io.rb +92 -0
  13. data/lib/logsly/logging182/appenders/syslog.rb +215 -0
  14. data/lib/logsly/logging182/appenders.rb +64 -0
  15. data/lib/logsly/logging182/color_scheme.rb +248 -0
  16. data/lib/logsly/logging182/config/configurator.rb +187 -0
  17. data/lib/logsly/logging182/config/yaml_configurator.rb +190 -0
  18. data/lib/logsly/logging182/diagnostic_context.rb +332 -0
  19. data/lib/logsly/logging182/layout.rb +132 -0
  20. data/lib/logsly/logging182/layouts/basic.rb +38 -0
  21. data/lib/logsly/logging182/layouts/parseable.rb +256 -0
  22. data/lib/logsly/logging182/layouts/pattern.rb +568 -0
  23. data/lib/logsly/logging182/layouts.rb +9 -0
  24. data/lib/logsly/logging182/log_event.rb +44 -0
  25. data/lib/logsly/logging182/logger.rb +509 -0
  26. data/lib/logsly/logging182/proxy.rb +59 -0
  27. data/lib/logsly/logging182/rails_compat.rb +36 -0
  28. data/lib/logsly/logging182/repository.rb +231 -0
  29. data/lib/logsly/logging182/root_logger.rb +60 -0
  30. data/lib/logsly/logging182/stats.rb +277 -0
  31. data/lib/logsly/logging182/utils.rb +231 -0
  32. data/lib/logsly/logging182.rb +559 -0
  33. data/lib/logsly/outputs.rb +5 -5
  34. data/lib/logsly/version.rb +1 -1
  35. data/lib/logsly.rb +6 -6
  36. data/logsly.gemspec +4 -2
  37. data/test/unit/colors_tests.rb +3 -3
  38. data/test/unit/logsly_tests.rb +14 -14
  39. data/test/unit/outputs_tests.rb +34 -24
  40. metadata +45 -6
@@ -0,0 +1,187 @@
1
+
2
+ module Logsly::Logging182::Config
3
+
4
+ # The Configurator class is used to configure the Logsly::Logging182 framework
5
+ # using information found in a block of Ruby code. This block is evaluated
6
+ # in the context of the configurator's DSL.
7
+ #
8
+ class Configurator
9
+
10
+ class Error < StandardError; end # :nodoc:
11
+
12
+ # call-seq:
13
+ # Configurator.process( &block )
14
+ #
15
+ def self.process( &block )
16
+ new.load(&block)
17
+ end
18
+
19
+ # call-seq:
20
+ # load { block }
21
+ #
22
+ # Loads the configuration from the _block_ and configures the Logsly::Logging182
23
+ # gem.
24
+ #
25
+ def load( &block )
26
+ raise Error, "missing configuration block" unless block
27
+
28
+ dsl = TopLevelDSL.new
29
+ dsl.instance_eval(&block)
30
+
31
+ pre_config dsl.__pre_config
32
+ ::Logsly::Logging182::Logger[:root] # ensures the log levels are defined
33
+ appenders dsl.__appenders
34
+ loggers dsl.__loggers
35
+ end
36
+
37
+ # call-seq:
38
+ # pre_config( config )
39
+ #
40
+ # Configures the logging levels, object format style, and root logging
41
+ # level.
42
+ #
43
+ def pre_config( config )
44
+ if config.nil?
45
+ ::Logsly::Logging182.init unless ::Logsly::Logging182.initialized?
46
+ return
47
+ end
48
+
49
+ # define levels
50
+ levels = config[:levels]
51
+ ::Logsly::Logging182.init(levels) unless levels.nil?
52
+
53
+ # format as
54
+ format = config[:format_as]
55
+ ::Logsly::Logging182.format_as(format) unless format.nil?
56
+
57
+ # backtrace
58
+ value = config[:backtrace]
59
+ ::Logsly::Logging182.backtrace(value) unless value.nil?
60
+ end
61
+
62
+ # call-seq:
63
+ # appenders( ary )
64
+ #
65
+ # Given an array of Appender configurations, this method will iterate
66
+ # over each and create the Appender(s).
67
+ #
68
+ def appenders( ary )
69
+ ary.each {|name, config| appender(name, config)}
70
+ end
71
+
72
+ # call-seq:
73
+ # loggers( ary )
74
+ #
75
+ # Given an array of Logger configurations, this method will iterate over
76
+ # each and create the Logger(s).
77
+ #
78
+ def loggers( ary )
79
+ ary.each do |name, config|
80
+ l = Logsly::Logging182::Logger[name]
81
+ l.level = config[:level] if config[:level]
82
+ l.additive = config[:additive] if l.respond_to? :additive=
83
+ l.trace = config[:trace]
84
+ l.appenders = Array(config[:appenders]).
85
+ map {|nm| ::Logsly::Logging182::Appenders[nm]}
86
+ end
87
+ end
88
+
89
+ # call-seq:
90
+ # appender( name, config )
91
+ #
92
+ # Creates a new Appender based on the given _config_ options (a hash).
93
+ # The type of Appender created is determined by the 'type' option in the
94
+ # config. The remaining config options are passed to the Appender
95
+ # initializer.
96
+ #
97
+ # The config options can also contain a 'layout' option. This should be
98
+ # another set of options used to create a Layout for this Appender.
99
+ #
100
+ def appender( name, config )
101
+ type = config.delete(:type)
102
+ raise Error, "appender type not given for #{name.inspect}" if type.nil?
103
+
104
+ config[:layout] = layout(config[:layout]) if config.has_key? :layout
105
+
106
+ clazz = ::Logsly::Logging182::Appenders.const_get type
107
+ clazz.new(name, config)
108
+ rescue NameError
109
+ raise Error, "unknown appender class Logsly::Logging182::Appenders::#{type}"
110
+ end
111
+
112
+ # call-seq:
113
+ # layout( config )
114
+ #
115
+ # Creates a new Layout based on the given _config_ options (a hash).
116
+ # The type of Layout created is determined by the 'type' option in the
117
+ # config. The remaining config options are passed to the Layout
118
+ # initializer.
119
+ #
120
+ def layout( config )
121
+ return ::Logsly::Logging182::Layouts::Basic.new if config.nil?
122
+
123
+ type = config.delete(:type)
124
+ raise Error, 'layout type not given' if type.nil?
125
+
126
+ clazz = ::Logsly::Logging182::Layouts.const_get type
127
+ clazz.new config
128
+ rescue NameError
129
+ raise Error, "unknown layout class Logsly::Logging182::Layouts::#{type}"
130
+ end
131
+
132
+ class DSL
133
+ instance_methods.each do |m|
134
+ undef_method m unless m[%r/^(__|object_id|instance_eval)/]
135
+ end
136
+
137
+ def self.process( &block )
138
+ dsl = new
139
+ dsl.instance_eval(&block)
140
+ dsl.__hash
141
+ end
142
+
143
+ def __hash
144
+ @hash ||= Hash.new
145
+ end
146
+
147
+ def method_missing( method, *args, &block )
148
+ args << DSL.process(&block) if block
149
+
150
+ key = method.to_sym
151
+ value = (1 == args.length ? args.first : args)
152
+ __store(key, value)
153
+ end
154
+
155
+ def __store( key, value )
156
+ __hash[key] = value
157
+ end
158
+ end
159
+
160
+ class TopLevelDSL < DSL
161
+ undef_method :method_missing
162
+
163
+ def initialize
164
+ @loggers = []
165
+ @appenders = []
166
+ end
167
+
168
+ def pre_config( &block )
169
+ __store(:preconfig, DSL.process(&block))
170
+ end
171
+
172
+ def logger( name, &block )
173
+ @loggers << [name, DSL.process(&block)]
174
+ end
175
+
176
+ def appender( name, &block )
177
+ @appenders << [name, DSL.process(&block)]
178
+ end
179
+
180
+ def __pre_config() __hash[:preconfig]; end
181
+ def __loggers() @loggers; end
182
+ def __appenders() @appenders; end
183
+ end
184
+
185
+ end # class Configurator
186
+ end # module Logsly::Logging182::Config
187
+
@@ -0,0 +1,190 @@
1
+
2
+ module Logsly::Logging182::Config
3
+
4
+ # The YamlConfigurator class is used to configure the Logsly::Logging182 framework
5
+ # using information found in a YAML file.
6
+ #
7
+ class YamlConfigurator
8
+
9
+ class Error < StandardError; end # :nodoc:
10
+
11
+ class << self
12
+
13
+ # call-seq:
14
+ # YamlConfigurator.load( file, key = 'logging_config' )
15
+ #
16
+ # Load the given YAML _file_ and use it to configure the Logsly::Logging182
17
+ # framework. The file can be either a filename, and open File, or an
18
+ # IO object. If it is the latter two, the File / IO object will not be
19
+ # closed by this method.
20
+ #
21
+ # The configuration will be loaded from the given _key_ in the YAML
22
+ # stream.
23
+ #
24
+ def load( file, key = 'logging_config' )
25
+ io, close = nil, false
26
+ case file
27
+ when String
28
+ io = File.open(file, 'r')
29
+ close = true
30
+ when IO
31
+ io = file
32
+ else
33
+ raise Error, 'expecting a filename or a File'
34
+ end
35
+
36
+ begin
37
+ new(io, key).load
38
+ ensure
39
+ io.close if close
40
+ end
41
+ nil
42
+ end
43
+ end # class << self
44
+
45
+ # call-seq:
46
+ # YamlConfigurator.new( io, key )
47
+ #
48
+ # Creates a new YAML configurator that will load the Logsly::Logging182
49
+ # configuration from the given _io_ stream. The configuration will be
50
+ # loaded from the given _key_ in the YAML stream.
51
+ #
52
+ def initialize( io, key )
53
+ YAML.load_documents(io) do |doc|
54
+ @config = doc[key]
55
+ break if @config.instance_of?(Hash)
56
+ end
57
+
58
+ unless @config.instance_of?(Hash)
59
+ raise Error, "Key '#{key}' not defined in YAML configuration"
60
+ end
61
+ end
62
+
63
+ # call-seq:
64
+ # load
65
+ #
66
+ # Loads the Logsly::Logging182 configuration from the data loaded from the YAML
67
+ # file.
68
+ #
69
+ def load
70
+ pre_config @config['pre_config']
71
+ ::Logsly::Logging182::Logger[:root] # ensures the log levels are defined
72
+ appenders @config['appenders']
73
+ loggers @config['loggers']
74
+ end
75
+
76
+ # call-seq:
77
+ # pre_config( config )
78
+ #
79
+ # Configures the logging levels, object format style, and root logging
80
+ # level.
81
+ #
82
+ def pre_config( config )
83
+ # if no pre_config section was given, just create an empty hash
84
+ # we do this to ensure that some logging levels are always defined
85
+ config ||= Hash.new
86
+
87
+ # define levels
88
+ levels = config['define_levels']
89
+ ::Logsly::Logging182.init(levels) unless levels.nil?
90
+
91
+ # format as
92
+ format = config['format_as']
93
+ ::Logsly::Logging182.format_as(format) unless format.nil?
94
+
95
+ # backtrace
96
+ value = config['backtrace']
97
+ ::Logsly::Logging182.backtrace(value) unless value.nil?
98
+
99
+ # grab the root logger and set the logging level
100
+ root = ::Logsly::Logging182::Logger.root
101
+ if config.has_key?('root')
102
+ root.level = config['root']['level']
103
+ end
104
+ end
105
+
106
+ # call-seq:
107
+ # appenders( ary )
108
+ #
109
+ # Given an array of Appender configurations, this method will iterate
110
+ # over each and create the Appender(s).
111
+ #
112
+ def appenders( ary )
113
+ return if ary.nil?
114
+
115
+ ary.each {|h| appender(h)}
116
+ end
117
+
118
+ # call-seq:
119
+ # loggers( ary )
120
+ #
121
+ # Given an array of Logger configurations, this method will iterate over
122
+ # each and create the Logger(s).
123
+ #
124
+ def loggers( ary )
125
+ return if ary.nil?
126
+
127
+ ary.each do |config|
128
+ name = config['name']
129
+ raise Error, 'Logger name not given' if name.nil?
130
+
131
+ l = Logsly::Logging182::Logger.new name
132
+ l.level = config['level'] if config.has_key?('level')
133
+ l.additive = config['additive'] if l.respond_to? :additive=
134
+ l.trace = config['trace'] if l.respond_to? :trace=
135
+
136
+ if config.has_key?('appenders')
137
+ l.appenders = config['appenders'].map {|n| ::Logsly::Logging182::Appenders[n]}
138
+ end
139
+ end
140
+ end
141
+
142
+ # call-seq:
143
+ # appender( config )
144
+ #
145
+ # Creates a new Appender based on the given _config_ options (a hash).
146
+ # The type of Appender created is determined by the 'type' option in the
147
+ # config. The remaining config options are passed to the Appender
148
+ # initializer.
149
+ #
150
+ # The config options can also contain a 'layout' option. This should be
151
+ # another set of options used to create a Layout for this Appender.
152
+ #
153
+ def appender( config )
154
+ return if config.nil?
155
+ config = config.dup
156
+
157
+ type = config.delete('type')
158
+ raise Error, 'Appender type not given' if type.nil?
159
+
160
+ name = config.delete('name')
161
+ raise Error, 'Appender name not given' if name.nil?
162
+
163
+ config['layout'] = layout(config.delete('layout'))
164
+
165
+ clazz = ::Logsly::Logging182::Appenders.const_get type
166
+ clazz.new(name, config)
167
+ end
168
+
169
+ # call-seq:
170
+ # layout( config )
171
+ #
172
+ # Creates a new Layout based on the given _config_ options (a hash).
173
+ # The type of Layout created is determined by the 'type' option in the
174
+ # config. The remaining config options are passed to the Layout
175
+ # initializer.
176
+ #
177
+ def layout( config )
178
+ return if config.nil?
179
+ config = config.dup
180
+
181
+ type = config.delete('type')
182
+ raise Error, 'Layout type not given' if type.nil?
183
+
184
+ clazz = ::Logsly::Logging182::Layouts.const_get type
185
+ clazz.new config
186
+ end
187
+
188
+ end # class YamlConfigurator
189
+ end # module Logsly::Logging182::Config
190
+
@@ -0,0 +1,332 @@
1
+
2
+ module Logsly::Logging182
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
+ DIAGNOSTIC_MUTEX.synchronize {
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 `Logsly::Logging182.ndc.push`
133
+ # * When leaving a context, call `Logsly::Logging182.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
+ DIAGNOSTIC_MUTEX.synchronize {
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 Logsly::Logging182 module.
260
+ #
261
+ def self.clear_diagnostic_contexts( all = false )
262
+ if all
263
+ DIAGNOSTIC_MUTEX.synchronize {
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
+ DIAGNOSTIC_MUTEX = Mutex.new
278
+
279
+ end # module Logsly::Logging182
280
+
281
+
282
+ # :stopdoc:
283
+ class Thread
284
+ class << self
285
+
286
+ %w[new start fork].each do |m|
287
+ class_eval <<-__, __FILE__, __LINE__
288
+ alias :_orig_#{m} :#{m}
289
+ private :_orig_#{m}
290
+ def #{m}( *a, &b )
291
+ create_with_logging_context(:_orig_#{m}, *a ,&b)
292
+ end
293
+ __
294
+ end
295
+
296
+ private
297
+
298
+ # In order for the diagnostic contexts to behave properly we need to
299
+ # inherit state from the parent thread. The only way I have found to do
300
+ # this in Ruby is to override `new` and capture the contexts from the
301
+ # parent Thread at the time the child Thread is created. The code below does
302
+ # just this. If there is a more idiomatic way of accomplishing this in Ruby,
303
+ # please let me know!
304
+ #
305
+ # Also, great care is taken in this code to ensure that a reference to the
306
+ # parent thread does not exist in the binding associated with the block
307
+ # being executed in the child thread. The same is true for the parent
308
+ # thread's mdc and ndc. If any of those references end up in the binding,
309
+ # then they cannot be garbage collected until the child thread exits.
310
+ #
311
+ def create_with_logging_context( m, *a, &b )
312
+ mdc, ndc = nil
313
+
314
+ if Thread.current[Logsly::Logging182::MappedDiagnosticContext::NAME]
315
+ mdc = Thread.current[Logsly::Logging182::MappedDiagnosticContext::NAME].dup
316
+ end
317
+
318
+ if Thread.current[Logsly::Logging182::NestedDiagnosticContext::NAME]
319
+ ndc = Thread.current[Logsly::Logging182::NestedDiagnosticContext::NAME].dup
320
+ end
321
+
322
+ self.send(m, *a) { |*args|
323
+ Logsly::Logging182::MappedDiagnosticContext.inherit(mdc)
324
+ Logsly::Logging182::NestedDiagnosticContext.inherit(ndc)
325
+ b.call(*args)
326
+ }
327
+ end
328
+
329
+ end
330
+ end # Thread
331
+ # :startdoc:
332
+