logging 0.9.8 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/History.txt +13 -0
  2. data/README.rdoc +51 -39
  3. data/Rakefile +1 -0
  4. data/examples/appenders.rb +44 -0
  5. data/examples/classes.rb +39 -0
  6. data/examples/formatting.rb +49 -0
  7. data/examples/hierarchies.rb +71 -0
  8. data/examples/layouts.rb +45 -0
  9. data/examples/loggers.rb +26 -0
  10. data/examples/names.rb +40 -0
  11. data/examples/simple.rb +14 -0
  12. data/lib/logging.rb +50 -21
  13. data/lib/logging/appender.rb +4 -56
  14. data/lib/logging/appenders.rb +120 -0
  15. data/lib/logging/appenders/buffering.rb +2 -1
  16. data/lib/logging/appenders/console.rb +0 -2
  17. data/lib/logging/appenders/rolling_file.rb +0 -2
  18. data/lib/logging/appenders/string_io.rb +1 -3
  19. data/lib/logging/appenders/syslog.rb +0 -7
  20. data/lib/logging/config/configurator.rb +1 -1
  21. data/lib/logging/config/yaml_configurator.rb +3 -7
  22. data/lib/logging/layout.rb +2 -4
  23. data/lib/logging/layouts.rb +47 -0
  24. data/lib/logging/layouts/basic.rb +2 -4
  25. data/lib/logging/layouts/parseable.rb +211 -0
  26. data/lib/logging/layouts/pattern.rb +6 -8
  27. data/lib/logging/log_event.rb +1 -1
  28. data/lib/logging/logger.rb +4 -4
  29. data/lib/spec/logging_helper.rb +2 -2
  30. data/test/appenders/test_buffered_io.rb +26 -18
  31. data/test/appenders/test_console.rb +10 -10
  32. data/test/appenders/test_email.rb +18 -19
  33. data/test/appenders/test_file.rb +12 -12
  34. data/test/appenders/test_growl.rb +11 -12
  35. data/test/appenders/test_io.rb +14 -15
  36. data/test/appenders/test_rolling_file.rb +15 -24
  37. data/test/appenders/test_syslog.rb +10 -10
  38. data/test/layouts/test_basic.rb +4 -5
  39. data/test/layouts/test_json.rb +112 -0
  40. data/test/layouts/test_pattern.rb +9 -9
  41. data/test/layouts/test_yaml.rb +121 -0
  42. data/test/setup.rb +1 -1
  43. data/test/test_appender.rb +0 -14
  44. data/test/test_log_event.rb +1 -1
  45. data/test/test_logging.rb +3 -3
  46. metadata +17 -2
@@ -1,6 +1,4 @@
1
1
 
2
- require 'yaml'
3
-
4
2
  module Logging
5
3
 
6
4
  # The +Layout+ class provides methods for formatting log events into a
@@ -30,6 +28,8 @@ class Layout
30
28
  # then <tt>:string</tt> is used.
31
29
  #
32
30
  def initialize( opts = {} )
31
+ ::Logging.init unless ::Logging.const_defined? :MAX_LEVEL_LENGTH
32
+
33
33
  default = ::Logging.const_defined?('OBJ_FORMAT') ?
34
34
  ::Logging::OBJ_FORMAT : nil
35
35
 
@@ -114,6 +114,4 @@ class Layout
114
114
  end # class Layout
115
115
  end # module Logging
116
116
 
117
- Logging.require_all_libs_relative_to(__FILE__, 'layouts')
118
-
119
117
  # EOF
@@ -0,0 +1,47 @@
1
+
2
+ module Logging
3
+ module Layouts
4
+
5
+ # Accessor / Factory for the Basic layout.
6
+ #
7
+ def basic( *args )
8
+ return ::Logging::Layouts::Basic if args.empty?
9
+ ::Logging::Layouts::Basic.new(*args)
10
+ end
11
+
12
+ # Accessor / Factory for the Pattern layout.
13
+ #
14
+ def pattern( *args )
15
+ return ::Logging::Layouts::Pattern if args.empty?
16
+ ::Logging::Layouts::Pattern.new(*args)
17
+ end
18
+
19
+ # Accessor for the Parseable layout.
20
+ #
21
+ def parseable
22
+ ::Logging::Layouts::Parseable
23
+ end
24
+
25
+ # Factory for the Parseable layout using JSON formatting.
26
+ #
27
+ def json( *args )
28
+ ::Logging::Layouts::Parseable.json(*args)
29
+ end
30
+
31
+ # Factory for the Parseable layout using YAML formatting.
32
+ #
33
+ def yaml( *args )
34
+ ::Logging::Layouts::Parseable.yaml(*args)
35
+ end
36
+
37
+ extend self
38
+ end # module Layouts
39
+ end # module Logging
40
+
41
+
42
+ %w[basic parseable pattern].
43
+ each do |fn|
44
+ require ::Logging.libpath('logging', 'layouts', fn)
45
+ end
46
+
47
+ # EOF
@@ -1,6 +1,5 @@
1
1
 
2
- module Logging
3
- module Layouts
2
+ module Logging::Layouts
4
3
 
5
4
  # The +Basic+ layout class provides methods for simple formatting of log
6
5
  # events. The resulting string follows the format below.
@@ -28,7 +27,6 @@ module Layouts
28
27
  end
29
28
 
30
29
  end # class Basic
31
- end # module Layouts
32
- end # module Logging
30
+ end # module Logging::Layouts
33
31
 
34
32
  # EOF
@@ -0,0 +1,211 @@
1
+
2
+ module Logging::Layouts
3
+
4
+ # This layout will produce parseable log output in either JSON or YAML
5
+ # format. This makes it much easier for machines to parse log files and
6
+ # perform analysis on those logs.
7
+ #
8
+ # The information about the log event can be configured when the layout is
9
+ # created. Any or all of the following labels can be set as the _items_ to
10
+ # log:
11
+ #
12
+ # 'logger' Used to output the name of the logger that generated the
13
+ # log event.
14
+ # 'timestamp' Used to output the timestamp of the log event.
15
+ # 'level' Used to output the level of the log event.
16
+ # 'message' Used to output the application supplied message
17
+ # associated with the log event.
18
+ # 'file' Used to output the file name where the logging request
19
+ # was issued.
20
+ # 'line' Used to output the line number where the logging request
21
+ # was issued.
22
+ # 'method' Used to output the method name where the logging request
23
+ # was issued.
24
+ # 'pid' Used to output the process ID of the currently running
25
+ # program.
26
+ # 'millis' Used to output the number of milliseconds elapsed from
27
+ # the construction of the Layout until creation of the log
28
+ # event.
29
+ # 'thread_id' Used to output the object ID of the thread that generated
30
+ # the log event.
31
+ # 'thread' Used to output the name of the thread that generated the
32
+ # log event. Name can be specified using Thread.current[:name]
33
+ # notation. Output empty string if name not specified. This
34
+ # option helps to create more human readable output for
35
+ # multithread application logs.
36
+ #
37
+ # These items are supplied to the layout as an array of strings. The items
38
+ # 'file', 'line', and 'method' will only work if the Logger generating the
39
+ # events is configured to generate tracing information. If this is not the
40
+ # case these fields will always be empty.
41
+ #
42
+ # When configured to output log events in YAML format, each log message
43
+ # will be formatted as a hash in it's own YAML document. The hash keys are
44
+ # the name of the item, and the value is what you would expect it to be.
45
+ # Therefore, for the default set of times log message would appear as
46
+ # follows:
47
+ #
48
+ # ---
49
+ # timestamp: 2009-04-17 16:15:42
50
+ # level: INFO
51
+ # logger: Foo::Bar
52
+ # message: this is a log message
53
+ # ---
54
+ # timestamp: 2009-04-17 16:15:43
55
+ # level: ERROR
56
+ # logger: Foo
57
+ # message: <RuntimeError> Oooops!!
58
+ #
59
+ # The output order of the fields is not guaranteed to be the same as the
60
+ # order specified in the _items_ list. This is because Ruby hashes are not
61
+ # ordered by default (unless your running this in Ruby 1.9).
62
+ #
63
+ # When configured to output log events in JSON format, each log message
64
+ # will be formatted as an object (in the JSON sense of the work) on it's
65
+ # own line in the log output. Therefore, to parse the output you must read
66
+ # it line by line and parse the individual objects. Taking the same
67
+ # example above the JSON output would be:
68
+ #
69
+ # {"timestamp":"2009-04-17 16:15:42","level":"INFO","logger":"Foo::Bar","message":"this is a log message"}
70
+ # {"timestamp":"2009-04-17 16:15:43","level":"ERROR","logger":"Foo","message":"<RuntimeError> Oooops!!"}
71
+ #
72
+ # The output order of the fields is guaranteed to be the same as the order
73
+ # specified in the _items_ list.
74
+ #
75
+ class Parseable < ::Logging::Layout
76
+
77
+ # :stopdoc:
78
+ # Arguments to sprintf keyed to directive letters
79
+ DIRECTIVE_TABLE = {
80
+ 'logger' => 'event.logger',
81
+ 'timestamp' => 'Time.now.strftime(Pattern::ISO8601)',
82
+ 'level' => '::Logging::LNAMES[event.level]',
83
+ 'message' => 'format_obj(event.data)',
84
+ 'file' => 'event.file',
85
+ 'line' => 'event.line',
86
+ 'method' => 'event.method',
87
+ 'pid' => 'Process.pid',
88
+ 'millis' => 'Integer((Time.now-@created_at)*1000)',
89
+ 'thread_id' => 'Thread.current.object_id',
90
+ 'thread' => 'Thread.current[:name]'
91
+ }
92
+
93
+ # call-seq:
94
+ # Pattern.create_yaml_format_methods( layout )
95
+ #
96
+ # This method will create the +format+ method in the given Parseable
97
+ # _layout_ based on the configured items for the layout instance.
98
+ #
99
+ def self.create_yaml_format_method( layout )
100
+ code = "undef :format if method_defined? :format\n"
101
+ code << "def format( event )\nstr = {\n"
102
+
103
+ code << layout.items.map {|name|
104
+ "'#{name}' => #{Parseable::DIRECTIVE_TABLE[name]}"
105
+ }.join(",\n")
106
+ code << "\n}.to_yaml\nreturn str\nend\n"
107
+
108
+ (class << layout; self end).class_eval(code, __FILE__, __LINE__)
109
+ end
110
+
111
+ # call-seq:
112
+ # Pattern.create_json_format_methods( layout )
113
+ #
114
+ # This method will create the +format+ method in the given Parseable
115
+ # _layout_ based on the configured items for the layout instance.
116
+ #
117
+ def self.create_json_format_method( layout )
118
+ code = "undef :format if method_defined? :format\n"
119
+ code << "def format( event )\n\"{"
120
+
121
+ args = []
122
+ code << layout.items.map {|name|
123
+ args << "format_as_json(#{Parseable::DIRECTIVE_TABLE[name]})"
124
+ "\\\"#{name}\\\":%s"
125
+ }.join(',')
126
+ code << "}\\n\" % [#{args.join(', ')}]\nend"
127
+
128
+ (class << layout; self end).class_eval(code, __FILE__, __LINE__)
129
+ end
130
+ # :startdoc:
131
+
132
+ # call-seq:
133
+ # Parseable.json( opts )
134
+ #
135
+ # Create a new Parseable layout that outputs log events usig JSON style
136
+ # formatting. See the initializer documentation for available options.
137
+ #
138
+ def self.json( opts = {} )
139
+ opts[:style] = 'json'
140
+ new(opts)
141
+ end
142
+
143
+ # call-seq:
144
+ # Parseable.yaml( opts )
145
+ #
146
+ # Create a new Parseable layout that outputs log events usig YAML style
147
+ # formatting. See the initializer documentation for available options.
148
+ #
149
+ def self.yaml( opts = {} )
150
+ opts[:style] = 'yaml'
151
+ new(opts)
152
+ end
153
+
154
+ # call-seq:
155
+ # Parseable.new( opts )
156
+ #
157
+ # Creates a new Parseable layout using the following options:
158
+ #
159
+ # :style => :json or :yaml
160
+ # :items => %w[timestamp level logger message]
161
+ #
162
+ def initialize( opts = {} )
163
+ super
164
+ @created_at = Time.now
165
+ @style = opts.getopt(:style, 'json').to_s.intern
166
+ self.items = opts.getopt(:items, %w[timestamp level logger message])
167
+ end
168
+
169
+ attr_reader :items
170
+
171
+ # call-seq:
172
+ # layout.items = %w[timestamp level logger message]
173
+ #
174
+ # Set the log event items that will be formatted by this layout. These
175
+ # items, and only these items, will appear in the log output.
176
+ #
177
+ def items=( ary )
178
+ @items = Array(ary).map {|name| name.to_s.downcase}
179
+ valid = DIRECTIVE_TABLE.keys
180
+ @items.each do |name|
181
+ raise ArgumentError, "unknown item - #{name.inspect}" unless valid.include? name
182
+ end
183
+ create_format_method
184
+ end
185
+
186
+
187
+ private
188
+
189
+ # Take the given _value_ and format it into a JSON compatible string.
190
+ #
191
+ def format_as_json( value )
192
+ case value
193
+ when String, Integer, Float; value.inspect
194
+ when nil; 'null'
195
+ else value.to_s.inspect end
196
+ end
197
+
198
+ # Call the appropriate class level create format method based on the
199
+ # style of this parseable layout.
200
+ #
201
+ def create_format_method
202
+ case @style
203
+ when :json; Parseable.create_json_format_method(self)
204
+ when :yaml; Parseable.create_yaml_format_method(self)
205
+ else raise ArgumentError, "unknown format style '#@style'" end
206
+ end
207
+
208
+ end # class Parseable
209
+ end # module Logging::Layouts
210
+
211
+ # EOF
@@ -1,6 +1,5 @@
1
1
 
2
- module Logging
3
- module Layouts
2
+ module Logging::Layouts
4
3
 
5
4
  # A flexible layout configurable with pattern string.
6
5
  #
@@ -22,7 +21,7 @@ module Layouts
22
21
  # Let the conversion pattern be "%-5l [%c]: %m\n" and assume that the
23
22
  # logging environment was set to use a Pattern layout. Then the statements
24
23
  #
25
- # root = Logging::Logger[:root]
24
+ # root = Logging.logger[:root]
26
25
  # root.debug("Message 1")
27
26
  # root.warn("Message 2")
28
27
  #
@@ -57,9 +56,9 @@ module Layouts
57
56
  # [t] Used to output the object ID of the thread that generated the
58
57
  # log event.
59
58
  # [T] Used to output the name of the thread that generated the log event.
60
- # Name can be specified using Thread.current[:name] notation. Output empty
61
- # string if name not specified. This options helps to create more human
62
- # readable output for multithread application log.
59
+ # Name can be specified using Thread.current[:name] notation. Output
60
+ # empty string if name not specified. This option helps to create
61
+ # more human readable output for multithread application logs.
63
62
  # [%] The sequence '%%' outputs a single percent sign.
64
63
  #
65
64
  # The directives F, L, and M will only work if the Logger generating the
@@ -290,7 +289,6 @@ module Layouts
290
289
  # :startdoc:
291
290
 
292
291
  end # class Pattern
293
- end # module Layouts
294
- end # module Logging
292
+ end # module Logging::Layouts
295
293
 
296
294
  # EOF
@@ -37,7 +37,7 @@ module Logging
37
37
 
38
38
  m = CALLER_RGXP.match(t)
39
39
  @file = m[1]
40
- @line = m[2]
40
+ @line = Integer(m[2])
41
41
  @method = m[3] unless m[3].nil?
42
42
  end
43
43
  end
@@ -12,8 +12,8 @@ module Logging
12
12
  #
13
13
  # Example:
14
14
  #
15
- # log = Logging::Logger['my logger']
16
- # log.add_appenders( Logging::Appender.stdout ) # append to STDOUT
15
+ # log = Logging.logger['my logger']
16
+ # log.add_appenders( Logging.appenders.stdout ) # append to STDOUT
17
17
  # log.level = :info # log 'info' and above
18
18
  #
19
19
  # log.info 'starting foo operation'
@@ -312,8 +312,8 @@ module Logging
312
312
  #
313
313
  def add_appenders( *args )
314
314
  args.flatten.each do |arg|
315
- o = arg.kind_of?(::Logging::Appender) ? arg : ::Logging::Appender[arg]
316
- raise ArgumentError, "unknown appender '#{arg}'" if o.nil?
315
+ o = arg.kind_of?(::Logging::Appender) ? arg : ::Logging::Appenders[arg.to_s]
316
+ raise ArgumentError, "unknown appender #{arg.inspect}" if o.nil?
317
317
  @appenders << o unless @appenders.include?(o)
318
318
  end
319
319
  self
@@ -11,7 +11,7 @@ module Spec
11
11
  to = opts.getopt(:to, '__rspec__')
12
12
  exclusive = opts.getopt(:exclusive, true)
13
13
 
14
- appender = Logging::Appender[to] || Logging::Appenders::StringIo.new(to)
14
+ appender = Logging::Appenders[to] || Logging::Appenders::StringIo.new(to)
15
15
  logger = Logging::Logger[from]
16
16
  if exclusive
17
17
  logger.appenders = appender
@@ -20,7 +20,7 @@ module Spec
20
20
  end
21
21
 
22
22
  before(:all) do
23
- @log_output = Logging::Appender[to]
23
+ @log_output = Logging::Appenders[to]
24
24
  end
25
25
 
26
26
  before(:each) do
@@ -9,20 +9,18 @@ module TestAppenders
9
9
 
10
10
  def setup
11
11
  super
12
- ::Logging.init
13
- @levels = ::Logging::LEVELS
14
-
15
- @appender = ::Logging::Appenders::StringIo.new(
12
+ @appender = Logging.appenders.string_io(
16
13
  'test_appender', :auto_flushing => 3, :immediate_at => :error
17
14
  )
15
+ @appender.clear
18
16
  @sio = @appender.sio
19
-
17
+ @levels = Logging::LEVELS
20
18
  begin readline rescue EOFError end
21
19
  end
22
20
 
23
21
  def test_append
24
- event = ::Logging::LogEvent.new('TestLogger', @levels['warn'],
25
- [1, 2, 3, 4], false)
22
+ event = Logging::LogEvent.new('TestLogger', @levels['warn'],
23
+ [1, 2, 3, 4], false)
26
24
  @appender.append event
27
25
  assert_nil(readline)
28
26
 
@@ -45,15 +43,15 @@ module TestAppenders
45
43
  def test_append_error
46
44
  # setup an internal logger to capture error messages from the IO
47
45
  # appender
48
- log = Logging::Appenders::StringIo.new('__internal_io')
49
- Logging::Logger[Logging].add_appenders(log)
50
- Logging::Logger[Logging].level = 'all'
46
+ log = Logging.appenders.string_io('__internal_io')
47
+ Logging.logger[Logging].add_appenders(log)
48
+ Logging.logger[Logging].level = 'all'
51
49
 
52
50
 
53
51
  # close the string IO object so we get an error
54
52
  @sio.close
55
- event = ::Logging::LogEvent.new('TestLogger', @levels['warn'],
56
- [1, 2, 3, 4], false)
53
+ event = Logging::LogEvent.new('TestLogger', @levels['warn'],
54
+ [1, 2, 3, 4], false)
57
55
  @appender.append event
58
56
  assert_nil(log.readline)
59
57
 
@@ -68,6 +66,16 @@ module TestAppenders
68
66
  assert_equal 5, @appender.level
69
67
  end
70
68
 
69
+ def test_auto_flushing
70
+ assert_raise(ArgumentError) {
71
+ @appender.auto_flushing = Object.new
72
+ }
73
+
74
+ assert_raise(ArgumentError) {
75
+ @appender.auto_flushing = -1
76
+ }
77
+ end
78
+
71
79
  def test_close
72
80
  assert_equal false, @sio.closed?
73
81
  assert_equal false, @appender.closed?
@@ -77,7 +85,7 @@ module TestAppenders
77
85
  assert_equal true, @appender.closed?
78
86
 
79
87
  [STDIN, STDERR, STDOUT].each do |io|
80
- @appender = ::Logging::Appenders::IO.new 'test', io
88
+ @appender = Logging.appenders.io('test', io)
81
89
  @appender.close
82
90
  assert_equal false, io.closed?
83
91
  assert_equal true, @appender.closed?
@@ -105,9 +113,9 @@ module TestAppenders
105
113
  def test_concat_error
106
114
  # setup an internal logger to capture error messages from the IO
107
115
  # appender
108
- log = Logging::Appenders::StringIo.new('__internal_io')
109
- Logging::Logger[Logging].add_appenders(log)
110
- Logging::Logger[Logging].level = 'all'
116
+ log = Logging.appenders.string_io('__internal_io')
117
+ Logging.logger[Logging].add_appenders(log)
118
+ Logging.logger[Logging].level = 'all'
111
119
 
112
120
  # close the string IO object so we get an error
113
121
  @sio.close
@@ -141,8 +149,8 @@ module TestAppenders
141
149
  end
142
150
 
143
151
  def test_immediate_at
144
- event = ::Logging::LogEvent.new('TestLogger', @levels['warn'],
145
- [1, 2, 3, 4], false)
152
+ event = Logging::LogEvent.new('TestLogger', @levels['warn'],
153
+ [1, 2, 3, 4], false)
146
154
  @appender.append event
147
155
  assert_nil(readline)
148
156