logging 1.2.3 → 1.3.0

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