logsly 1.2.0 → 1.3.0

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