logging 0.5.3 → 0.6.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.
@@ -1,3 +1,10 @@
1
+ == 0.6.0 / 2007-12-26
2
+
3
+ * Using the new 'getopt' method for handling option hashes
4
+ * Rolling file appender is safe for multiple processes
5
+ * Added an e-mail appender from Jeremy Hinegardner
6
+ * Updated tests for the appenders
7
+
1
8
  == 0.5.3 / 2007-12-08
2
9
 
3
10
  * Fixed the quoting for messages sent to the growl appender
@@ -6,6 +6,7 @@ data/logging.yaml
6
6
  lib/logging.rb
7
7
  lib/logging/appender.rb
8
8
  lib/logging/appenders/console.rb
9
+ lib/logging/appenders/email.rb
9
10
  lib/logging/appenders/file.rb
10
11
  lib/logging/appenders/growl.rb
11
12
  lib/logging/appenders/io.rb
@@ -20,15 +21,18 @@ lib/logging/log_event.rb
20
21
  lib/logging/logger.rb
21
22
  lib/logging/repository.rb
22
23
  lib/logging/root_logger.rb
23
- tasks/doc.rake
24
- tasks/gem.rake
25
- tasks/manifest.rake
26
- tasks/rubyforge.rake
27
- tasks/setup.rb
28
- tasks/test.rake
29
- tasks/website.rake
24
+ lib/logging/utils.rb
25
+ rakelib/doc.rake
26
+ rakelib/gem.rake
27
+ rakelib/manifest.rake
28
+ rakelib/rubyforge.rake
29
+ rakelib/setup.rb
30
+ rakelib/test.rake
31
+ rakelib/website.rake
30
32
  test/appenders/test_console.rb
33
+ test/appenders/test_email.rb
31
34
  test/appenders/test_file.rb
35
+ test/appenders/test_growl.rb
32
36
  test/appenders/test_io.rb
33
37
  test/appenders/test_rolling_file.rb
34
38
  test/appenders/test_syslog.rb
@@ -44,3 +48,4 @@ test/test_logger.rb
44
48
  test/test_logging.rb
45
49
  test/test_repository.rb
46
50
  test/test_root_logger.rb
51
+ test/test_utils.rb
data/Rakefile CHANGED
@@ -1,6 +1,6 @@
1
1
  # $Id$
2
2
 
3
- load './tasks/setup.rb'
3
+ load 'rakelib/setup.rb'
4
4
  ensure_in_path 'lib'
5
5
 
6
6
  require 'logging'
@@ -26,4 +26,7 @@ PROJ.exclude << '^(\.\/|\/)?tags$'
26
26
  PROJ.rdoc_exclude << '^(\.\/|\/)?data'
27
27
  PROJ.rdoc_exclude << '^(\.\/|\/)?website'
28
28
 
29
+ depend_on 'flexmock'
30
+ depend_on 'lockfile'
31
+
29
32
  # EOF
@@ -47,7 +47,7 @@ logging_config:
47
47
  - type : File
48
48
  name : logfile
49
49
  level : DEB
50
- filename : 'temp.log'
50
+ filename : 'tmp/temp.log'
51
51
  truncate : true
52
52
  layout:
53
53
  type : Pattern
@@ -1,9 +1,11 @@
1
- # $Id: logging.rb 58 2007-12-05 20:32:54Z tim_pease $
1
+ # $Id: logging.rb 65 2007-12-23 04:48:55Z tim_pease $
2
2
 
3
+ require 'logging/utils'
3
4
  require 'logging/repository'
4
5
 
5
6
  # require all appenders
6
7
  require 'logging/appenders/console'
8
+ require 'logging/appenders/email'
7
9
  require 'logging/appenders/file'
8
10
  require 'logging/appenders/growl'
9
11
  require 'logging/appenders/rolling_file'
@@ -22,7 +24,7 @@ require 'logging/config/yaml_configurator'
22
24
  #
23
25
  module Logging
24
26
 
25
- VERSION = '0.5.3' # :nodoc:
27
+ VERSION = '0.6.0' # :nodoc:
26
28
 
27
29
  LEVELS = {} # :nodoc:
28
30
  LNAMES = {} # :nodoc:
@@ -242,21 +244,6 @@ module Logging
242
244
  when 'off'; LEVELS.length
243
245
  else begin; Integer(l); rescue ArgumentError; LEVELS[l] end end
244
246
  end
245
-
246
- # Helper method for retrieving options from a hash.
247
- def options( opts = {} )
248
- lambda do |*args|
249
- keys, default, ignored = args
250
- catch(:opt) do
251
- Array(keys).each do |key|
252
- [key, key.to_s, key.to_s.intern].each do |k|
253
- throw :opt, opts[k] if opts.has_key?(k)
254
- end
255
- end
256
- default
257
- end
258
- end
259
- end
260
247
  # :startdoc:
261
248
  end
262
249
 
@@ -1,4 +1,4 @@
1
- # $Id: appender.rb 52 2007-11-27 23:53:23Z tim_pease $
1
+ # $Id: appender.rb 67 2007-12-26 16:27:06Z tim_pease $
2
2
 
3
3
  require 'thread'
4
4
  require 'logging'
@@ -38,14 +38,12 @@ module Logging
38
38
  @name = name.to_s
39
39
  @closed = false
40
40
 
41
- layout = opts[:layout] || opts['layout']
42
- self.layout = layout unless layout.nil?
43
- @layout ||= ::Logging::Layouts::Basic.new
44
-
45
- self.level = opts[:level] || opts['level']
41
+ self.layout = opts.getopt(:layout, ::Logging::Layouts::Basic.new)
42
+ self.level = opts.getopt(:level)
46
43
 
47
44
  @mutex = Mutex.new
48
- sync {write(@layout.header)}
45
+ header = @layout.header
46
+ sync {write(header, false)} unless header.nil? || header.empty?
49
47
 
50
48
  ::Logging::Appender[@name] = self
51
49
  end
@@ -62,7 +60,7 @@ module Logging
62
60
  "appender '<#{self.class.name}: #{@name}>' is closed"
63
61
  end
64
62
 
65
- sync {write(@layout.format(event))} unless @level > event.level
63
+ sync {write(event, true)} unless @level > event.level
66
64
  self
67
65
  end
68
66
 
@@ -78,7 +76,7 @@ module Logging
78
76
  "appender '<#{self.class.name}: #{@name}>' is closed"
79
77
  end
80
78
 
81
- sync {write(str)}
79
+ sync {write(str, false)} unless @level >= ::Logging::LEVELS.length
82
80
  self
83
81
  end
84
82
 
@@ -148,7 +146,7 @@ module Logging
148
146
  def close( footer = true )
149
147
  return self if @closed
150
148
  @closed = true
151
- sync {write(@layout.footer)} if footer
149
+ sync {write(@layout.footer, false)} if footer
152
150
  self
153
151
  end
154
152
 
@@ -159,7 +157,9 @@ module Logging
159
157
  # otherwise. When an appender is closed, no more log events can be
160
158
  # written to the logging destination.
161
159
  #
162
- def closed?( ) @closed end
160
+ def closed?
161
+ @closed
162
+ end
163
163
 
164
164
  # call-seq:
165
165
  # flush
@@ -167,18 +167,25 @@ module Logging
167
167
  # Call +flush+ to force an appender to write out any buffered log events.
168
168
  # Similar to IO#flush, so use in a similar fashion.
169
169
  #
170
- def flush( ) self end
170
+ def flush
171
+ self
172
+ end
171
173
 
172
174
 
173
175
  private
174
176
 
175
177
  # call-seq:
176
- # write( str )
178
+ # write( event, do_layout = true )
177
179
  #
178
- # Writes the given string to the logging destination. Subclasses should
179
- # provide an implementation of this method.
180
+ # Writes the given _event_ to the logging destination. Subclasses should
181
+ # provide an implementation of this method. If the _do_layout_ flag is
182
+ # set to +true+, then the event will be formatted using the configured
183
+ # layout object. If set to false, then the event will be stringiied and
184
+ # appended to the logging destination.
180
185
  #
181
- def write( str ) nil end
186
+ def write( event, do_layout = true )
187
+ nil
188
+ end
182
189
 
183
190
  # call-seq:
184
191
  # sync { block }
@@ -1,9 +1,8 @@
1
- # $Id: console.rb 37 2007-10-26 19:12:44Z tim_pease $
1
+ # $Id: console.rb 65 2007-12-23 04:48:55Z tim_pease $
2
2
 
3
3
  require 'logging/appenders/io'
4
4
 
5
- module Logging
6
- module Appenders
5
+ module Logging::Appenders
7
6
 
8
7
  # This class provides an Appender that can write to STDOUT.
9
8
  #
@@ -39,7 +38,6 @@ module Appenders
39
38
  end
40
39
  end # class Stderr
41
40
 
42
- end # module Appenders
43
- end # module Logging
41
+ end # module Logging::Appenders
44
42
 
45
43
  # EOF
@@ -0,0 +1,127 @@
1
+ # $Id: email.rb 68 2007-12-26 18:45:34Z tim_pease $
2
+
3
+ require 'net/smtp'
4
+ require 'time' # get rfc822 time format
5
+
6
+ # a replacement EmailOutputter. This is essentially the default EmailOutptter from Log4r but with the following
7
+ # changes:
8
+ # 1) if there is data to send in an email, then do not send anything
9
+ # 2) connect to the smtp server at the last minute, do not connect at startup and then send later on.
10
+ # 3) Fix the To: field so that it looks alright.
11
+ module Logging::Appenders
12
+
13
+ class Email < ::Logging::Appender
14
+
15
+ attr_reader :server, :port, :domain, :acct, :authtype, :subject
16
+
17
+ def initialize( name, opts = {} )
18
+ super(name, opts)
19
+
20
+ @buff = []
21
+ @buffsize = opts.getopt :buffsize, 100, :as => Integer
22
+
23
+ # get the immediate levels -- no buffering occurs at these levels, and
24
+ # an e-mail is sent as soon as possible
25
+ @immediate = []
26
+ opts.getopt(:immediate_at, '').split(',').each do |lvl|
27
+ num = ::Logging.level_num(lvl.strip)
28
+ next if num.nil?
29
+ @immediate[num] = true
30
+ end
31
+
32
+ # get the SMTP parameters
33
+ @from = opts.getopt(:from)
34
+ raise ArgumentError, 'Must specify from address' if @from.nil?
35
+
36
+ @to = opts.getopt(:to, '').split(',')
37
+ raise ArgumentError, 'Must specify recipients' if @to.empty?
38
+
39
+ @server = opts.getopt :server, 'localhost'
40
+ @port = opts.getopt :port, 25, :as => Integer
41
+ @domain = opts.getopt :domain, ENV['HOSTNAME']
42
+ @acct = opts.getopt :acct
43
+ @passwd = opts.getopt :passwd
44
+ @authtype = opts.getopt :authtype, :cram_md5, :as => Symbol
45
+ @subject = opts.getopt :subject, "Message of #{$0}"
46
+ @params = [@server, @port, @domain, @acct, @passwd, @authtype]
47
+ end
48
+
49
+ # call-seq:
50
+ # flush
51
+ #
52
+ # Create and send an email containing the current message buffer.
53
+ #
54
+ def flush
55
+ sync { send_mail }
56
+ self
57
+ end
58
+
59
+ # call-seq:
60
+ # close( footer = true )
61
+ #
62
+ # Close the e-mail appender and then flush the message buffer. This will
63
+ # ensure that a final e-mail is sent with any remaining messages.
64
+ #
65
+ def close( footer = true )
66
+ super
67
+ flush
68
+ end
69
+
70
+ # cal-seq:
71
+ # queued_messages => integer
72
+ #
73
+ # Returns the number of messages in the buffer.
74
+ #
75
+ def queued_messages
76
+ @buff.length
77
+ end
78
+
79
+
80
+ private
81
+
82
+ # call-seq:
83
+ # write( event, do_layout = true )
84
+ #
85
+ # Write the given _event_ to the e-mail message buffer. The log event will
86
+ # be processed through the Layout associated with this appender.
87
+ #
88
+ # If the _do_layout_ flag is set to false, the _event_ will be converted
89
+ # to a string and then written to the e-mail message buffer "as is" -- no
90
+ # layout formatting will be performed.
91
+ #
92
+ def write( event, do_layout = true )
93
+ str = do_layout ? @layout.format(event) : event.to_s
94
+ @buff << str
95
+ send_mail if @buff.length >= @buffsize ||
96
+ (do_layout && @immediate[event.level])
97
+ self
98
+ end
99
+
100
+ # Connect to the mail server and send out any buffered messages.
101
+ #
102
+ def send_mail
103
+ return if @buff.empty?
104
+
105
+ ### build a mail header for RFC 822
106
+ rfc822msg = "From: #{@from}\n"
107
+ rfc822msg << "To: #{@to.join(",")}\n"
108
+ rfc822msg << "Subject: #{@subject}\n"
109
+ rfc822msg << "Date: #{Time.new.rfc822}\n"
110
+ rfc822msg << "Message-Id: <#{"%.8f" % Time.now.to_f}@#{@domain}>\n\n"
111
+ rfc822msg << @buff.join
112
+
113
+ ### send email
114
+ begin
115
+ Net::SMTP.start(*@params) {|smtp| smtp.sendmail(rfc822msg, @from, @to)}
116
+ rescue Exception => e
117
+ self.level = :off
118
+ # TODO - log that e-mail notification has been turned off
119
+ ensure
120
+ @buff.clear
121
+ end
122
+ end
123
+
124
+ end # class Email
125
+ end # module Logging::Appenders
126
+
127
+ # EOF
@@ -1,4 +1,4 @@
1
- # $Id: file.rb 46 2007-10-31 14:39:01Z tim_pease $
1
+ # $Id: file.rb 62 2007-12-22 20:37:05Z tim_pease $
2
2
 
3
3
  require 'logging/appenders/io'
4
4
 
@@ -8,6 +8,29 @@ module Logging::Appenders
8
8
  #
9
9
  class File < ::Logging::Appenders::IO
10
10
 
11
+ # call-seq:
12
+ # File.assert_valid_logfile( filename ) => true
13
+ #
14
+ # Asserts that the given _filename_ can be used as a log file by ensuring
15
+ # that if the file exists it is a regular file and it is writable. If
16
+ # the file does not exist, then the directory is checked to see if it is
17
+ # writable.
18
+ #
19
+ # An +ArgumentError+ is raised if any of these assertions fail.
20
+ #
21
+ def self.assert_valid_logfile( fn )
22
+ if ::File.exist?(fn)
23
+ if not ::File.file?(fn)
24
+ raise ArgumentError, "#{fn} is not a regular file"
25
+ elsif not ::File.writable?(fn)
26
+ raise ArgumentError, "#{fn} is not writeable"
27
+ end
28
+ elsif not ::File.writable?(::File.dirname(fn))
29
+ raise ArgumentError, "#{::File.dirname(fn)} is not writable"
30
+ end
31
+ true
32
+ end
33
+
11
34
  # call-seq:
12
35
  # File.new( name, :filename => 'file' )
13
36
  # File.new( name, :filename => 'file', :truncate => true )
@@ -20,21 +43,10 @@ module Logging::Appenders
20
43
  # appened to the file.
21
44
  #
22
45
  def initialize( name, opts = {} )
23
- @fn = opts.delete(:filename) || opts.delete('filename') || name
46
+ @fn = opts.getopt(:filename, name)
24
47
  raise ArgumentError, 'no filename was given' if @fn.nil?
25
-
26
- mode = opts.delete(:truncate) || opts.delete('truncate')
27
- mode = mode ? 'w' : 'a'
28
-
29
- if ::File.exist?(@fn)
30
- if not ::File.file?(@fn)
31
- raise ArgumentError, "#{@fn} is not a regular file"
32
- elsif not ::File.writable?(@fn)
33
- raise ArgumentError, "#{@fn} is not writeable"
34
- end
35
- elsif not ::File.writable?(::File.dirname(@fn))
36
- raise ArgumentError, "#{::File.dirname(@fn)} is not writable"
37
- end
48
+ self.class.assert_valid_logfile(@fn)
49
+ mode = opts.getopt(:truncate) ? 'w' : 'a'
38
50
 
39
51
  super(name, ::File.new(@fn, mode), opts)
40
52
  end
@@ -1,9 +1,8 @@
1
- # $Id: growl.rb 59 2007-12-08 23:35:14Z tim_pease $
1
+ # $Id: growl.rb 69 2007-12-26 20:53:33Z tim_pease $
2
2
 
3
3
  require 'logging/appender'
4
4
 
5
- module Logging
6
- module Appenders
5
+ module Logging::Appenders
7
6
 
8
7
  # This class provides an Appender that can send notifications to the Growl
9
8
  # notification system on Mac OS X.
@@ -24,18 +23,22 @@ module Appenders
24
23
 
25
24
  @growl = "growlnotify -w -n \"#{@name}\" -t \"%s\" -m \"%s\" -p %d &"
26
25
 
27
- getopt = ::Logging.options(opts)
28
- @coalesce = getopt[:coalesce, false]
29
- @title_sep = getopt[:separator]
26
+ @coalesce = opts.getopt(:coalesce, false)
27
+ @title_sep = opts.getopt(:separator)
30
28
 
31
29
  # provides a mapping from the default Logging levels
32
30
  # to the Growl notification levels
33
31
  @map = [-2, -1, 0, 1, 2]
34
32
 
35
- map = getopt[:map]
33
+ map = opts.getopt(:map)
36
34
  self.map = map unless map.nil?
37
-
38
35
  setup_coalescing if @coalesce
36
+
37
+ # make sure the growlnotify command can be called
38
+ unless system('growlnotify -v 2>&1 > /dev/null')
39
+ self.level = :off
40
+ # TODO - log that the growl notification is turned off
41
+ end
39
42
  end
40
43
 
41
44
  # call-seq:
@@ -62,50 +65,31 @@ module Appenders
62
65
  @map = map
63
66
  end
64
67
 
65
- # call-seq:
66
- # append( event )
67
- #
68
- # Send the given _event_ to the Growl framework. The log event will be
69
- # processed through the Layout assciated with this appender. The message
70
- # will be logged at the level specified by the event.
71
- #
72
- def append( event )
73
- if closed?
74
- raise RuntimeError,
75
- "appender '<#{self.class.name}: #{@name}>' is closed"
76
- end
77
-
78
- sync do
79
- title = ''
80
- message = @layout.format(event)
81
- priority = @map[event.level]
82
68
 
83
- if @title_sep
84
- title, message = message.split(@title_sep)
85
- title, message = '', title if message.nil?
86
- title.strip!
87
- end
88
-
89
- growl(title, message, priority)
90
- end unless @level > event.level
91
- self
92
- end
69
+ private
93
70
 
94
71
  # call-seq:
95
- # syslog << string
72
+ # write( event, do_layout = true )
96
73
  #
97
- # Write the given _string_ to the Growl framework "as is" -- no
98
- # layout formatting will be performed. The string will be logged at the
99
- # 0 notification level of the Growl framework.
74
+ # Write the given _event_ to the growl notification facility. The log
75
+ # event will be processed through the Layout assciated with this
76
+ # appender if the _do_layout_ flag is set to +true+. The message will be
77
+ # logged at the level specified by the event.
100
78
  #
101
- def <<( str )
102
- if closed?
103
- raise RuntimeError,
104
- "appender '<#{self.class.name}: #{@name}>' is closed"
105
- end
106
-
79
+ # If the _do_layout_ flag is set to +false+, the _event_ will be
80
+ # converted to a string and wirtten to the growl notification facility
81
+ # "as is" -- no layout formatting will be performed. The string will be
82
+ # logged at the 0 notification level of the Growl framework.
83
+ #
84
+ def write( event, do_layout = true )
107
85
  title = ''
108
- message = str
86
+ priority = 0
87
+ message = if do_layout
88
+ priority = @map[event.level]
89
+ @layout.format(event)
90
+ else
91
+ event.to_s
92
+ end
109
93
 
110
94
  if @title_sep
111
95
  title, message = message.split(@title_sep)
@@ -113,13 +97,10 @@ module Appenders
113
97
  title.strip!
114
98
  end
115
99
 
116
- sync {growl(title, message, 0)}
100
+ growl(title, message, priority)
117
101
  self
118
102
  end
119
103
 
120
-
121
- private
122
-
123
104
  # call-seq:
124
105
  # growl_level_num( level ) => integer
125
106
  #
@@ -127,10 +108,7 @@ module Appenders
127
108
  # corresponding Growl notification level number.
128
109
  #
129
110
  def growl_level_num( level )
130
- level = case level
131
- when Integer; level
132
- when String; Integer(level)
133
- else raise ArgumentError, "unkonwn level '#{level}'" end
111
+ level = Integer(level)
134
112
  if level < -2 or level > 2
135
113
  raise ArgumentError, "level '#{level}' is not in range -2..2"
136
114
  end
@@ -205,7 +183,6 @@ module Appenders
205
183
 
206
184
  end # class Growl
207
185
 
208
- end # module Appenders
209
- end # module Logging
186
+ end # module Logging::Appenders
210
187
 
211
188
  # EOF