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.
- 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
|