logging 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/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