TwP-logging 0.9.7

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.
Files changed (54) hide show
  1. data/History.txt +169 -0
  2. data/README.rdoc +102 -0
  3. data/Rakefile +42 -0
  4. data/data/bad_logging_1.rb +13 -0
  5. data/data/bad_logging_2.rb +21 -0
  6. data/data/logging.rb +42 -0
  7. data/data/logging.yaml +63 -0
  8. data/data/simple_logging.rb +13 -0
  9. data/lib/logging.rb +408 -0
  10. data/lib/logging/appender.rb +303 -0
  11. data/lib/logging/appenders/buffering.rb +167 -0
  12. data/lib/logging/appenders/console.rb +62 -0
  13. data/lib/logging/appenders/email.rb +75 -0
  14. data/lib/logging/appenders/file.rb +54 -0
  15. data/lib/logging/appenders/growl.rb +197 -0
  16. data/lib/logging/appenders/io.rb +69 -0
  17. data/lib/logging/appenders/rolling_file.rb +291 -0
  18. data/lib/logging/appenders/syslog.rb +201 -0
  19. data/lib/logging/config/configurator.rb +190 -0
  20. data/lib/logging/config/yaml_configurator.rb +195 -0
  21. data/lib/logging/layout.rb +119 -0
  22. data/lib/logging/layouts/basic.rb +34 -0
  23. data/lib/logging/layouts/pattern.rb +296 -0
  24. data/lib/logging/log_event.rb +51 -0
  25. data/lib/logging/logger.rb +490 -0
  26. data/lib/logging/repository.rb +172 -0
  27. data/lib/logging/root_logger.rb +61 -0
  28. data/lib/logging/stats.rb +278 -0
  29. data/lib/logging/utils.rb +130 -0
  30. data/logging.gemspec +41 -0
  31. data/test/appenders/test_buffered_io.rb +183 -0
  32. data/test/appenders/test_console.rb +66 -0
  33. data/test/appenders/test_email.rb +171 -0
  34. data/test/appenders/test_file.rb +93 -0
  35. data/test/appenders/test_growl.rb +128 -0
  36. data/test/appenders/test_io.rb +142 -0
  37. data/test/appenders/test_rolling_file.rb +207 -0
  38. data/test/appenders/test_syslog.rb +194 -0
  39. data/test/benchmark.rb +87 -0
  40. data/test/config/test_configurator.rb +70 -0
  41. data/test/config/test_yaml_configurator.rb +40 -0
  42. data/test/layouts/test_basic.rb +43 -0
  43. data/test/layouts/test_pattern.rb +177 -0
  44. data/test/setup.rb +74 -0
  45. data/test/test_appender.rb +166 -0
  46. data/test/test_layout.rb +110 -0
  47. data/test/test_log_event.rb +80 -0
  48. data/test/test_logger.rb +734 -0
  49. data/test/test_logging.rb +267 -0
  50. data/test/test_repository.rb +126 -0
  51. data/test/test_root_logger.rb +81 -0
  52. data/test/test_stats.rb +274 -0
  53. data/test/test_utils.rb +116 -0
  54. metadata +156 -0
@@ -0,0 +1,69 @@
1
+
2
+ module Logging::Appenders
3
+
4
+ # This class provides an Appender that can write to any IO stream
5
+ # configured for writing.
6
+ #
7
+ class IO < ::Logging::Appender
8
+ include Buffering
9
+
10
+ # call-seq:
11
+ # IO.new( name, io )
12
+ # IO.new( name, io, :layout => layout )
13
+ #
14
+ # Creates a new IO Appender using the given name that will use the _io_
15
+ # stream as the logging destination.
16
+ #
17
+ def initialize( name, io, opts = {} )
18
+ unless io.respond_to? :write
19
+ raise TypeError, "expecting an IO object but got '#{io.class.name}'"
20
+ end
21
+
22
+ @io = io
23
+ @io.sync = true if @io.respond_to?(:sync) rescue nil
24
+
25
+ configure_buffering(opts)
26
+ super(name, opts)
27
+ end
28
+
29
+ # call-seq:
30
+ # close( footer = true )
31
+ #
32
+ # Close the appender and writes the layout footer to the logging
33
+ # destination if the _footer_ flag is set to +true+. Log events will
34
+ # no longer be written to the logging destination after the appender
35
+ # is closed.
36
+ #
37
+ def close( *args )
38
+ return self if @io.nil?
39
+ super
40
+ io, @io = @io, nil
41
+ io.close unless [STDIN, STDERR, STDOUT].include?(io)
42
+ rescue IOError => err
43
+ ensure
44
+ return self
45
+ end
46
+
47
+ # call-seq:
48
+ # flush
49
+ #
50
+ # Call +flush+ to force an appender to write out any buffered log events.
51
+ # Similar to IO#flush, so use in a similar fashion.
52
+ #
53
+ def flush
54
+ return self if @io.nil?
55
+ @io.write(buffer.join) unless buffer.empty?
56
+ @io.flush
57
+ self
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
64
+ end
65
+
66
+ end # class IO
67
+ end # module Logging::Appenders
68
+
69
+ # EOF
@@ -0,0 +1,291 @@
1
+
2
+ require 'lockfile'
3
+
4
+ module Logging::Appenders
5
+
6
+ # An appender that writes to a file and ensures that the file size or age
7
+ # never exceeds some user specified level.
8
+ #
9
+ # The goal of this class is to write log messages to a file. When the file
10
+ # age or size exceeds a given limit then the log file is closed, the name
11
+ # is changed to indicate it is an older log file, and a new log file is
12
+ # created.
13
+ #
14
+ # The name of the log file is changed by inserting the age of the log file
15
+ # (as a single number) between the log file name and the extension. If the
16
+ # file has no extension then the number is appended to the filename. Here
17
+ # is a simple example:
18
+ #
19
+ # /var/log/ruby.log => /var/log/ruby.1.log
20
+ #
21
+ # New log messages will be appended to a newly opened log file of the same
22
+ # name (<tt>/var/log/ruby.log</tt> in our example above). The age number
23
+ # for all older log files is incremented when the log file is rolled. The
24
+ # number of older log files to keep can be given, otherwise all the log
25
+ # files are kept.
26
+ #
27
+ # The actual process of rolling all the log file names can be expensive if
28
+ # there are many, many older log files to process.
29
+ #
30
+ class RollingFile < ::Logging::Appenders::IO
31
+
32
+ # call-seq:
33
+ # RollingFile.new( name, opts )
34
+ #
35
+ # Creates a new Rolling File Appender. The _name_ is the unique Appender
36
+ # name used to retrieve this appender from the Appender hash. The only
37
+ # required option is the filename to use for creating log files.
38
+ #
39
+ # [:filename] The base filename to use when constructing new log
40
+ # filenames.
41
+ #
42
+ # The following options are optional:
43
+ #
44
+ # [:layout] The Layout that will be used by this appender. The Basic
45
+ # layout will be used if none is given.
46
+ # [:truncate] When set to true any existing log files will be rolled
47
+ # immediately and a new, empty log file will be created.
48
+ # [:size] The maximum allowed size (in bytes) of a log file before
49
+ # it is rolled.
50
+ # [:age] The maximum age (in seconds) of a log file before it is
51
+ # rolled. The age can also be given as 'daily', 'weekly',
52
+ # or 'monthly'.
53
+ # [:keep] The number of rolled log files to keep.
54
+ # [:safe] When set to true, extra checks are made to ensure that
55
+ # only once process can roll the log files; this option
56
+ # should only be used when multiple processes will be
57
+ # logging to the same log file (does not work on Windows)
58
+ #
59
+ def initialize( name, opts = {} )
60
+ # raise an error if a filename was not given
61
+ @fn = opts.getopt(:filename, name)
62
+ raise ArgumentError, 'no filename was given' if @fn.nil?
63
+ ::Logging::Appenders::File.assert_valid_logfile(@fn)
64
+
65
+ # grab the information we need to properly roll files
66
+ ext = ::File.extname(@fn)
67
+ bn = ::File.join(::File.dirname(@fn), ::File.basename(@fn, ext))
68
+ @rgxp = %r/\.(\d+)#{Regexp.escape(ext)}\z/
69
+ @glob = "#{bn}.*#{ext}"
70
+ @logname_fmt = "#{bn}.%d#{ext}"
71
+
72
+ # grab our options
73
+ @keep = opts.getopt(:keep, :as => Integer)
74
+ @size = opts.getopt(:size, :as => Integer)
75
+
76
+ @lockfile = if opts.getopt(:safe, false) and !::Logging::WIN32
77
+ ::Lockfile.new(
78
+ @fn + '.lck',
79
+ :retries => 1,
80
+ :timeout => 2
81
+ )
82
+ end
83
+
84
+ code = 'def sufficiently_aged?() false end'
85
+ @age_fn = @fn + '.age'
86
+
87
+ case @age = opts.getopt(:age)
88
+ when 'daily'
89
+ FileUtils.touch(@age_fn) unless test(?f, @age_fn)
90
+ code = <<-CODE
91
+ def sufficiently_aged?
92
+ now = Time.now
93
+ start = ::File.mtime(@age_fn)
94
+ if (now.day != start.day) or (now - start) > 86400
95
+ return true
96
+ end
97
+ false
98
+ end
99
+ CODE
100
+ when 'weekly'
101
+ FileUtils.touch(@age_fn) unless test(?f, @age_fn)
102
+ code = <<-CODE
103
+ def sufficiently_aged?
104
+ if (Time.now - ::File.mtime(@age_fn)) > 604800
105
+ return true
106
+ end
107
+ false
108
+ end
109
+ CODE
110
+ when 'monthly'
111
+ FileUtils.touch(@age_fn) unless test(?f, @age_fn)
112
+ code = <<-CODE
113
+ def sufficiently_aged?
114
+ now = Time.now
115
+ start = ::File.mtime(@age_fn)
116
+ if (now.month != start.month) or (now - start) > 2678400
117
+ return true
118
+ end
119
+ false
120
+ end
121
+ CODE
122
+ when Integer, String
123
+ @age = Integer(@age)
124
+ FileUtils.touch(@age_fn) unless test(?f, @age_fn)
125
+ code = <<-CODE
126
+ def sufficiently_aged?
127
+ if (Time.now - ::File.mtime(@age_fn)) > @age
128
+ return true
129
+ end
130
+ false
131
+ end
132
+ CODE
133
+ end
134
+ meta = class << self; self end
135
+ meta.class_eval code, __FILE__, __LINE__
136
+
137
+ # if the truncate flag was set to true, then roll
138
+ roll_now = opts.getopt(:truncate, false)
139
+ roll_files if roll_now
140
+
141
+ super(name, open_logfile, opts)
142
+ end
143
+
144
+
145
+ private
146
+
147
+ # call-seq:
148
+ # write( event )
149
+ #
150
+ # Write the given _event_ to the log file. The log file will be rolled
151
+ # if the maximum file size is exceeded or if the file is older than the
152
+ # maximum age.
153
+ #
154
+ def write( event )
155
+ str = event.instance_of?(::Logging::LogEvent) ?
156
+ @layout.format(event) : event.to_s
157
+ return if str.empty?
158
+
159
+ check_logfile
160
+ super(str)
161
+
162
+ if roll_required?(str)
163
+ return roll unless @lockfile
164
+
165
+ @lockfile.lock {
166
+ check_logfile
167
+ roll if roll_required?
168
+ }
169
+ end
170
+ end
171
+
172
+ # call-seq:
173
+ # roll
174
+ #
175
+ # Close the currently open log file, roll all the log files, and open a
176
+ # new log file.
177
+ #
178
+ def roll
179
+ @io.close rescue nil
180
+ roll_files
181
+ open_logfile
182
+ end
183
+
184
+ # call-seq:
185
+ # roll_required?( str ) => true or false
186
+ #
187
+ # Returns +true+ if the log file needs to be rolled.
188
+ #
189
+ def roll_required?( str = nil )
190
+ # check if max size has been exceeded
191
+ s = if @size
192
+ @file_size = @stat.size if @stat.size > @file_size
193
+ @file_size += str.size if str
194
+ @file_size > @size
195
+ end
196
+
197
+ # check if max age has been exceeded
198
+ a = sufficiently_aged?
199
+
200
+ return (s || a)
201
+ end
202
+
203
+ # call-seq:
204
+ # roll_files
205
+ #
206
+ # Roll the log files. This is accomplished by renaming the log files
207
+ # starting with the oldest and working towards the youngest.
208
+ #
209
+ # test.10.log => deleted (we are only keeping 10)
210
+ # test.9.log => test.10.log
211
+ # test.8.log => test.9.log
212
+ # ...
213
+ # test.1.log => test.2.log
214
+ #
215
+ # Lastly the current log file is rolled to a numbered log file.
216
+ #
217
+ # test.log => test.1.log
218
+ #
219
+ # This method leaves no <tt>test.log</tt> file when it is done. This
220
+ # file will be created elsewhere.
221
+ #
222
+ def roll_files
223
+ return unless ::File.exist?(@fn)
224
+
225
+ files = Dir.glob(@glob).find_all {|fn| @rgxp =~ fn}
226
+ unless files.empty?
227
+ # sort the files in revese order based on their count number
228
+ files = files.sort do |a,b|
229
+ a = Integer(@rgxp.match(a)[1])
230
+ b = Integer(@rgxp.match(b)[1])
231
+ b <=> a
232
+ end
233
+
234
+ # for each file, roll its count number one higher
235
+ files.each do |fn|
236
+ cnt = Integer(@rgxp.match(fn)[1])
237
+ if @keep and cnt >= @keep
238
+ ::File.delete fn
239
+ next
240
+ end
241
+ ::File.rename fn, sprintf(@logname_fmt, cnt+1)
242
+ end
243
+ end
244
+
245
+ # finally reanme the base log file
246
+ ::File.rename(@fn, sprintf(@logname_fmt, 1))
247
+
248
+ # touch the age file if needed
249
+ FileUtils.touch(@age_fn) if @age
250
+ end
251
+
252
+ # call-seq:
253
+ # open_logfile => io
254
+ #
255
+ # Opens the logfile and stores the current file szie and inode.
256
+ #
257
+ def open_logfile
258
+ @io = ::File.new(@fn, 'a')
259
+ @io.sync = true
260
+
261
+ @stat = ::File.stat(@fn)
262
+ @file_size = @stat.size
263
+ @inode = @stat.ino
264
+
265
+ return @io
266
+ end
267
+
268
+ #
269
+ #
270
+ def check_logfile
271
+ retry_cnt ||= 0
272
+
273
+ if ::File.exist?(@fn) then
274
+ @stat = ::File.stat(@fn)
275
+ return unless @lockfile
276
+ return if @inode == @stat.ino
277
+
278
+ @io.close rescue nil
279
+ end
280
+ open_logfile
281
+ rescue SystemCallError
282
+ raise if retry_cnt > 3
283
+ retry_cnt += 1
284
+ sleep 0.08
285
+ retry
286
+ end
287
+
288
+ end # class RollingFile
289
+ end # module Logging::Appenders
290
+
291
+ # EOF
@@ -0,0 +1,201 @@
1
+
2
+ begin
3
+ require 'syslog'
4
+ HAVE_SYSLOG = true
5
+ rescue LoadError
6
+ HAVE_SYSLOG = false
7
+ end
8
+
9
+ # only load this class if we have the syslog library
10
+ # Windows does not have syslog
11
+ #
12
+ if HAVE_SYSLOG
13
+
14
+ module Logging::Appenders
15
+
16
+ # This class provides an Appender that can write to the UNIX syslog
17
+ # daemon.
18
+ #
19
+ class Syslog < ::Logging::Appender
20
+ include ::Syslog::Constants
21
+
22
+ # call-seq:
23
+ # Syslog.new( name, opts = {} )
24
+ #
25
+ # Create an appender that will log messages to the system message
26
+ # logger. The message is then written to the system console, log files,
27
+ # logged-in users, or forwarded to other machines as appropriate. The
28
+ # options that can be used to configure the appender are as follows:
29
+ #
30
+ # :ident => identifier string (name is used by default)
31
+ # :logopt => options used when opening the connection
32
+ # :facility => the syslog facility to use
33
+ #
34
+ # The parameter :ident is a string that will be prepended to every
35
+ # message. The :logopt argument is a bit field specifying logging
36
+ # options, which is formed by OR'ing one or more of the following
37
+ # values:
38
+ #
39
+ # LOG_CONS If syslog() cannot pass the message to syslogd(8) it
40
+ # wil attempt to write the message to the console
41
+ # ('/dev/console').
42
+ #
43
+ # LOG_NDELAY Open the connection to syslogd(8) immediately. Normally
44
+ # the open is delayed until the first message is logged.
45
+ # Useful for programs that need to manage the order in
46
+ # which file descriptors are allocated.
47
+ #
48
+ # LOG_PERROR Write the message to standard error output as well to
49
+ # the system log.
50
+ #
51
+ # LOG_PID Log the process id with each message: useful for
52
+ # identifying instantiations of daemons.
53
+ #
54
+ # The :facility parameter encodes a default facility to be assigned to
55
+ # all messages that do not have an explicit facility encoded:
56
+ #
57
+ # LOG_AUTH The authorization system: login(1), su(1), getty(8),
58
+ # etc.
59
+ #
60
+ # LOG_AUTHPRIV The same as LOG_AUTH, but logged to a file readable
61
+ # only by selected individuals.
62
+ #
63
+ # LOG_CONSOLE Messages written to /dev/console by the kernel console
64
+ # output driver.
65
+ #
66
+ # LOG_CRON The cron daemon: cron(8).
67
+ #
68
+ # LOG_DAEMON System daemons, such as routed(8), that are not
69
+ # provided for explicitly by other facilities.
70
+ #
71
+ # LOG_FTP The file transfer protocol daemons: ftpd(8), tftpd(8).
72
+ #
73
+ # LOG_KERN Messages generated by the kernel. These cannot be
74
+ # generated by any user processes.
75
+ #
76
+ # LOG_LPR The line printer spooling system: lpr(1), lpc(8),
77
+ # lpd(8), etc.
78
+ #
79
+ # LOG_MAIL The mail system.
80
+ #
81
+ # LOG_NEWS The network news system.
82
+ #
83
+ # LOG_SECURITY Security subsystems, such as ipfw(4).
84
+ #
85
+ # LOG_SYSLOG Messages generated internally by syslogd(8).
86
+ #
87
+ # LOG_USER Messages generated by random user processes. This is
88
+ # the default facility identifier if none is specified.
89
+ #
90
+ # LOG_UUCP The uucp system.
91
+ #
92
+ # LOG_LOCAL0 Reserved for local use. Similarly for LOG_LOCAL1
93
+ # through LOG_LOCAL7.
94
+ #
95
+ def initialize( name, opts = {} )
96
+ ident = opts.getopt(:ident, name)
97
+ logopt = opts.getopt(:logopt, (LOG_PID | LOG_CONS), :as => Integer)
98
+ facility = opts.getopt(:facility, LOG_USER, :as => Integer)
99
+ @syslog = ::Syslog.open(ident, logopt, facility)
100
+
101
+ # provides a mapping from the default Logging levels
102
+ # to the syslog levels
103
+ @map = [LOG_DEBUG, LOG_INFO, LOG_WARNING, LOG_ERR, LOG_CRIT]
104
+
105
+ map = opts.getopt(:map)
106
+ self.map = map unless map.nil?
107
+
108
+ super
109
+ end
110
+
111
+ # call-seq:
112
+ # map = { logging_levels => syslog_levels }
113
+ #
114
+ # Configure the mapping from the Logging levels to the syslog levels.
115
+ # This is needed in order to log events at the proper syslog level.
116
+ #
117
+ # Without any configuration, the following maping will be used:
118
+ #
119
+ # :debug => LOG_DEBUG
120
+ # :info => LOG_INFO
121
+ # :warn => LOG_WARNING
122
+ # :error => LOG_ERR
123
+ # :fatal => LOG_CRIT
124
+ #
125
+ def map=( levels )
126
+ map = []
127
+ levels.keys.each do |lvl|
128
+ num = ::Logging.level_num(lvl)
129
+ map[num] = syslog_level_num(levels[lvl])
130
+ end
131
+ @map = map
132
+ end
133
+
134
+ # call-seq:
135
+ # close
136
+ #
137
+ # Closes the connetion to the syslog facility.
138
+ #
139
+ def close( footer = true )
140
+ super
141
+ @syslog.close
142
+ self
143
+ end
144
+
145
+ # call-seq:
146
+ # closed? => true or false
147
+ #
148
+ # Queries the connection to the syslog facility and returns +true+ if
149
+ # the connection is closed.
150
+ #
151
+ def closed?
152
+ !@syslog.opened?
153
+ end
154
+
155
+
156
+ private
157
+
158
+ # call-seq:
159
+ # write( event )
160
+ #
161
+ # Write the given _event_ to the syslog facility. The log event will be
162
+ # processed through the Layout assciated with this appender. The message
163
+ # will be logged at the level specified by the event.
164
+ #
165
+ def write( event )
166
+ pri = LOG_DEBUG
167
+ message = if event.instance_of?(::Logging::LogEvent)
168
+ pri = @map[event.level]
169
+ @layout.format(event)
170
+ else
171
+ event.to_s
172
+ end
173
+ return if message.empty?
174
+
175
+ @syslog.log(pri, '%s', message)
176
+ self
177
+ end
178
+
179
+ # call-seq:
180
+ # syslog_level_num( level ) => integer
181
+ #
182
+ # Takes the given _level_ as a string, symbol, or integer and returns
183
+ # the corresponding syslog level number.
184
+ #
185
+ def syslog_level_num( level )
186
+ case level
187
+ when Integer; level
188
+ when String, Symbol
189
+ level = level.to_s.upcase
190
+ self.class.const_get level
191
+ else
192
+ raise ArgumentError, "unkonwn level '#{level}'"
193
+ end
194
+ end
195
+
196
+ end # class Syslog
197
+
198
+ end # module Logging::Appenders
199
+ end # HAVE_SYSLOG
200
+
201
+ # EOF