logging 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,3 +1,11 @@
1
+ == 1.1.0 / 2009-04-21
2
+
3
+ 3 minor enhancements
4
+ - Added a "global" logger method
5
+ - Loggers can be consolidated on a per-namespace basis
6
+ - Added a precision to the logger name specifier in the pattern layout
7
+ [addresses http://github.com/TwP/logging/issues#issue/1]
8
+
1
9
  == 1.0.0 / 2009-04-17
2
10
 
3
11
  2 major enhancements
data/README.rdoc CHANGED
@@ -86,6 +86,7 @@ package. The recommended reading order is the following:
86
86
  appenders.rb
87
87
  layouts.rb
88
88
  formatting.rb
89
+ consolidation.rb
89
90
 
90
91
  == NOTES
91
92
 
@@ -0,0 +1,81 @@
1
+ #
2
+ # Logging support can be included globally giving all objects in the Ruby
3
+ # space access to a logger instance. This "logger" method invokes
4
+ #
5
+ # Logging.logger[self]
6
+ #
7
+ # And returns the appropriate logger for the current context.
8
+ #
9
+ # However, there might be times when it is not desirable to create an
10
+ # individual logger for every class and module. This is where the concept of
11
+ # "logger consolidation" comes into play. A ruby namespace can be configured
12
+ # to consolidate loggers such that all classes and modules in that namespace
13
+ # use the same logger instance.
14
+ #
15
+ # Because our loggers are being accessed via the self context, it becomes
16
+ # very easy to turn on debugging on a class-by-class basis (or a
17
+ # module-by-module basis). The trick is to create the debug logger first and
18
+ # then configure the namespace to consolidate all loggers. Since we already
19
+ # created our debug logger, it will be used by the class in question instead
20
+ # of the consolidated namespace logger.
21
+ #
22
+
23
+ require 'logging'
24
+ include Logging.globally
25
+
26
+ Logging.logger.root.appenders = Logging.appenders.stdout
27
+ Logging.logger.root.level = :info
28
+
29
+ # we want to debug the "FooBar" module of ActiveRecord
30
+ Logging.logger['ActiveRecord::FooBar'].level = :debug
31
+
32
+ # and we want everything else in ActiveRecord and ActiveResource
33
+ # to use the same consolidated loggers (one for each namespace)
34
+ Logging.consolidate 'ActiveRecord', 'ActiveResource'
35
+
36
+
37
+ logger.info 'because we included Logging globally, ' \
38
+ 'we have access to a logger anywhere in our code'
39
+
40
+
41
+ module ActiveRecord
42
+ logger.info 'even at the module level'
43
+
44
+ class Base
45
+ logger.info 'and at the class level'
46
+ end
47
+ end
48
+
49
+
50
+ module ActiveResource
51
+ logger.info "you'll notice that these log messages " \
52
+ "are coming from the same logger"
53
+
54
+ class Base
55
+ logger.info "even though the logger is invoked from different classes"
56
+ end
57
+
58
+ class Foo
59
+ def foo
60
+ logger.info "that is because ActiveRecord and ActiveResource " \
61
+ "are consolidating loggers in their respective namespaces"
62
+ end
63
+ end
64
+ Foo.new.foo
65
+ end
66
+
67
+
68
+ module ActiveRecord
69
+ logger.debug 'this debug message will not be logged ' \
70
+ '- level is info'
71
+
72
+ class Base
73
+ logger.debug 'and this debug message will not be logged either ' \
74
+ '- same logger as above'
75
+ end
76
+
77
+ module FooBar
78
+ logger.debug 'however, this debug message WILL be logged'
79
+ end
80
+ end
81
+
data/lib/logging.rb CHANGED
@@ -30,7 +30,7 @@ begin require 'fastthread'; rescue LoadError; end
30
30
  module Logging
31
31
 
32
32
  # :stopdoc:
33
- VERSION = '1.0.0'
33
+ VERSION = '1.1.0'
34
34
  LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
35
35
  PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
36
36
  WIN32 = %r/djgpp|(cyg|ms|bcc)win|mingw/ =~ RUBY_PLATFORM
@@ -172,6 +172,67 @@ module Logging
172
172
  ::Logging::Appenders
173
173
  end
174
174
 
175
+ # call-seq:
176
+ # Logging.consolidate( 'First::Name', 'Second::Name', ... )
177
+ #
178
+ # Consolidate all loggers under the given namespace. All child loggers
179
+ # in the namespace will use the "consolidated" namespace logger instead
180
+ # of creating a new logger for each class or module.
181
+ #
182
+ # If the "root" logger name is passed to this method then all loggers
183
+ # will consolidate to the root logger. In other words, only the root
184
+ # logger will be created, and it will be used by all classes and moduels
185
+ # in the applicaiton.
186
+ #
187
+ # ==== Example
188
+ #
189
+ # Logging.consolidate( 'Foo' )
190
+ #
191
+ # foo = Logging.logger['Foo']
192
+ # bar = Logging.logger['Foo::Bar']
193
+ # baz = Logging.logger['Baz']
194
+ #
195
+ # foo.object_id == bar.object_id #=> true
196
+ # foo.object_id == baz.object_id #=> false
197
+ #
198
+ def consolidate( *args )
199
+ ::Logging::Repository.instance.add_master(*args)
200
+ end
201
+
202
+ # call-seq:
203
+ # include Logging.globally
204
+ # include Logging.globally( :logger )
205
+ #
206
+ # Add a "logger" method to the including context. If included from
207
+ # Object or Kernel, the logger method will be available to all objects.
208
+ #
209
+ # Optionally, a method name can be given and that will be used to
210
+ # provided access to the logger:
211
+ #
212
+ # include Logging.globally( :log )
213
+ # log.info "Just using a shorter method name"
214
+ #
215
+ # If you prefer to use the shorter "log" to access the logger.
216
+ #
217
+ # ==== Example
218
+ #
219
+ # include Logging.globally
220
+ #
221
+ # class Foo
222
+ # logger.debug "Loading the Foo class"
223
+ # def initialize
224
+ # logger.info "Creating some new foo"
225
+ # end
226
+ # end
227
+ #
228
+ # logger.fatal "End of example"
229
+ #
230
+ def globally( name = :logger )
231
+ Module.new {
232
+ eval "def #{name}() @_logging_logger ||= ::Logging::Logger[self] end"
233
+ }
234
+ end
235
+
175
236
  # call-seq:
176
237
  # Logging.init( levels )
177
238
  #
@@ -38,7 +38,7 @@ module Logging::Layouts
38
38
  # conversion characters are
39
39
  #
40
40
  # [c] Used to output the name of the logger that generated the log
41
- # event.
41
+ # event. Supports an optional "precision" described further below.
42
42
  # [d] Used to output the date of the log event. The format of the
43
43
  # date is specified using the :date_pattern option when the Layout
44
44
  # is created. ISO8601 format is assumed if not date pattern is given.
@@ -61,6 +61,11 @@ module Logging::Layouts
61
61
  # more human readable output for multithread application logs.
62
62
  # [%] The sequence '%%' outputs a single percent sign.
63
63
  #
64
+ # The logger name directive 'c' accepts an optional precision that will
65
+ # only print the rightmost number of namespace identifiers for the logger.
66
+ # By default the logger name is printed in full. For example, for the
67
+ # logger name "Foo::Bar::Baz" the pattern %c{2} will output "Bar::Baz".
68
+ #
64
69
  # The directives F, L, and M will only work if the Logger generating the
65
70
  # events is configured to generate tracing information. If this is not
66
71
  # the case these fields will always be empty.
@@ -118,30 +123,31 @@ module Logging::Layouts
118
123
 
119
124
  # Arguments to sprintf keyed to directive letters
120
125
  DIRECTIVE_TABLE = {
121
- 'c' => 'event.logger',
122
- 'd' => 'format_date',
123
- 'F' => 'event.file',
124
- 'l' => '::Logging::LNAMES[event.level]',
125
- 'L' => 'event.line',
126
- 'm' => 'format_obj(event.data)',
127
- 'M' => 'event.method',
128
- 'p' => 'Process.pid',
129
- 'r' => 'Integer((Time.now-@created_at)*1000).to_s',
130
- 't' => 'Thread.current.object_id.to_s',
131
- 'T' => 'Thread.current[:name]',
126
+ 'c' => 'event.logger'.freeze,
127
+ 'd' => 'format_date'.freeze,
128
+ 'F' => 'event.file'.freeze,
129
+ 'l' => '::Logging::LNAMES[event.level]'.freeze,
130
+ 'L' => 'event.line'.freeze,
131
+ 'm' => 'format_obj(event.data)'.freeze,
132
+ 'M' => 'event.method'.freeze,
133
+ 'p' => 'Process.pid'.freeze,
134
+ 'r' => 'Integer((Time.now-@created_at)*1000).to_s'.freeze,
135
+ 't' => 'Thread.current.object_id.to_s'.freeze,
136
+ 'T' => 'Thread.current[:name]'.freeze,
132
137
  '%' => :placeholder
133
- }
138
+ }.freeze
134
139
 
135
140
  # Matches the first directive encountered and the stuff around it.
136
141
  #
137
142
  # * $1 is the stuff before directive or "" if not applicable
138
143
  # * $2 is the %#.# match within directive group
139
144
  # * $3 is the directive letter
140
- # * $4 is the stuff after the directive or "" if not applicable
141
- DIRECTIVE_RGXP = %r/([^%]*)(?:(%-?\d*(?:\.\d+)?)([a-zA-Z%]))?(.*)/m
145
+ # * $4 is the precision specifier for the logger name
146
+ # * $5 is the stuff after the directive or "" if not applicable
147
+ DIRECTIVE_RGXP = %r/([^%]*)(?:(%-?\d*(?:\.\d+)?)([a-zA-Z%])(?:\{(\d+)\})?)?(.*)/m
142
148
 
143
149
  # default date format
144
- ISO8601 = "%Y-%m-%d %H:%M:%S"
150
+ ISO8601 = "%Y-%m-%d %H:%M:%S".freeze
145
151
 
146
152
  # call-seq:
147
153
  # Pattern.create_date_format_methods( pf )
@@ -167,6 +173,7 @@ module Logging::Layouts
167
173
  code << "Time.now.#{pf.date_method}\n"
168
174
  end
169
175
  code << "end\n"
176
+ ::Logging.log_internal(0) {code}
170
177
 
171
178
  pf._meta_eval(code, __FILE__, __LINE__)
172
179
  end
@@ -191,22 +198,33 @@ module Logging::Layouts
191
198
 
192
199
  case m[3]
193
200
  when '%'; code << '%%'
201
+ when 'c'
202
+ code << m[2] + 's'
203
+ args << DIRECTIVE_TABLE[m[3]].dup
204
+ if m[4]
205
+ raise ArgumentError, "logger name precision must be an integer greater than zero: #{m[4]}" unless Integer(m[4]) > 0
206
+ args.last <<
207
+ ".split(::Logging::Repository::PATH_DELIMITER)" \
208
+ ".last(#{m[4]}).join(::Logging::Repository::PATH_DELIMITER)"
209
+ end
194
210
  when *DIRECTIVE_TABLE.keys
195
211
  code << m[2] + 's'
212
+ code << "{#{m[4]}}" if m[4]
196
213
  args << DIRECTIVE_TABLE[m[3]]
197
214
  when nil; break
198
215
  else
199
216
  raise ArgumentError, "illegal format character - '#{m[3]}'"
200
217
  end
201
218
 
202
- break if m[4].empty?
203
- pattern = m[4]
219
+ break if m[5].empty?
220
+ pattern = m[5]
204
221
  end
205
222
 
206
223
  code << '"'
207
224
  code << ', ' + args.join(', ') unless args.empty?
208
225
  code << ")\n"
209
226
  code << "end\n"
227
+ ::Logging.log_internal(0) {code}
210
228
 
211
229
  pf._meta_eval(code, __FILE__, __LINE__)
212
230
  end
@@ -51,9 +51,22 @@ module Logging
51
51
  @mutex.synchronize do
52
52
  logger = repo[name]
53
53
  if logger.nil?
54
- logger = super(name, *args)
55
- repo[name] = logger
56
- repo.children(name).each {|c| c.__send__(:parent=, logger)}
54
+
55
+ master = repo.master_for(name)
56
+ if master
57
+ if repo.has_logger?(master)
58
+ logger = repo[master]
59
+ else
60
+ logger = super(master)
61
+ repo[master] = logger
62
+ repo.children(master).each {|c| c.__send__(:parent=, logger)}
63
+ end
64
+ repo[name] = logger
65
+ else
66
+ logger = super(name)
67
+ repo[name] = logger
68
+ repo.children(name).each {|c| c.__send__(:parent=, logger)}
69
+ end
57
70
  end
58
71
  logger
59
72
  end
@@ -18,6 +18,7 @@ module Logging
18
18
  # +Repository+ instance.
19
19
  #
20
20
  def initialize
21
+ @masters = []
21
22
  @h = {:root => ::Logging::RootLogger.new}
22
23
 
23
24
  # configures the internal logger which is disabled by default
@@ -107,7 +108,7 @@ module Logging
107
108
  # of B is A. Parents are determined by namespace.
108
109
  #
109
110
  def parent( key )
110
- name = _parent_name(to_key(key))
111
+ name = parent_name(to_key(key))
111
112
  return if name.nil?
112
113
  @h[name]
113
114
  end
@@ -126,7 +127,7 @@ module Logging
126
127
 
127
128
  @h.each_pair do |child,logger|
128
129
  next if :root == child
129
- ary << logger if parent == _parent_name(child)
130
+ ary << logger if parent == parent_name(child)
130
131
  end
131
132
  return ary.sort
132
133
  end
@@ -154,7 +155,7 @@ module Logging
154
155
  # Returns the name of the parent for the logger identified by the given
155
156
  # _key_. If the _key_ is for the root logger, then +nil+ is returned.
156
157
  #
157
- def _parent_name( key )
158
+ def parent_name( key )
158
159
  return if :root == key
159
160
 
160
161
  a = key.split PATH_DELIMITER
@@ -166,6 +167,43 @@ module Logging
166
167
  p
167
168
  end
168
169
 
170
+ # call-seq:
171
+ # add_master( 'First::Name', 'Second::Name', ... )
172
+ #
173
+ # Add the given logger names to the list of consolidation masters. All
174
+ # classes in the given namespace(s) will use these loggers instead of
175
+ # creating their own individual loggers.
176
+ #
177
+ def add_master( *args )
178
+ args.map do |key|
179
+ key = to_key(key)
180
+ @masters << key unless @masters.include? key
181
+ key
182
+ end
183
+ end
184
+
185
+ # call-seq:
186
+ # master_for( key )
187
+ #
188
+ # Retruns the consolidation master name for the given _key_. If there is
189
+ # no consolidation master, then +nil+ is returned.
190
+ #
191
+ def master_for( key )
192
+ return if @masters.empty?
193
+ key = to_key(key)
194
+
195
+ loop do
196
+ break key if @masters.include? key
197
+ break nil if :root == key
198
+
199
+ if index = key.rindex(PATH_DELIMITER)
200
+ key = key.slice(0, index)
201
+ else
202
+ key = :root
203
+ end
204
+ end
205
+ end
206
+
169
207
  end # class Repository
170
208
  end # module Logging
171
209
 
@@ -170,6 +170,27 @@ module TestLayouts
170
170
  assert_equal 'tim ', @layout.format(event)
171
171
  end
172
172
 
173
+ def test_pattern_logger_name_precision
174
+ event = Logging::LogEvent.new('Foo', @levels['info'], 'message', false)
175
+
176
+ @layout.pattern = '%c{2}'
177
+ assert_equal 'Foo', @layout.format(event)
178
+
179
+ event.logger = 'Foo::Bar::Baz::Buz'
180
+ assert_equal 'Baz::Buz', @layout.format(event)
181
+
182
+ assert_raise(ArgumentError) {
183
+ @layout.pattern = '%c{0}'
184
+ }
185
+
186
+ @layout.pattern = '%c{foo}'
187
+ event.logger = 'Foo::Bar'
188
+ assert_equal 'Foo::Bar{foo}', @layout.format(event)
189
+
190
+ @layout.pattern = '%m{42}'
191
+ assert_equal 'message{42}', @layout.format(event)
192
+ end
193
+
173
194
  end # class TestBasic
174
195
  end # module TestLayouts
175
196
  end # module TestLogging
@@ -0,0 +1,46 @@
1
+
2
+ require File.join(File.dirname(__FILE__), %w[setup])
3
+
4
+ module TestLogging
5
+
6
+ class TestConsolidate < Test::Unit::TestCase
7
+ include LoggingTestCase
8
+
9
+ def test_root
10
+ Logging.consolidate :root
11
+ root = Logging.logger.root
12
+
13
+ assert_same root, Logging.logger['Foo']
14
+ assert_same root, Logging.logger['Foo::Bar']
15
+ assert_same root, Logging.logger[Array]
16
+ end
17
+
18
+ def test_foo
19
+ Logging.consolidate 'Foo'
20
+ logger = Logging.logger['Foo::Bar::Baz']
21
+
22
+ assert_same Logging.logger['Foo'], logger
23
+ assert_not_same Logging.logger.root, logger
24
+ end
25
+
26
+ def test_many
27
+ Logging.consolidate 'Foo', 'root', 'Foo::Bar::Baz'
28
+
29
+ root = Logging.logger.root
30
+ foo = Logging.logger['Foo']
31
+ fbb = Logging.logger['Foo::Bar::Baz']
32
+
33
+ assert_not_same root, foo
34
+ assert_not_same root, fbb
35
+ assert_not_same foo, fbb
36
+
37
+ assert_same root, Logging.logger[Hash]
38
+ assert_same root, Logging.logger['ActiveRecord::Base']
39
+ assert_same foo, Logging.logger['Foo::Bar']
40
+ assert_same fbb, Logging.logger['Foo::Bar::Baz::Buz']
41
+ end
42
+
43
+ end # class TestConsolidate
44
+ end # module TestLogging
45
+
46
+ # EOF
data/test/test_logger.rb CHANGED
@@ -6,10 +6,6 @@ module TestLogging
6
6
  class TestLogger < Test::Unit::TestCase
7
7
  include LoggingTestCase
8
8
 
9
- def setup
10
- super
11
- end
12
-
13
9
  def test_initialize
14
10
  assert_nothing_raised {::Logging::Logger[:test]}
15
11
  assert_equal ::Logging::Logger[:test], ::Logging::Logger['test']
@@ -120,6 +120,38 @@ module TestLogging
120
120
  assert_equal 'blah', @repo.to_key(:blah)
121
121
  end
122
122
 
123
+ def test_add_master
124
+ ary = @repo.instance_variable_get(:@masters)
125
+ assert true, ary.empty?
126
+
127
+ @repo.add_master 'root'
128
+ assert_equal [:root], ary
129
+
130
+ @repo.add_master Object, 'Foo'
131
+ assert_equal [:root, 'Object', 'Foo'], ary
132
+ end
133
+
134
+ def test_master_for
135
+ assert_nil @repo.master_for('root')
136
+ assert_nil @repo.master_for('Foo::Bar::Baz')
137
+
138
+ @repo.add_master('Foo')
139
+ assert_equal 'Foo', @repo.master_for('Foo')
140
+ assert_equal 'Foo', @repo.master_for('Foo::Bar::Baz')
141
+
142
+ @repo.add_master('Foo::Bar::Baz')
143
+ assert_equal 'Foo', @repo.master_for('Foo')
144
+ assert_equal 'Foo', @repo.master_for('Foo::Bar')
145
+ assert_equal 'Foo::Bar::Baz', @repo.master_for('Foo::Bar::Baz')
146
+ assert_equal 'Foo::Bar::Baz', @repo.master_for('Foo::Bar::Baz::Buz')
147
+
148
+ assert_nil @repo.master_for('Bar::Baz::Buz')
149
+ @repo.add_master 'root'
150
+ assert_equal :root, @repo.master_for('Bar::Baz::Buz')
151
+ assert_equal 'Foo', @repo.master_for('Foo::Bar')
152
+ assert_equal 'Foo::Bar::Baz', @repo.master_for('Foo::Bar::Baz::Buz')
153
+ end
154
+
123
155
  end # class TestRepository
124
156
  end # module TestLogging
125
157
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logging
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tim Pease
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-04-17 00:00:00 -06:00
12
+ date: 2009-04-21 00:00:00 -06:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -62,6 +62,7 @@ files:
62
62
  - data/simple_logging.rb
63
63
  - examples/appenders.rb
64
64
  - examples/classes.rb
65
+ - examples/consolidation.rb
65
66
  - examples/formatting.rb
66
67
  - examples/hierarchies.rb
67
68
  - examples/layouts.rb
@@ -124,6 +125,7 @@ files:
124
125
  - test/layouts/test_yaml.rb
125
126
  - test/setup.rb
126
127
  - test/test_appender.rb
128
+ - test/test_consolidate.rb
127
129
  - test/test_layout.rb
128
130
  - test/test_log_event.rb
129
131
  - test/test_logger.rb
@@ -175,6 +177,7 @@ test_files:
175
177
  - test/layouts/test_pattern.rb
176
178
  - test/layouts/test_yaml.rb
177
179
  - test/test_appender.rb
180
+ - test/test_consolidate.rb
178
181
  - test/test_layout.rb
179
182
  - test/test_log_event.rb
180
183
  - test/test_logger.rb