logging 0.5.3 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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