logging 1.2.3 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +7 -0
- data/Rakefile +0 -1
- data/examples/fork.rb +37 -0
- data/lib/logging.rb +15 -8
- data/lib/logging/appender.rb +9 -0
- data/lib/logging/appenders/email.rb +1 -1
- data/lib/logging/appenders/file.rb +19 -2
- data/lib/logging/appenders/rolling_file.rb +173 -139
- data/lib/logging/appenders/syslog.rb +20 -4
- data/lib/logging/utils.rb +40 -0
- data/test/appenders/test_rolling_file.rb +29 -20
- metadata +5 -14
data/History.txt
CHANGED
@@ -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
data/examples/fork.rb
ADDED
@@ -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:
|
data/lib/logging.rb
CHANGED
@@ -11,9 +11,7 @@ require 'stringio'
|
|
11
11
|
require 'thread'
|
12
12
|
require 'little-plugger'
|
13
13
|
|
14
|
-
|
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.
|
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
|
#
|
data/lib/logging/appender.rb
CHANGED
@@ -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
|
#
|
@@ -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
|
9
|
-
#
|
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
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
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
|
-
# [:
|
53
|
-
#
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
136
|
-
|
137
|
-
|
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
|
-
|
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
|
-
|
162
|
-
super(str)
|
163
|
-
|
164
|
-
if roll_required?(str)
|
165
|
-
return roll unless @lockfile
|
191
|
+
@io.flock_sh { super(str) }
|
166
192
|
|
167
|
-
|
168
|
-
|
169
|
-
|
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?
|
204
|
+
def roll_required?
|
205
|
+
return false if ::File.exist? @fn_copy
|
206
|
+
|
192
207
|
# check if max size has been exceeded
|
193
|
-
s =
|
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
|
-
#
|
206
|
-
#
|
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
|
-
|
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
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
#
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
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
|
-
|
248
|
-
|
281
|
+
class DateRoller
|
282
|
+
attr_accessor :roll
|
249
283
|
|
250
|
-
|
251
|
-
|
252
|
-
|
284
|
+
def initialize( fn, opts )
|
285
|
+
@fn_copy = fn + '._copy_'
|
286
|
+
@roll = false
|
287
|
+
@keep = opts.getopt(:keep, :as => Integer)
|
253
288
|
|
254
|
-
|
255
|
-
|
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
|
-
|
264
|
-
|
265
|
-
|
292
|
+
if @keep
|
293
|
+
@rgxp = %r/\.(\d+)(-\d+)?#{Regexp.escape(ext)}\z/
|
294
|
+
@glob = "#{bn}.*#{ext}"
|
295
|
+
end
|
266
296
|
|
267
|
-
|
268
|
-
|
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
|
-
|
276
|
-
|
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
|
-
|
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
|
|
data/lib/logging/utils.rb
CHANGED
@@ -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 :
|
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
|
-
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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.
|
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-
|
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.
|
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.
|
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
|