TwP-logging 0.9.7

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