logging 0.9.4 → 0.9.5

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,13 @@
1
+ == 0.9.5 / 2009-01-25
2
+
3
+ 2 minor enhancements
4
+ - The pattern layout can output the current thread name
5
+ if set using Thread.current[:name] [valodzka]
6
+ - Added buffered logging to all IO based loggers
7
+ (console, file, rolling file)
8
+ 1 bug fix
9
+ - Uncaught TimeoutError in the e-mail appender
10
+
1
11
  == 0.9.4 / 2008-10-04
2
12
 
3
13
  2 minor enhancements
data/README.rdoc CHANGED
@@ -89,6 +89,14 @@ class names.
89
89
  Logging requires the "lockfile" gem to run and the "flexmock" gem to run the
90
90
  tests"
91
91
 
92
+ == DEVELOPMENT REQUIREMENTS
93
+
94
+ The Logging source code relies on the Mr Bones project for default rake tasks.
95
+ You will need to install the Mr Bones gem if you want to build or test the
96
+ logging gem.
97
+
98
+ gem install bones
99
+
92
100
  == LICENSE
93
101
 
94
102
  Ruby
data/Rakefile CHANGED
@@ -1,5 +1,14 @@
1
1
 
2
- load 'tasks/setup.rb'
2
+ begin
3
+ require 'bones'
4
+ Bones.setup
5
+ rescue LoadError
6
+ begin
7
+ load 'tasks/setup.rb'
8
+ rescue LoadError
9
+ raise RuntimeError, '### please install the "bones" gem ###'
10
+ end
11
+ end
3
12
 
4
13
  ensure_in_path 'lib'
5
14
  require 'logging'
@@ -14,11 +23,10 @@ PROJ.url = 'http://logging.rubyforge.org/'
14
23
  PROJ.rubyforge.name = 'logging'
15
24
  PROJ.version = Logging::VERSION
16
25
  PROJ.readme_file = 'README.rdoc'
26
+ PROJ.ignore_file = '.gitignore'
17
27
 
18
- PROJ.exclude << %w[^tags$ ^tasks/archive ^coverage]
28
+ PROJ.exclude << %w[^tags$]
19
29
  PROJ.rdoc.exclude << '^data'
20
- PROJ.rdoc.include << PROJ.readme_file
21
- PROJ.rdoc.main = PROJ.readme_file
22
30
  #PROJ.rdoc.dir = 'doc/rdoc'
23
31
  #PROJ.rdoc.remote_dir = 'rdoc'
24
32
  PROJ.rdoc.dir = 'doc'
data/lib/logging.rb CHANGED
@@ -8,12 +8,16 @@ begin require 'fastthread'; rescue LoadError; end
8
8
 
9
9
  # TODO: Windows Log Service appender
10
10
 
11
+ # TODO: Option to buffer log messages at the appender level
12
+ # extend the concept found in the e-mail appender into the other IO
13
+ # appenders
14
+
11
15
  #
12
16
  #
13
17
  module Logging
14
18
 
15
19
  # :stopdoc:
16
- VERSION = '0.9.4'
20
+ VERSION = '0.9.5'
17
21
  LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
18
22
  PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
19
23
  WIN32 = %r/djgpp|(cyg|ms|bcc)win|mingw/ =~ RUBY_PLATFORM
@@ -219,6 +219,9 @@ class Appender
219
219
  return self if @closed
220
220
  ::Logging::Appender.remove(@name)
221
221
  @closed = true
222
+
223
+ sync {flush}
224
+
222
225
  if footer
223
226
  footer = @layout.footer
224
227
  unless footer.nil? || footer.empty?
@@ -0,0 +1,167 @@
1
+
2
+ module Logging::Appenders
3
+
4
+ # The Buffering module is used to implement buffering of the log messages
5
+ # in a given appender. The size of the buffer can be specified, and the
6
+ # buffer can be configured to auto-flush at a given threshold. The
7
+ # threshold can be a single message or a very large number of messages.
8
+ #
9
+ # Log messages of a certain level can cause the buffer to be flushed
10
+ # immediately. If an error occurs, all previous messages and the error
11
+ # message will be written immediately to the logging destination if the
12
+ # buffer is configured to do so.
13
+ #
14
+ module Buffering
15
+
16
+ # Default buffer size
17
+ #
18
+ DEFAULT_BUFFER_SIZE = 500;
19
+
20
+ # The buffer holding the log messages
21
+ #
22
+ attr_reader :buffer
23
+
24
+ # The auto-flushing setting. When the buffer reaches this size, all
25
+ # messages will be be flushed automatically.
26
+ #
27
+ attr_reader :auto_flushing
28
+
29
+ # This message must be implemented by the including class. The method
30
+ # should write the contents of the buffer to the logging destination,
31
+ # and it should clear the buffer when done.
32
+ #
33
+ def flush
34
+ raise NotImplementedError
35
+ end
36
+
37
+ # Configure the levels that will trigger and immediate flush of the
38
+ # logging buffer. When a log event of the given level is seen, the
39
+ # buffer will be flushed immediately. Only the levels explicitly given
40
+ # in this assignment will flush the buffer; if an "error" message is
41
+ # configured to immediately flush the buffer, a "fatal" message will not
42
+ # even though it is a higher level. Both must be explicitly passed to
43
+ # this assignment.
44
+ #
45
+ # You can pass in a single leveal name or number, and array of level
46
+ # names or numbers, or a string containg a comma separated list of level
47
+ # names or numbers.
48
+ #
49
+ # immediate_at = :error
50
+ # immediate_at = [:error, :fatal]
51
+ # immediate_at = "warn, error"
52
+ #
53
+ def immediate_at=( level )
54
+ @immediate ||= []
55
+ @immediate.clear
56
+
57
+ # get the immediate levels -- no buffering occurs at these levels, and
58
+ # a log message is written to the logging destination immediately
59
+ immediate_at =
60
+ case level
61
+ when String; level.split(',').map {|x| x.strip}
62
+ when Array; level
63
+ else Array(level) end
64
+
65
+ immediate_at.each do |lvl|
66
+ num = ::Logging.level_num(lvl)
67
+ next if num.nil?
68
+ @immediate[num] = true
69
+ end
70
+ end
71
+
72
+ # Configure the auto-flushing period. Auto-flushing is used to flush the
73
+ # contents of the logging buffer to the logging destination
74
+ # automatically when the buffer reaches a certain threshold.
75
+ #
76
+ # By default, the auto-flushing will be configured to flush after each
77
+ # log message.
78
+ #
79
+ # The allowed settings are as follows:
80
+ #
81
+ # N : flush after every N messages (N is an integer)
82
+ # true : flush after each log message
83
+ # false OR
84
+ # nil OR
85
+ # 0 : only flush when the buffer is full (500 messages)
86
+ #
87
+ # If the default buffer size of 500 is too small, you can manuall
88
+ # configure to be as large as you want. This will consume more memory.
89
+ #
90
+ # auto_flushing = 42_000
91
+ #
92
+ def auto_flushing=( period )
93
+ @auto_flushing =
94
+ case period
95
+ when true; 1
96
+ when false, nil, 0; DEFAULT_BUFFER_SIZE
97
+ when Integer; period
98
+ when String; Integer(period)
99
+ else
100
+ raise ArgumentError,
101
+ "unrecognized auto_flushing period: #{period.inspect}"
102
+ end
103
+
104
+ if @auto_flushing < 0
105
+ raise ArgumentError,
106
+ "auto_flushing period cannot be negative: #{period.inspect}"
107
+ end
108
+ end
109
+
110
+
111
+ protected
112
+
113
+ # Configure the buffering using the arguments found in the give options
114
+ # hash. This method must be called in order to use the message buffer.
115
+ # The supported options are "immediate_at" and "auto_flushing". Please
116
+ # refer to the documentation for those methods to see the allowed
117
+ # options.
118
+ #
119
+ def configure_buffering( opts )
120
+ @buffer = []
121
+
122
+ self.immediate_at = opts.getopt(:immediate_at, '')
123
+ self.auto_flushing = opts.getopt(:auto_flushing, true)
124
+ end
125
+
126
+ # Append the given event to the message buffer. The event can be either
127
+ # a string or a LogEvent object.
128
+ #
129
+ def add_to_buffer( event )
130
+ str = event.instance_of?(::Logging::LogEvent) ?
131
+ layout.format(event) : event.to_s
132
+ return if str.empty?
133
+
134
+ buffer << str
135
+ flush if buffer.length >= auto_flushing || immediate?(event)
136
+
137
+ self
138
+ end
139
+
140
+ # Returns true if the _event_ level matches one of the configured
141
+ # immediate logging levels. Otherwise returns false.
142
+ #
143
+ def immediate?( event )
144
+ return false unless event.respond_to? :level
145
+ @immediate[event.level]
146
+ end
147
+
148
+
149
+ private
150
+
151
+ # call-seq:
152
+ # write( event )
153
+ #
154
+ # Writes the given _event_ to the logging destination. The _event_ can
155
+ # be either a LogEvent or a String. If a LogEvent, then it will be
156
+ # formatted using the layout given to the appender when it was created.
157
+ #
158
+ def write( event )
159
+ add_to_buffer event
160
+ self
161
+ end
162
+
163
+
164
+ end # module Buffering
165
+ end # module Logging::Appenders
166
+
167
+ # EOF
@@ -10,23 +10,20 @@ require 'time' # get rfc822 time format
10
10
  module Logging::Appenders
11
11
 
12
12
  class Email < ::Logging::Appender
13
+ include Buffering
13
14
 
14
15
  attr_reader :server, :port, :domain, :acct, :authtype, :subject
15
16
 
17
+ # TODO: make the from/to fields modifiable
18
+ # possibly the subject, too
19
+
16
20
  def initialize( name, opts = {} )
17
21
  super(name, opts)
18
22
 
19
- @buff = []
20
- @buffsize = opts.getopt :buffsize, 100, :as => Integer
21
-
22
- # get the immediate levels -- no buffering occurs at these levels, and
23
- # an e-mail is sent as soon as possible
24
- @immediate = []
25
- opts.getopt(:immediate_at, '').split(',').each do |lvl|
26
- num = ::Logging.level_num(lvl.strip)
27
- next if num.nil?
28
- @immediate[num] = true
29
- end
23
+ af = opts.getopt(:buffsize) ||
24
+ opts.getopt(:buffer_size) ||
25
+ 100
26
+ configure_buffering({:auto_flushing => af}.merge(opts))
30
27
 
31
28
  # get the SMTP parameters
32
29
  @from = opts.getopt(:from)
@@ -51,58 +48,7 @@ class Email < ::Logging::Appender
51
48
  # Create and send an email containing the current message buffer.
52
49
  #
53
50
  def flush
54
- sync { send_mail }
55
- self
56
- end
57
-
58
- # call-seq:
59
- # close( footer = true )
60
- #
61
- # Close the e-mail appender and then flush the message buffer. This will
62
- # ensure that a final e-mail is sent with any remaining messages.
63
- #
64
- def close( footer = true )
65
- super
66
- flush
67
- end
68
-
69
- # cal-seq:
70
- # queued_messages => integer
71
- #
72
- # Returns the number of messages in the buffer.
73
- #
74
- def queued_messages
75
- @buff.length
76
- end
77
-
78
-
79
- private
80
-
81
- # call-seq:
82
- # write( event )
83
- #
84
- # Write the given _event_ to the e-mail message buffer. The log event will
85
- # be processed through the Layout associated with this appender.
86
- #
87
- def write( event )
88
- immediate = false
89
- str = if event.instance_of?(::Logging::LogEvent)
90
- immediate = @immediate[event.level]
91
- @layout.format(event)
92
- else
93
- event.to_s
94
- end
95
- return if str.empty?
96
-
97
- @buff << str
98
- send_mail if @buff.length >= @buffsize || immediate
99
- self
100
- end
101
-
102
- # Connect to the mail server and send out any buffered messages.
103
- #
104
- def send_mail
105
- return if @buff.empty?
51
+ return self if buffer.empty?
106
52
 
107
53
  ### build a mail header for RFC 822
108
54
  rfc822msg = "From: #{@from}\n"
@@ -110,18 +56,17 @@ class Email < ::Logging::Appender
110
56
  rfc822msg << "Subject: #{@subject}\n"
111
57
  rfc822msg << "Date: #{Time.new.rfc822}\n"
112
58
  rfc822msg << "Message-Id: <#{"%.8f" % Time.now.to_f}@#{@domain}>\n\n"
113
- rfc822msg << @buff.join
59
+ rfc822msg << buffer.join
114
60
 
115
61
  ### send email
116
- begin
117
- Net::SMTP.start(*@params) {|smtp| smtp.sendmail(rfc822msg, @from, @to)}
118
- rescue StandardError => err
119
- self.level = :off
120
- ::Logging.log_internal {'e-mail notifications have been disabled'}
121
- ::Logging.log_internal(-2) {err}
122
- ensure
123
- @buff.clear
124
- end
62
+ Net::SMTP.start(*@params) {|smtp| smtp.sendmail(rfc822msg, @from, @to)}
63
+ self
64
+ rescue StandardError, TimeoutError => err
65
+ self.level = :off
66
+ ::Logging.log_internal {'e-mail notifications have been disabled'}
67
+ ::Logging.log_internal(-2) {err}
68
+ ensure
69
+ buffer.clear
125
70
  end
126
71
 
127
72
  end # class Email
@@ -5,6 +5,7 @@ module Logging::Appenders
5
5
  # configured for writing.
6
6
  #
7
7
  class IO < ::Logging::Appender
8
+ include Buffering
8
9
 
9
10
  # call-seq:
10
11
  # IO.new( name, io )
@@ -14,12 +15,14 @@ module Logging::Appenders
14
15
  # stream as the logging destination.
15
16
  #
16
17
  def initialize( name, io, opts = {} )
17
- unless io.respond_to? :print
18
+ unless io.respond_to? :write
18
19
  raise TypeError, "expecting an IO object but got '#{io.class.name}'"
19
20
  end
20
21
 
21
22
  @io = io
22
- @io.sync = true if @io.respond_to?('sync') rescue nil
23
+ @io.sync = true if @io.respond_to?(:sync) rescue nil
24
+
25
+ configure_buffering(opts)
23
26
  super(name, opts)
24
27
  end
25
28
 
@@ -33,7 +36,7 @@ module Logging::Appenders
33
36
  #
34
37
  def close( *args )
35
38
  return self if @io.nil?
36
- super(*args)
39
+ super
37
40
  io, @io = @io, nil
38
41
  io.close unless [STDIN, STDERR, STDOUT].include?(io)
39
42
  rescue IOError => err
@@ -49,29 +52,15 @@ module Logging::Appenders
49
52
  #
50
53
  def flush
51
54
  return self if @io.nil?
55
+ @io.write(buffer.join) unless buffer.empty?
52
56
  @io.flush
53
57
  self
54
- end
55
-
56
-
57
- private
58
-
59
- # call-seq:
60
- # write( event )
61
- #
62
- # Writes the given _event_ to the IO stream. If an +IOError+ is detected,
63
- # than this appender will be turned off and the error reported.
64
- #
65
- def write( event )
66
- begin
67
- str = event.instance_of?(::Logging::LogEvent) ?
68
- @layout.format(event) : event.to_s
69
- return if str.empty?
70
- @io.print str
71
- rescue IOError
72
- self.level = :off
73
- raise
74
- end
58
+ rescue StandardError => err
59
+ self.level = :off
60
+ ::Logging.log_internal {"appender #{name.inspect} has been disabled"}
61
+ ::Logging.log_internal(-2) {err}
62
+ ensure
63
+ buffer.clear
75
64
  end
76
65
 
77
66
  end # class IO