logging 1.2.3 → 1.3.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
+ == 1.3.0 / 2009-12-18
2
+
3
+ Minor Enhancements
4
+ - Using copy/truncate semantics for a faster RollingFile appender
5
+ - Global reopen method for using Logging in forked environments
6
+ - RollingFile appender can use date stamps instead of numbers
7
+
1
8
  == 1.2.3 / 2009-12-01
2
9
 
3
10
  1 bug fix
data/Rakefile CHANGED
@@ -29,7 +29,6 @@ Bones {
29
29
  enable_sudo
30
30
 
31
31
  depend_on 'little-plugger'
32
- depend_on 'lockfile'
33
32
  depend_on 'flexmock', :development => true
34
33
  depend_on 'bones-git', :development => true
35
34
  depend_on 'bones-extras', :development => true
@@ -0,0 +1,37 @@
1
+ # :stopdoc:
2
+ #
3
+ # Because of the global interpreter lock, Kernel#fork is the best way
4
+ # to acheive true concurrency in Ruby scripts. However, there are pecularities
5
+ # when using frok and passing file descriptors between process. These
6
+ # pecularities affect the logging framework.
7
+ #
8
+ # In short, always reopen file descriptors in the child process after fork has
9
+ # been called. The RollingFile appender uses flock to safely coordinate the
10
+ # rolling of the log file when multiple processes are writing to the same
11
+ # file. If the file descriptor is opened in the parent and multiple children
12
+ # are forked, then each child will use the same file descriptor lock; when one
13
+ # child locks the file any other child will also have the lock. This creates a
14
+ # race condition in the rolling code. The solution is to reopen the file to
15
+ # obtain a new file descriptor in each of the children.
16
+ #
17
+
18
+ require 'logging'
19
+
20
+ log = Logging.logger['example']
21
+ log.add_appenders(
22
+ Logging.appenders.rolling_file('roller.log', :age => 'daily')
23
+ )
24
+ log.level = :debug
25
+
26
+ # Create four child processes and reopen the "roller.log" file descriptor in
27
+ # each child. Now log rolling will work safely.
28
+ 4.times do
29
+ fork {
30
+ Logging.reopen
31
+ log.info "This is child process #{Process.pid}"
32
+ }
33
+ end
34
+
35
+ log.info "This is the parent process #{Process.pid}"
36
+
37
+ # :startdoc:
@@ -11,9 +11,7 @@ require 'stringio'
11
11
  require 'thread'
12
12
  require 'little-plugger'
13
13
 
14
- HAVE_LOCKFILE = require? 'lockfile'
15
- HAVE_SYSLOG = require? 'syslog'
16
-
14
+ HAVE_SYSLOG = require? 'syslog'
17
15
 
18
16
  #
19
17
  #
@@ -21,7 +19,7 @@ module Logging
21
19
  extend LittlePlugger
22
20
 
23
21
  # :stopdoc:
24
- VERSION = '1.2.3'
22
+ VERSION = '1.3.0'
25
23
  LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
26
24
  PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
27
25
  LEVELS = {}
@@ -83,7 +81,7 @@ module Logging
83
81
  # The format of the log messages can be changed using a few optional
84
82
  # parameters. The <tt>:pattern</tt> can be used to change the log
85
83
  # message format. The <tt>:date_pattern</tt> can be used to change how
86
- # timestamps are formatted.
84
+ # timestamps are formatted.
87
85
  #
88
86
  # log = Logging.logger(STDOUT,
89
87
  # :pattern => "[%d] %-5l : %m\n",
@@ -131,7 +129,7 @@ module Logging
131
129
  case dev
132
130
  when String
133
131
  ::Logging::Appenders::RollingFile.new(name, a_opts)
134
- else
132
+ else
135
133
  ::Logging::Appenders::IO.new(name, dev, a_opts)
136
134
  end
137
135
 
@@ -163,6 +161,15 @@ module Logging
163
161
  ::Logging::Appenders
164
162
  end
165
163
 
164
+ # Reopen all appenders. This method should be called immediately after a
165
+ # fork to ensure no conflict with file descriptors and calls to fcntl or
166
+ # flock.
167
+ #
168
+ def reopen
169
+ log_internal {'re-opening all appenders'}
170
+ ::Logging::Appenders.each {|appender| appender.reopen}
171
+ end
172
+
166
173
  # call-seq:
167
174
  # Logging.consolidate( 'First::Name', 'Second::Name', ... )
168
175
  #
@@ -272,7 +279,7 @@ module Logging
272
279
  args.each do |lvl|
273
280
  lvl = levelify lvl
274
281
  unless levels.has_key?(lvl) or lvl == 'all' or lvl == 'off'
275
- levels[lvl] = id
282
+ levels[lvl] = id
276
283
  names[id] = lvl.upcase
277
284
  id += 1
278
285
  end
@@ -286,7 +293,7 @@ module Logging
286
293
 
287
294
  levels.keys
288
295
  end
289
-
296
+
290
297
  # call-seq:
291
298
  # Logging.format_as( obj_format )
292
299
  #
@@ -196,6 +196,15 @@ class Appender
196
196
  @closed
197
197
  end
198
198
 
199
+ # Reopen the connection to the underlying logging destination. If the
200
+ # connection is currently closed then it will be opened. If the connection
201
+ # is currently open then it will be closed and immediately opened.
202
+ #
203
+ def reopen
204
+ @closed = false
205
+ self
206
+ end
207
+
199
208
  # call-seq:
200
209
  # flush
201
210
  #
@@ -41,7 +41,7 @@ class Email < ::Logging::Appender
41
41
  @subject = opts.getopt :subject, "Message of #{$0}"
42
42
  @params = [@server, @port, @domain, @acct, @passwd, @authtype]
43
43
  end
44
-
44
+
45
45
  # call-seq:
46
46
  # flush
47
47
  #
@@ -43,15 +43,32 @@ module Logging::Appenders
43
43
  @fn = opts.getopt(:filename, name)
44
44
  raise ArgumentError, 'no filename was given' if @fn.nil?
45
45
  self.class.assert_valid_logfile(@fn)
46
- mode = opts.getopt(:truncate) ? 'w' : 'a'
46
+ @mode = opts.getopt(:truncate) ? 'w' : 'a'
47
47
 
48
- super(name, ::File.new(@fn, mode), opts)
48
+ super(name, ::File.new(@fn, @mode), opts)
49
49
  end
50
50
 
51
51
  # Returns the path to the logfile.
52
52
  #
53
53
  def filename() @fn.dup end
54
54
 
55
+ # Reopen the connection to the underlying logging destination. If the
56
+ # connection is currently closed then it will be opened. If the connection
57
+ # is currently open then it will be closed and immediately opened.
58
+ #
59
+ def reopen
60
+ @mutex.synchronize {
61
+ if defined? @io and @io
62
+ flush
63
+ @io.close rescue nil
64
+ end
65
+ @closed = false
66
+ @io = ::File.new(@fn, @mode)
67
+ @io.sync = true
68
+ }
69
+ self
70
+ end
71
+
55
72
  end # class FileAppender
56
73
  end # module Logging::Appenders
57
74
 
@@ -5,9 +5,8 @@ module Logging::Appenders
5
5
  # never exceeds some user specified level.
6
6
  #
7
7
  # The goal of this class is to write log messages to a file. When the file
8
- # age or size exceeds a given limit then the log file is closed, the name
9
- # is changed to indicate it is an older log file, and a new log file is
10
- # created.
8
+ # age or size exceeds a given limit then the log file is copied and then
9
+ # truncated. The name of the copy indicates it is an older log file.
11
10
  #
12
11
  # The name of the log file is changed by inserting the age of the log file
13
12
  # (as a single number) between the log file name and the extension. If the
@@ -16,15 +15,30 @@ module Logging::Appenders
16
15
  #
17
16
  # /var/log/ruby.log => /var/log/ruby.1.log
18
17
  #
19
- # New log messages will be appended to a newly opened log file of the same
20
- # name (<tt>/var/log/ruby.log</tt> in our example above). The age number
21
- # for all older log files is incremented when the log file is rolled. The
22
- # number of older log files to keep can be given, otherwise all the log
23
- # files are kept.
18
+ # New log messages will continue to be appended to the same log file
19
+ # (<tt>/var/log/ruby.log</tt> in our example above). The age number for all
20
+ # older log files is incremented when the log file is rolled. The number of
21
+ # older log files to keep can be given, otherwise all the log files are
22
+ # kept.
24
23
  #
25
24
  # The actual process of rolling all the log file names can be expensive if
26
25
  # there are many, many older log files to process.
27
26
  #
27
+ # If you do not wish to use numbered files when rolling, you can specify the
28
+ # :roll_by option as 'date'. This will use a date/time stamp to
29
+ # differentiate the older files from one another. If you configure your
30
+ # rolling file appender to roll daily and ignore the file size:
31
+ #
32
+ # /var/log/ruby.log => /var/log/ruby.20091225.log
33
+ #
34
+ # Where the date is expressed as <tt>%Y%m%d</tt> in the Time#strftime format.
35
+ #
36
+ # NOTE: this class is not safe to use when log messages are written to files
37
+ # on NFS mounts or other remote file system. It should only be used for log
38
+ # files on the local file system. The exception to this is when a single
39
+ # process is writing to the log file; remote file systems are safe to
40
+ # use in this case but still not recommended.
41
+ #
28
42
  class RollingFile < ::Logging::Appenders::IO
29
43
 
30
44
  # call-seq:
@@ -49,69 +63,51 @@ module Logging::Appenders
49
63
  # rolled. The age can also be given as 'daily', 'weekly',
50
64
  # or 'monthly'.
51
65
  # [:keep] The number of rolled log files to keep.
52
- # [:safe] When set to true, extra checks are made to ensure that
53
- # only once process can roll the log files; this option
54
- # should only be used when multiple processes will be
55
- # logging to the same log file (does not work on Windows)
66
+ # [:roll_by] How to name the rolled log files. This can be 'number' or
67
+ # 'date'.
56
68
  #
57
69
  def initialize( name, opts = {} )
58
70
  # raise an error if a filename was not given
59
71
  @fn = opts.getopt(:filename, name)
72
+ @fn_copy = @fn + '._copy_'
60
73
  raise ArgumentError, 'no filename was given' if @fn.nil?
61
74
  ::Logging::Appenders::File.assert_valid_logfile(@fn)
62
75
 
63
- # grab the information we need to properly roll files
64
- ext = ::File.extname(@fn)
65
- bn = ::File.join(::File.dirname(@fn), ::File.basename(@fn, ext))
66
- @rgxp = %r/\.(\d+)#{Regexp.escape(ext)}\z/
67
- @glob = "#{bn}.*#{ext}"
68
- @logname_fmt = "#{bn}.%d#{ext}"
69
-
70
76
  # grab our options
71
- @keep = opts.getopt(:keep, :as => Integer)
72
77
  @size = opts.getopt(:size, :as => Integer)
73
78
 
74
- @lockfile = if HAVE_LOCKFILE and opts.getopt(:safe, false)
75
- ::Lockfile.new(
76
- @fn + '.lck',
77
- :retries => 1,
78
- :timeout => 2
79
- )
80
- end
81
-
82
79
  code = 'def sufficiently_aged?() false end'
83
80
  @age_fn = @fn + '.age'
81
+ @age_fn_mtime = nil
84
82
 
85
83
  case @age = opts.getopt(:age)
86
84
  when 'daily'
87
- FileUtils.touch(@age_fn) unless test(?f, @age_fn)
88
85
  code = <<-CODE
89
86
  def sufficiently_aged?
87
+ @age_fn_mtime ||= ::File.mtime(@age_fn)
90
88
  now = Time.now
91
- start = ::File.mtime(@age_fn)
92
- if (now.day != start.day) or (now - start) > 86400
89
+ if (now.day != @age_fn_mtime.day) or (now - @age_fn_mtime) > 86400
93
90
  return true
94
91
  end
95
92
  false
96
93
  end
97
94
  CODE
98
95
  when 'weekly'
99
- FileUtils.touch(@age_fn) unless test(?f, @age_fn)
100
96
  code = <<-CODE
101
97
  def sufficiently_aged?
102
- if (Time.now - ::File.mtime(@age_fn)) > 604800
98
+ @age_fn_mtime ||= ::File.mtime(@age_fn)
99
+ if (Time.now - @age_fn_mtime) > 604800
103
100
  return true
104
101
  end
105
102
  false
106
103
  end
107
104
  CODE
108
105
  when 'monthly'
109
- FileUtils.touch(@age_fn) unless test(?f, @age_fn)
110
106
  code = <<-CODE
111
107
  def sufficiently_aged?
108
+ @age_fn_mtime ||= ::File.mtime(@age_fn)
112
109
  now = Time.now
113
- start = ::File.mtime(@age_fn)
114
- if (now.month != start.month) or (now - start) > 2678400
110
+ if (now.month != @age_fn_mtime.month) or (now - @age_fn_mtime) > 2678400
115
111
  return true
116
112
  end
117
113
  false
@@ -119,30 +115,64 @@ module Logging::Appenders
119
115
  CODE
120
116
  when Integer, String
121
117
  @age = Integer(@age)
122
- FileUtils.touch(@age_fn) unless test(?f, @age_fn)
123
118
  code = <<-CODE
124
119
  def sufficiently_aged?
125
- if (Time.now - ::File.mtime(@age_fn)) > @age
120
+ @age_fn_mtime ||= ::File.mtime(@age_fn)
121
+ if (Time.now - @age_fn_mtime) > @age
126
122
  return true
127
123
  end
128
124
  false
129
125
  end
130
126
  CODE
131
127
  end
128
+
129
+ FileUtils.touch(@age_fn) if @age and !test(?f, @age_fn)
130
+
132
131
  meta = class << self; self end
133
132
  meta.class_eval code, __FILE__, __LINE__
134
133
 
135
- # if the truncate flag was set to true, then roll
136
- roll_now = opts.getopt(:truncate, false)
137
- roll_files if roll_now
134
+ super(name, ::File.new(@fn, 'a'), opts)
135
+
136
+ # setup the file roller
137
+ @roller =
138
+ case opts.getopt(:roll_by)
139
+ when 'number'; NumberedRoller.new(@fn, opts)
140
+ when 'date'; DateRoller.new(@fn, opts)
141
+ else
142
+ (@age and !@size) ?
143
+ DateRoller.new(@fn, opts) :
144
+ NumberedRoller.new(@fn, opts)
145
+ end
138
146
 
139
- super(name, open_logfile, opts)
147
+ # if the truncate flag was set to true, then roll
148
+ roll_now = opts.getopt(:truncate, false)
149
+ if roll_now
150
+ copy_truncate
151
+ @roller.roll_files
152
+ end
140
153
  end
141
154
 
142
155
  # Returns the path to the logfile.
143
156
  #
144
157
  def filename() @fn.dup end
145
158
 
159
+ # Reopen the connection to the underlying logging destination. If the
160
+ # connection is currently closed then it will be opened. If the connection
161
+ # is currently open then it will be closed and immediately opened.
162
+ #
163
+ def reopen
164
+ @mutex.synchronize {
165
+ if defined? @io and @io
166
+ flush
167
+ @io.close rescue nil
168
+ end
169
+ @closed = false
170
+ @io = ::File.new(@fn, 'a')
171
+ @io.sync = true
172
+ }
173
+ self
174
+ end
175
+
146
176
 
147
177
  private
148
178
 
@@ -158,43 +188,24 @@ module Logging::Appenders
158
188
  @layout.format(event) : event.to_s
159
189
  return if str.empty?
160
190
 
161
- check_logfile
162
- super(str)
163
-
164
- if roll_required?(str)
165
- return roll unless @lockfile
191
+ @io.flock_sh { super(str) }
166
192
 
167
- @lockfile.lock {
168
- check_logfile
169
- roll if roll_required?
193
+ if roll_required?
194
+ @io.flock? {
195
+ @age_fn_mtime = nil
196
+ copy_truncate if roll_required?
170
197
  }
198
+ @roller.roll_files
171
199
  end
172
200
  end
173
201
 
174
- # call-seq:
175
- # roll
176
- #
177
- # Close the currently open log file, roll all the log files, and open a
178
- # new log file.
179
- #
180
- def roll
181
- @io.close rescue nil
182
- roll_files
183
- open_logfile
184
- end
185
-
186
- # call-seq:
187
- # roll_required?( str ) => true or false
188
- #
189
202
  # Returns +true+ if the log file needs to be rolled.
190
203
  #
191
- def roll_required?( str = nil )
204
+ def roll_required?
205
+ return false if ::File.exist? @fn_copy
206
+
192
207
  # check if max size has been exceeded
193
- s = if @size
194
- @file_size = @stat.size if @stat.size > @file_size
195
- @file_size += str.size if str
196
- @file_size > @size
197
- end
208
+ s = @size ? ::File.size(@fn) > @size : false
198
209
 
199
210
  # check if max age has been exceeded
200
211
  a = sufficiently_aged?
@@ -202,90 +213,113 @@ module Logging::Appenders
202
213
  return (s || a)
203
214
  end
204
215
 
205
- # call-seq:
206
- # roll_files
216
+ # Copy the contents of the logfile to another file. Truncate the logfile
217
+ # to zero length. This method will set the roll flag so that all the
218
+ # current logfiles will be rolled along with the copied file.
207
219
  #
208
- # Roll the log files. This is accomplished by renaming the log files
209
- # starting with the oldest and working towards the youngest.
210
- #
211
- # test.10.log => deleted (we are only keeping 10)
212
- # test.9.log => test.10.log
213
- # test.8.log => test.9.log
214
- # ...
215
- # test.1.log => test.2.log
216
- #
217
- # Lastly the current log file is rolled to a numbered log file.
218
- #
219
- # test.log => test.1.log
220
- #
221
- # This method leaves no <tt>test.log</tt> file when it is done. This
222
- # file will be created elsewhere.
223
- #
224
- def roll_files
220
+ def copy_truncate
225
221
  return unless ::File.exist?(@fn)
222
+ FileUtils.copy @fn, @fn_copy
223
+ @io.truncate 0
224
+
225
+ # touch the age file if needed
226
+ if @age
227
+ FileUtils.touch @age_fn
228
+ @age_fn_mtime = nil
229
+ end
226
230
 
227
- files = Dir.glob(@glob).find_all {|fn| @rgxp =~ fn}
228
- unless files.empty?
229
- # sort the files in revese order based on their count number
230
- files = files.sort do |a,b|
231
- a = Integer(@rgxp.match(a)[1])
232
- b = Integer(@rgxp.match(b)[1])
233
- b <=> a
234
- end
235
-
236
- # for each file, roll its count number one higher
237
- files.each do |fn|
238
- cnt = Integer(@rgxp.match(fn)[1])
239
- if @keep and cnt >= @keep
240
- ::File.delete fn
241
- next
231
+ @roller.roll = true
232
+ end
233
+
234
+
235
+ # :stopdoc:
236
+ class NumberedRoller
237
+ attr_accessor :roll
238
+
239
+ def initialize( fn, opts )
240
+ # grab the information we need to properly roll files
241
+ ext = ::File.extname(fn)
242
+ bn = ::File.join(::File.dirname(fn), ::File.basename(fn, ext))
243
+ @rgxp = %r/\.(\d+)#{Regexp.escape(ext)}\z/
244
+ @glob = "#{bn}.*#{ext}"
245
+ @logname_fmt = "#{bn}.%d#{ext}"
246
+ @fn_copy = fn + '._copy_'
247
+ @keep = opts.getopt(:keep, :as => Integer)
248
+ @roll = false
249
+ end
250
+
251
+ def roll_files
252
+ return unless @roll and ::File.exist?(@fn_copy)
253
+
254
+ files = Dir.glob(@glob).find_all {|fn| @rgxp =~ fn}
255
+ unless files.empty?
256
+ # sort the files in revese order based on their count number
257
+ files = files.sort do |a,b|
258
+ a = Integer(@rgxp.match(a)[1])
259
+ b = Integer(@rgxp.match(b)[1])
260
+ b <=> a
261
+ end
262
+
263
+ # for each file, roll its count number one higher
264
+ files.each do |fn|
265
+ cnt = Integer(@rgxp.match(fn)[1])
266
+ if @keep and cnt >= @keep
267
+ ::File.delete fn
268
+ next
269
+ end
270
+ ::File.rename fn, sprintf(@logname_fmt, cnt+1)
242
271
  end
243
- ::File.rename fn, sprintf(@logname_fmt, cnt+1)
244
272
  end
273
+
274
+ # finally reanme the copied log file
275
+ ::File.rename(@fn_copy, sprintf(@logname_fmt, 1))
276
+ ensure
277
+ @roll = false
245
278
  end
279
+ end
246
280
 
247
- # finally reanme the base log file
248
- ::File.rename(@fn, sprintf(@logname_fmt, 1))
281
+ class DateRoller
282
+ attr_accessor :roll
249
283
 
250
- # touch the age file if needed
251
- FileUtils.touch(@age_fn) if @age
252
- end
284
+ def initialize( fn, opts )
285
+ @fn_copy = fn + '._copy_'
286
+ @roll = false
287
+ @keep = opts.getopt(:keep, :as => Integer)
253
288
 
254
- # call-seq:
255
- # open_logfile => io
256
- #
257
- # Opens the logfile and stores the current file szie and inode.
258
- #
259
- def open_logfile
260
- @io = ::File.new(@fn, 'a')
261
- @io.sync = true
289
+ ext = ::File.extname(fn)
290
+ bn = ::File.join(::File.dirname(fn), ::File.basename(fn, ext))
262
291
 
263
- @stat = ::File.stat(@fn)
264
- @file_size = @stat.size
265
- @inode = @stat.ino
292
+ if @keep
293
+ @rgxp = %r/\.(\d+)(-\d+)?#{Regexp.escape(ext)}\z/
294
+ @glob = "#{bn}.*#{ext}"
295
+ end
266
296
 
267
- return @io
268
- end
297
+ if %w[daily weekly monthly].include?(opts.getopt(:age)) and !opts.getopt(:size)
298
+ @logname_fmt = "#{bn}.%Y%m%d#{ext}"
299
+ else
300
+ @logname_fmt = "#{bn}.%Y%m%d-%H%M%S#{ext}"
301
+ end
302
+ end
269
303
 
270
- #
271
- #
272
- def check_logfile
273
- retry_cnt ||= 0
304
+ def roll_files
305
+ return unless @roll and ::File.exist?(@fn_copy)
274
306
 
275
- if ::File.exist?(@fn) then
276
- @stat = ::File.stat(@fn)
277
- return unless @lockfile
278
- return if @inode == @stat.ino
307
+ # reanme the copied log file
308
+ ::File.rename(@fn_copy, Time.now.strftime(@logname_fmt))
279
309
 
280
- @io.close rescue nil
310
+ # prune old log files
311
+ if @keep
312
+ files = Dir.glob(@glob).find_all {|fn| @rgxp =~ fn}
313
+ length = files.length
314
+ if length > @keep
315
+ files.sort {|a,b| b <=> a}.last(length-@keep).each {|fn| ::File.delete fn}
316
+ end
317
+ end
318
+ ensure
319
+ @roll = false
281
320
  end
282
- open_logfile
283
- rescue SystemCallError
284
- raise if retry_cnt > 3
285
- retry_cnt += 1
286
- sleep 0.08
287
- retry
288
321
  end
322
+ # :startdoc:
289
323
 
290
324
  end # class RollingFile
291
325
  end # module Logging::Appenders
@@ -86,10 +86,10 @@ module Logging::Appenders
86
86
  # through LOG_LOCAL7.
87
87
  #
88
88
  def initialize( name, opts = {} )
89
- ident = opts.getopt(:ident, name)
90
- logopt = opts.getopt(:logopt, (LOG_PID | LOG_CONS), :as => Integer)
91
- facility = opts.getopt(:facility, LOG_USER, :as => Integer)
92
- @syslog = ::Syslog.open(ident, logopt, facility)
89
+ @ident = opts.getopt(:ident, name)
90
+ @logopt = opts.getopt(:logopt, (LOG_PID | LOG_CONS), :as => Integer)
91
+ @facility = opts.getopt(:facility, LOG_USER, :as => Integer)
92
+ @syslog = ::Syslog.open(@ident, @logopt, @facility)
93
93
 
94
94
  # provides a mapping from the default Logging levels
95
95
  # to the syslog levels
@@ -145,6 +145,22 @@ module Logging::Appenders
145
145
  !@syslog.opened?
146
146
  end
147
147
 
148
+ # Reopen the connection to the underlying logging destination. If the
149
+ # connection is currently closed then it will be opened. If the connection
150
+ # is currently open then it will be closed and immediately opened.
151
+ #
152
+ def reopen
153
+ @mutex.synchronize {
154
+ if @syslog.opened?
155
+ flush
156
+ @syslog.close
157
+ end
158
+ @syslog = ::Syslog.open(@ident, @logopt, @facility)
159
+ @closed = false
160
+ }
161
+ self
162
+ end
163
+
148
164
 
149
165
  private
150
166
 
@@ -124,6 +124,46 @@ module Kernel
124
124
  end
125
125
  end # module Kernel
126
126
 
127
+ # --------------------------------------------------------------------------
128
+ class File
129
+
130
+ # Returns <tt>true</tt> if another process holds an exclusive lock on the
131
+ # file. Returns <tt>false</tt> if this is not the case.
132
+ #
133
+ # If a <tt>block</tt> of code is passed to this method, it will be run iff
134
+ # this process can obtain an exclusive lock on the file. The block will be
135
+ # run while this lock is held, and the exclusive lock will be released when
136
+ # the method returns.
137
+ #
138
+ # The exclusive lock is requested in a non-blocking mode. This method will
139
+ # return immediately (and the block will not be executed) if an exclusive
140
+ # lock cannot be obtained.
141
+ #
142
+ def flock?( &block )
143
+ status = flock(LOCK_EX|LOCK_NB)
144
+ case status
145
+ when false; true
146
+ when 0; block ? block.call : false
147
+ else
148
+ raise SystemCallError, "flock failed with status: #{status}"
149
+ end
150
+ ensure
151
+ flock LOCK_UN
152
+ end
153
+
154
+ # Execute the <tt>block</tt> in the context of a shared lock on this file. A
155
+ # shared lock will be obtained on the file, the block executed, and the lock
156
+ # released.
157
+ #
158
+ def flock_sh( &block )
159
+ flock LOCK_SH
160
+ block.call
161
+ ensure
162
+ flock LOCK_UN
163
+ end
164
+
165
+ end
166
+
127
167
  # --------------------------------------------------------------------------
128
168
  class ReentrantMutex < Mutex
129
169
 
@@ -75,7 +75,8 @@ module TestAppenders
75
75
  assert_equal 13, Dir.glob(@glob).length
76
76
 
77
77
  # force the appender to roll the files
78
- ap.send :roll
78
+ ap.send :copy_truncate
79
+ ap.instance_variable_get(:@roller).roll_files
79
80
  assert_equal 6, Dir.glob(@glob).length
80
81
 
81
82
  (1..5).each do |cnt|
@@ -86,6 +87,10 @@ module TestAppenders
86
87
  end
87
88
 
88
89
  def test_age
90
+ d_glob = File.join(TMP, 'test.*.log')
91
+ dt_glob = File.join(TMP, 'test.*-*.log')
92
+ age_fn = @fn + '.age'
93
+
89
94
  assert_equal [], Dir.glob(@glob)
90
95
 
91
96
  assert_raise(ArgumentError) do
@@ -96,47 +101,61 @@ module TestAppenders
96
101
  ap << "random message\n"
97
102
  assert_equal 1, Dir.glob(@glob).length
98
103
 
99
- sleep 1.250
104
+ now = ::File.mtime(age_fn)
105
+ start = now - 42
106
+ ::File.utime(start, start, age_fn)
107
+ ap.instance_variable_set(:@age_fn_mtime, nil)
100
108
  ap << "another random message\n"
101
- assert_equal 2, Dir.glob(@glob).length
109
+ assert_equal 1, Dir.glob(dt_glob).length
102
110
 
111
+ Dir.glob(d_glob).each {|fn| ::File.delete fn}
103
112
  cleanup
113
+
104
114
  ap = Logging.appenders.rolling_file(NAME, :filename => @fn, 'age' => 'daily')
105
115
  ap << "random message\n"
106
- assert_equal 2, Dir.glob(@glob).length
116
+ assert_equal 1, Dir.glob(@glob).length
107
117
 
108
- age_fn = @fn + '.age'
109
118
  now = ::File.mtime(age_fn)
110
119
  start = now - 3600 * 24
111
120
  ::File.utime(start, start, age_fn)
121
+ ap.instance_variable_set(:@age_fn_mtime, nil)
112
122
 
113
123
  sleep 0.250
114
124
  ap << "yet another random message\n"
115
- assert_equal 3, Dir.glob(@glob).length
125
+ assert_equal 0, Dir.glob(dt_glob).length
126
+ assert_equal 1, Dir.glob(d_glob).length
116
127
 
128
+ Dir.glob(d_glob).each {|fn| ::File.delete fn}
117
129
  cleanup
130
+
118
131
  ap = Logging.appenders.rolling_file(NAME, :filename => @fn, :age => 'weekly')
119
132
  ap << "random message\n"
120
- assert_equal 3, Dir.glob(@glob).length
133
+ assert_equal 1, Dir.glob(@glob).length
121
134
 
122
135
  start = now - 3600 * 24 * 7
123
136
  ::File.utime(start, start, age_fn)
137
+ ap.instance_variable_set(:@age_fn_mtime, nil)
124
138
 
125
139
  sleep 0.250
126
140
  ap << "yet another random message\n"
127
- assert_equal 4, Dir.glob(@glob).length
141
+ assert_equal 0, Dir.glob(dt_glob).length
142
+ assert_equal 1, Dir.glob(d_glob).length
128
143
 
144
+ Dir.glob(d_glob).each {|fn| ::File.delete fn}
129
145
  cleanup
146
+
130
147
  ap = Logging.appenders.rolling_file(NAME, :filename => @fn, :age => 'monthly')
131
148
  ap << "random message\n"
132
- assert_equal 4, Dir.glob(@glob).length
149
+ assert_equal 1, Dir.glob(@glob).length
133
150
 
134
151
  start = now - 3600 * 24 * 31
135
152
  ::File.utime(start, start, age_fn)
153
+ ap.instance_variable_set(:@age_fn_mtime, nil)
136
154
 
137
155
  sleep 0.250
138
156
  ap << "yet another random message\n"
139
- assert_equal 5, Dir.glob(@glob).length
157
+ assert_equal 0, Dir.glob(dt_glob).length
158
+ assert_equal 1, Dir.glob(d_glob).length
140
159
  end
141
160
 
142
161
  def test_size
@@ -173,16 +192,6 @@ module TestAppenders
173
192
  ap << 'X' * 100; ap.flush
174
193
  assert_equal 1, Dir.glob(@glob).length
175
194
  assert_equal 100, File.size(@fn)
176
-
177
- # Now remove @fn and make sure that the log file is written to
178
- # again
179
- File.unlink(@fn)
180
- assert_equal 0, Dir.glob(@glob).length
181
-
182
- ap << 'X' * 50; ap.flush
183
- assert_equal 1, Dir.glob(@glob).length
184
- assert_equal 50, File.size(@fn)
185
-
186
195
  end
187
196
 
188
197
  private
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logging
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.3
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tim Pease
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-12-01 00:00:00 -07:00
12
+ date: 2009-12-21 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -22,16 +22,6 @@ dependencies:
22
22
  - !ruby/object:Gem::Version
23
23
  version: 1.1.1
24
24
  version:
25
- - !ruby/object:Gem::Dependency
26
- name: lockfile
27
- type: :runtime
28
- version_requirement:
29
- version_requirements: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - ">="
32
- - !ruby/object:Gem::Version
33
- version: 1.4.3
34
- version:
35
25
  - !ruby/object:Gem::Dependency
36
26
  name: flexmock
37
27
  type: :development
@@ -60,7 +50,7 @@ dependencies:
60
50
  requirements:
61
51
  - - ">="
62
52
  - !ruby/object:Gem::Version
63
- version: 1.2.0
53
+ version: 1.2.2
64
54
  version:
65
55
  - !ruby/object:Gem::Dependency
66
56
  name: bones
@@ -70,7 +60,7 @@ dependencies:
70
60
  requirements:
71
61
  - - ">="
72
62
  - !ruby/object:Gem::Version
73
- version: 3.1.0
63
+ version: 3.2.0
74
64
  version:
75
65
  description: |-
76
66
  Logging is a flexible logging library for use in Ruby programs based on the
@@ -97,6 +87,7 @@ files:
97
87
  - examples/appenders.rb
98
88
  - examples/classes.rb
99
89
  - examples/consolidation.rb
90
+ - examples/fork.rb
100
91
  - examples/formatting.rb
101
92
  - examples/hierarchies.rb
102
93
  - examples/layouts.rb