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.
- checksums.yaml +4 -4
- data/README.md +5 -3
- data/lib/logsly/colors.rb +2 -2
- data/lib/logsly/logging182/appender.rb +290 -0
- data/lib/logsly/logging182/appenders/buffering.rb +398 -0
- data/lib/logsly/logging182/appenders/console.rb +81 -0
- data/lib/logsly/logging182/appenders/email.rb +178 -0
- data/lib/logsly/logging182/appenders/file.rb +85 -0
- data/lib/logsly/logging182/appenders/growl.rb +200 -0
- data/lib/logsly/logging182/appenders/io.rb +84 -0
- data/lib/logsly/logging182/appenders/rolling_file.rb +338 -0
- data/lib/logsly/logging182/appenders/string_io.rb +92 -0
- data/lib/logsly/logging182/appenders/syslog.rb +215 -0
- data/lib/logsly/logging182/appenders.rb +64 -0
- data/lib/logsly/logging182/color_scheme.rb +248 -0
- data/lib/logsly/logging182/config/configurator.rb +187 -0
- data/lib/logsly/logging182/config/yaml_configurator.rb +190 -0
- data/lib/logsly/logging182/diagnostic_context.rb +332 -0
- data/lib/logsly/logging182/layout.rb +132 -0
- data/lib/logsly/logging182/layouts/basic.rb +38 -0
- data/lib/logsly/logging182/layouts/parseable.rb +256 -0
- data/lib/logsly/logging182/layouts/pattern.rb +568 -0
- data/lib/logsly/logging182/layouts.rb +9 -0
- data/lib/logsly/logging182/log_event.rb +44 -0
- data/lib/logsly/logging182/logger.rb +509 -0
- data/lib/logsly/logging182/proxy.rb +59 -0
- data/lib/logsly/logging182/rails_compat.rb +36 -0
- data/lib/logsly/logging182/repository.rb +231 -0
- data/lib/logsly/logging182/root_logger.rb +60 -0
- data/lib/logsly/logging182/stats.rb +277 -0
- data/lib/logsly/logging182/utils.rb +231 -0
- data/lib/logsly/logging182.rb +559 -0
- data/lib/logsly/outputs.rb +5 -5
- data/lib/logsly/version.rb +1 -1
- data/lib/logsly.rb +6 -6
- data/logsly.gemspec +4 -2
- data/test/unit/colors_tests.rb +3 -3
- data/test/unit/logsly_tests.rb +14 -14
- data/test/unit/outputs_tests.rb +34 -24
- 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
|
+
|