logging 1.8.2 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +3 -3
- data/History.txt +20 -0
- data/README.md +159 -0
- data/Rakefile +9 -5
- data/examples/appenders.rb +0 -4
- data/examples/layouts.rb +1 -8
- data/examples/names.rb +4 -4
- data/lib/logging.rb +24 -76
- data/lib/logging/appender.rb +71 -16
- data/lib/logging/appenders.rb +0 -2
- data/lib/logging/appenders/buffering.rb +32 -16
- data/lib/logging/appenders/file.rb +2 -2
- data/lib/logging/appenders/io.rb +1 -1
- data/lib/logging/appenders/rolling_file.rb +228 -165
- data/lib/logging/appenders/string_io.rb +1 -1
- data/lib/logging/appenders/syslog.rb +4 -4
- data/lib/logging/color_scheme.rb +20 -3
- data/lib/logging/diagnostic_context.rb +142 -17
- data/lib/logging/filter.rb +18 -0
- data/lib/logging/filters.rb +4 -0
- data/lib/logging/filters/level.rb +29 -0
- data/lib/logging/layout.rb +2 -2
- data/lib/logging/layouts/parseable.rb +5 -2
- data/lib/logging/layouts/pattern.rb +309 -168
- data/lib/logging/log_event.rb +5 -5
- data/lib/logging/logger.rb +55 -68
- data/lib/logging/repository.rb +24 -39
- data/lib/logging/root_logger.rb +1 -1
- data/lib/logging/utils.rb +4 -65
- data/lib/logging/version.rb +8 -0
- data/lib/rspec/logging_helper.rb +3 -3
- data/logging.gemspec +46 -0
- data/test/appenders/test_buffered_io.rb +29 -0
- data/test/appenders/test_file.rb +2 -2
- data/test/appenders/test_rolling_file.rb +62 -1
- data/test/layouts/test_color_pattern.rb +1 -1
- data/test/layouts/test_json.rb +3 -0
- data/test/layouts/test_pattern.rb +6 -2
- data/test/layouts/test_yaml.rb +4 -1
- data/test/test_appender.rb +56 -0
- data/test/test_filter.rb +33 -0
- data/test/test_layout.rb +4 -8
- data/test/test_log_event.rb +3 -3
- data/test/test_logger.rb +81 -57
- data/test/test_logging.rb +0 -59
- data/test/test_mapped_diagnostic_context.rb +49 -1
- data/test/test_nested_diagnostic_context.rb +16 -1
- data/test/test_repository.rb +24 -32
- data/test/test_utils.rb +14 -50
- metadata +35 -53
- data/README.rdoc +0 -143
- data/data/bad_logging_1.rb +0 -13
- data/data/bad_logging_2.rb +0 -21
- data/data/logging.rb +0 -42
- data/data/logging.yaml +0 -63
- data/data/simple_logging.rb +0 -13
- data/examples/consolidation.rb +0 -83
- data/lib/logging/appenders/email.rb +0 -178
- data/lib/logging/appenders/growl.rb +0 -200
- data/lib/logging/config/configurator.rb +0 -187
- data/lib/logging/config/yaml_configurator.rb +0 -190
- data/lib/logging/stats.rb +0 -277
- data/test/appenders/test_email.rb +0 -170
- data/test/appenders/test_growl.rb +0 -138
- data/test/config/test_configurator.rb +0 -69
- data/test/config/test_yaml_configurator.rb +0 -39
- data/test/test_consolidate.rb +0 -45
- data/test/test_stats.rb +0 -273
- data/version.txt +0 -1
data/lib/logging/appender.rb
CHANGED
@@ -16,7 +16,7 @@ module Logging
|
|
16
16
|
#
|
17
17
|
class Appender
|
18
18
|
|
19
|
-
attr_reader :name, :layout, :level
|
19
|
+
attr_reader :name, :layout, :level, :filters
|
20
20
|
|
21
21
|
# call-seq:
|
22
22
|
# Appender.new( name )
|
@@ -32,27 +32,29 @@ class Appender
|
|
32
32
|
# :layout => the layout to use when formatting log events
|
33
33
|
# :level => the level at which to log
|
34
34
|
# :encoding => encoding to use when writing messages (defaults to UTF-8)
|
35
|
+
# :filters => filters to apply to events before processing
|
35
36
|
#
|
36
37
|
def initialize( name, opts = {} )
|
37
38
|
::Logging.init unless ::Logging.initialized?
|
38
39
|
|
39
|
-
@name
|
40
|
-
@closed
|
40
|
+
@name = name.to_s
|
41
|
+
@closed = false
|
42
|
+
@filters = []
|
43
|
+
@mutex = ReentrantMutex.new
|
41
44
|
|
42
|
-
self.layout
|
43
|
-
self.level
|
45
|
+
self.layout = opts.fetch(:layout, ::Logging::Layouts::Basic.new)
|
46
|
+
self.level = opts.fetch(:level, nil)
|
44
47
|
self.encoding = opts.fetch(:encoding, self.encoding)
|
48
|
+
self.filters = opts.fetch(:filters, nil)
|
45
49
|
|
46
|
-
|
47
|
-
|
48
|
-
if opts.getopt(:header, true)
|
50
|
+
if opts.fetch(:header, true)
|
49
51
|
header = @layout.header
|
50
52
|
|
51
53
|
unless header.nil? || header.empty?
|
52
54
|
begin
|
53
55
|
write(header)
|
54
56
|
rescue StandardError => err
|
55
|
-
::Logging.
|
57
|
+
::Logging.log_internal_error(err)
|
56
58
|
end
|
57
59
|
end
|
58
60
|
end
|
@@ -73,12 +75,12 @@ class Appender
|
|
73
75
|
end
|
74
76
|
|
75
77
|
# only append if the event level is less than or equal to the configured
|
76
|
-
# appender level
|
77
|
-
|
78
|
+
# appender level and the filter does not disallow it
|
79
|
+
if event = allow(event)
|
78
80
|
begin
|
79
81
|
write(event)
|
80
82
|
rescue StandardError => err
|
81
|
-
::Logging.
|
83
|
+
::Logging.log_internal_error(err)
|
82
84
|
end
|
83
85
|
end
|
84
86
|
|
@@ -97,11 +99,11 @@ class Appender
|
|
97
99
|
"appender '<#{self.class.name}: #{@name}>' is closed"
|
98
100
|
end
|
99
101
|
|
100
|
-
unless
|
102
|
+
unless off?
|
101
103
|
begin
|
102
104
|
write(str)
|
103
105
|
rescue StandardError => err
|
104
|
-
::Logging.
|
106
|
+
::Logging.log_internal_error(err)
|
105
107
|
end
|
106
108
|
end
|
107
109
|
self
|
@@ -162,6 +164,35 @@ class Appender
|
|
162
164
|
@layout = layout
|
163
165
|
end
|
164
166
|
|
167
|
+
# Sets the filter(s) to be used by this appender. This method will clear the
|
168
|
+
# current filter set and add those passed to this setter method.
|
169
|
+
#
|
170
|
+
# Examples
|
171
|
+
# appender.filters = Logging::Filters::Level.new(:warn, :error)
|
172
|
+
#
|
173
|
+
def filters=( args )
|
174
|
+
@filters.clear
|
175
|
+
add_filters(*args)
|
176
|
+
end
|
177
|
+
|
178
|
+
# Sets the filter(s) to be used by this appender. The filters will be
|
179
|
+
# applied in the order that they are added to the appender.
|
180
|
+
#
|
181
|
+
# Examples
|
182
|
+
# add_filters(Logging::Filters::Level.new(:warn, :error))
|
183
|
+
#
|
184
|
+
# Returns this appender instance.
|
185
|
+
def add_filters( *args )
|
186
|
+
args.flatten.each do |filter|
|
187
|
+
next if filter.nil?
|
188
|
+
unless filter.kind_of?(::Logging::Filter)
|
189
|
+
raise TypeError, "#{filter.inspect} is not a kind of 'Logging::Filter'"
|
190
|
+
end
|
191
|
+
@filters << filter
|
192
|
+
end
|
193
|
+
self
|
194
|
+
end
|
195
|
+
|
165
196
|
# call-seq:
|
166
197
|
# close( footer = true )
|
167
198
|
#
|
@@ -183,7 +214,7 @@ class Appender
|
|
183
214
|
begin
|
184
215
|
write(footer)
|
185
216
|
rescue StandardError => err
|
186
|
-
::Logging.
|
217
|
+
::Logging.log_internal_error(err)
|
187
218
|
end
|
188
219
|
end
|
189
220
|
end
|
@@ -250,7 +281,6 @@ class Appender
|
|
250
281
|
# value - The encoding as a String, Symbol, or Encoding instance.
|
251
282
|
#
|
252
283
|
# Raises ArgumentError if the value is not a valid encoding.
|
253
|
-
#
|
254
284
|
def encoding=( value )
|
255
285
|
if value.nil?
|
256
286
|
@encoding = nil
|
@@ -259,6 +289,31 @@ class Appender
|
|
259
289
|
end
|
260
290
|
end
|
261
291
|
|
292
|
+
# Check to see if the event should be processed by the appender. An event will
|
293
|
+
# be rejected if the event level is lower than the configured level for the
|
294
|
+
# appender. Or it will be rejected if one of the filters rejects the event.
|
295
|
+
#
|
296
|
+
# event - The LogEvent to check
|
297
|
+
#
|
298
|
+
# Returns the event if it is allowed; returns `nil` if it is not allowed.
|
299
|
+
def allow( event )
|
300
|
+
return nil if @level > event.level
|
301
|
+
@filters.each do |filter|
|
302
|
+
break unless event = filter.allow(event)
|
303
|
+
end
|
304
|
+
event
|
305
|
+
end
|
306
|
+
|
307
|
+
# Returns `true` if the appender has been turned off. This is useful for
|
308
|
+
# appenders that write data to a remote location (such as syslog or email),
|
309
|
+
# and that write encounters too many errors. The appender can turn itself off
|
310
|
+
# to and log an error via the `Logging` logger.
|
311
|
+
#
|
312
|
+
# Set the appender's level to a valid value to turn it back on.
|
313
|
+
def off?
|
314
|
+
@level >= ::Logging::LEVELS.length
|
315
|
+
end
|
316
|
+
|
262
317
|
|
263
318
|
private
|
264
319
|
|
data/lib/logging/appenders.rb
CHANGED
@@ -54,9 +54,7 @@ module Logging
|
|
54
54
|
require libpath('logging/appenders/buffering')
|
55
55
|
require libpath('logging/appenders/io')
|
56
56
|
require libpath('logging/appenders/console')
|
57
|
-
require libpath('logging/appenders/email')
|
58
57
|
require libpath('logging/appenders/file')
|
59
|
-
require libpath('logging/appenders/growl')
|
60
58
|
require libpath('logging/appenders/rolling_file')
|
61
59
|
require libpath('logging/appenders/string_io')
|
62
60
|
require libpath('logging/appenders/syslog')
|
@@ -14,23 +14,24 @@ module Logging::Appenders
|
|
14
14
|
module Buffering
|
15
15
|
|
16
16
|
# Default buffer size
|
17
|
-
#
|
18
17
|
DEFAULT_BUFFER_SIZE = 500;
|
19
18
|
|
20
19
|
# The buffer holding the log messages
|
21
|
-
#
|
22
20
|
attr_reader :buffer
|
23
21
|
|
24
22
|
# The auto-flushing setting. When the buffer reaches this size, all
|
25
23
|
# messages will be be flushed automatically.
|
26
|
-
#
|
27
24
|
attr_reader :auto_flushing
|
28
25
|
|
29
26
|
# When set, the buffer will be flushed at regular intervals defined by the
|
30
27
|
# flush_period.
|
31
|
-
#
|
32
28
|
attr_reader :flush_period
|
33
29
|
|
30
|
+
# Messages will be written in chunks. This controls the number of messages
|
31
|
+
# to pull from the buffer for each write operation. The default is to pull
|
32
|
+
# all messages from the buffer at once.
|
33
|
+
attr_accessor :write_size
|
34
|
+
|
34
35
|
# Setup the message buffer and other variables for automatically and
|
35
36
|
# periodically flushing the buffer.
|
36
37
|
#
|
@@ -67,22 +68,36 @@ module Logging::Appenders
|
|
67
68
|
super
|
68
69
|
end
|
69
70
|
|
70
|
-
# Call
|
71
|
-
# Similar to IO#flush
|
72
|
-
#
|
71
|
+
# Call `flush` to force an appender to write out any buffered log events.
|
72
|
+
# Similar to `IO#flush`, so use in a similar fashion.
|
73
73
|
def flush
|
74
74
|
return self if @buffer.empty?
|
75
75
|
|
76
|
-
|
76
|
+
ary = nil
|
77
77
|
sync {
|
78
|
-
|
78
|
+
ary = @buffer.dup
|
79
79
|
@buffer.clear
|
80
80
|
}
|
81
81
|
|
82
|
-
|
82
|
+
if ary.length <= write_size
|
83
|
+
str = ary.join
|
84
|
+
canonical_write str unless str.empty?
|
85
|
+
else
|
86
|
+
ary.each_slice(write_size) do |a|
|
87
|
+
str = a.join
|
88
|
+
canonical_write str unless str.empty?
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
83
92
|
self
|
84
93
|
end
|
85
94
|
|
95
|
+
# Clear the underlying buffer of all log events. These events will not be
|
96
|
+
# appended to the logging destination; they will be lost.
|
97
|
+
def clear!
|
98
|
+
sync { @buffer.clear }
|
99
|
+
end
|
100
|
+
|
86
101
|
# Configure the levels that will trigger an immediate flush of the
|
87
102
|
# logging buffer. When a log event of the given level is seen, the
|
88
103
|
# buffer will be flushed immediately. Only the levels explicitly given
|
@@ -154,7 +169,7 @@ module Logging::Appenders
|
|
154
169
|
"auto_flushing period must be greater than zero: #{period.inspect}"
|
155
170
|
end
|
156
171
|
|
157
|
-
@auto_flushing = DEFAULT_BUFFER_SIZE if @flush_period
|
172
|
+
@auto_flushing = DEFAULT_BUFFER_SIZE if @flush_period && @auto_flushing <= 1
|
158
173
|
end
|
159
174
|
|
160
175
|
# Configure periodic flushing of the message buffer. Periodic flushing is
|
@@ -205,9 +220,10 @@ module Logging::Appenders
|
|
205
220
|
def configure_buffering( opts )
|
206
221
|
::Logging.init unless ::Logging.initialized?
|
207
222
|
|
208
|
-
self.immediate_at
|
209
|
-
self.auto_flushing = opts.
|
210
|
-
self.flush_period
|
223
|
+
self.immediate_at = opts.fetch(:immediate_at, '')
|
224
|
+
self.auto_flushing = opts.fetch(:auto_flushing, true)
|
225
|
+
self.flush_period = opts.fetch(:flush_period, nil)
|
226
|
+
self.write_size = opts.fetch(:write_size, DEFAULT_BUFFER_SIZE)
|
211
227
|
end
|
212
228
|
|
213
229
|
# Returns true if the _event_ level matches one of the configured
|
@@ -241,7 +257,7 @@ module Logging::Appenders
|
|
241
257
|
canonical_write(str)
|
242
258
|
else
|
243
259
|
sync {
|
244
|
-
str = str.force_encoding(encoding) if encoding
|
260
|
+
str = str.force_encoding(encoding) if encoding && str.encoding != encoding
|
245
261
|
@buffer << str
|
246
262
|
}
|
247
263
|
@periodic_flusher.signal if @periodic_flusher
|
@@ -333,7 +349,7 @@ module Logging::Appenders
|
|
333
349
|
@appender.flush
|
334
350
|
rescue => err
|
335
351
|
::Logging.log_internal {"PeriodicFlusher for appender #{@appender.inspect} encountered an error"}
|
336
|
-
::Logging.
|
352
|
+
::Logging.log_internal_error(err)
|
337
353
|
end
|
338
354
|
}; @thread = nil }
|
339
355
|
|
@@ -47,12 +47,12 @@ module Logging::Appenders
|
|
47
47
|
# appended to the file.
|
48
48
|
#
|
49
49
|
def initialize( name, opts = {} )
|
50
|
-
@fn = opts.
|
50
|
+
@fn = opts.fetch(:filename, name)
|
51
51
|
raise ArgumentError, 'no filename was given' if @fn.nil?
|
52
52
|
|
53
53
|
@fn = ::File.expand_path(@fn)
|
54
54
|
self.class.assert_valid_logfile(@fn)
|
55
|
-
@mode = opts.
|
55
|
+
@mode = opts.fetch(:truncate, false) ? 'w' : 'a'
|
56
56
|
|
57
57
|
self.encoding = opts.fetch(:encoding, self.encoding)
|
58
58
|
@mode = "#{@mode}:#{self.encoding}" if self.encoding
|
data/lib/logging/appenders/io.rb
CHANGED
@@ -1,8 +1,6 @@
|
|
1
|
-
|
2
1
|
module Logging::Appenders
|
3
2
|
|
4
3
|
# Accessor / Factory for the RollingFile appender.
|
5
|
-
#
|
6
4
|
def self.rolling_file( *args )
|
7
5
|
return ::Logging::Appenders::RollingFile if args.empty?
|
8
6
|
::Logging::Appenders::RollingFile.new(*args)
|
@@ -23,10 +21,9 @@ module Logging::Appenders
|
|
23
21
|
# /var/log/ruby.log => /var/log/ruby.1.log
|
24
22
|
#
|
25
23
|
# New log messages will continue to be appended to the same log file
|
26
|
-
# (
|
27
|
-
#
|
28
|
-
#
|
29
|
-
# kept.
|
24
|
+
# (`/var/log/ruby.log` in our example above). The age number for all older
|
25
|
+
# log files is incremented when the log file is rolled. The number of older
|
26
|
+
# log files to keep can be given, otherwise all the log files are kept.
|
30
27
|
#
|
31
28
|
# The actual process of rolling all the log file names can be expensive if
|
32
29
|
# there are many, many older log files to process.
|
@@ -38,14 +35,13 @@ module Logging::Appenders
|
|
38
35
|
#
|
39
36
|
# /var/log/ruby.log => /var/log/ruby.20091225.log
|
40
37
|
#
|
41
|
-
# Where the date is expressed as
|
38
|
+
# Where the date is expressed as `%Y%m%d` in the Time#strftime format.
|
42
39
|
#
|
43
40
|
# NOTE: this class is not safe to use when log messages are written to files
|
44
41
|
# on NFS mounts or other remote file system. It should only be used for log
|
45
42
|
# files on the local file system. The exception to this is when a single
|
46
43
|
# process is writing to the log file; remote file systems are safe to
|
47
44
|
# use in this case but still not recommended.
|
48
|
-
#
|
49
45
|
class RollingFile < ::Logging::Appenders::IO
|
50
46
|
|
51
47
|
# call-seq:
|
@@ -58,6 +54,20 @@ module Logging::Appenders
|
|
58
54
|
# [:filename] The base filename to use when constructing new log
|
59
55
|
# filenames.
|
60
56
|
#
|
57
|
+
# The "rolling" portion of the filename can be configured via some simple
|
58
|
+
# pattern templates. For numbered rolling, you can use {{.%d}}
|
59
|
+
#
|
60
|
+
# "logname{{.%d}}.log" => ["logname.log", "logname.1.log", "logname.2.log" ...]
|
61
|
+
# "logname.log{{-%d}}" => ["logname.log", "logname.log-1", "logname.log-2" ...]
|
62
|
+
#
|
63
|
+
# And for date rolling you can use `strftime` patterns:
|
64
|
+
#
|
65
|
+
# "logname{{.%Y%m%d}}.log" => ["logname.log, "logname.20130626.log" ...]
|
66
|
+
# "logname{{.%Y-%m-%dT%H:%M:%S}}.log" => ["logname.log, "logname.2013-06-26T22:03:31.log" ...]
|
67
|
+
#
|
68
|
+
# If the defaults suit you fine, just pass in the :roll_by option and use
|
69
|
+
# your normal log filename without any pattern template.
|
70
|
+
#
|
61
71
|
# The following options are optional:
|
62
72
|
#
|
63
73
|
# [:layout] The Layout that will be used by this appender. The Basic
|
@@ -74,90 +84,27 @@ module Logging::Appenders
|
|
74
84
|
# 'date'.
|
75
85
|
#
|
76
86
|
def initialize( name, opts = {} )
|
77
|
-
|
78
|
-
@fn = opts.getopt(:filename, name)
|
79
|
-
raise ArgumentError, 'no filename was given' if @fn.nil?
|
80
|
-
|
81
|
-
@fn = ::File.expand_path(@fn)
|
82
|
-
@fn_copy = @fn + '._copy_'
|
83
|
-
::Logging::Appenders::File.assert_valid_logfile(@fn)
|
87
|
+
@roller = Roller.new name, opts
|
84
88
|
|
85
89
|
# grab our options
|
86
|
-
@size = opts.
|
90
|
+
@size = opts.fetch(:size, nil)
|
91
|
+
@size = Integer(@size) unless @size.nil?
|
87
92
|
|
88
|
-
|
89
|
-
@age_fn = @fn + '.age'
|
93
|
+
@age_fn = filename + '.age'
|
90
94
|
@age_fn_mtime = nil
|
95
|
+
@age = opts.fetch(:age, nil)
|
91
96
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
def sufficiently_aged?
|
96
|
-
@age_fn_mtime ||= ::File.mtime(@age_fn)
|
97
|
-
now = Time.now
|
98
|
-
if (now.day != @age_fn_mtime.day) or (now - @age_fn_mtime) > 86400
|
99
|
-
return true
|
100
|
-
end
|
101
|
-
false
|
102
|
-
end
|
103
|
-
CODE
|
104
|
-
when 'weekly'
|
105
|
-
code = <<-CODE
|
106
|
-
def sufficiently_aged?
|
107
|
-
@age_fn_mtime ||= ::File.mtime(@age_fn)
|
108
|
-
if (Time.now - @age_fn_mtime) > 604800
|
109
|
-
return true
|
110
|
-
end
|
111
|
-
false
|
112
|
-
end
|
113
|
-
CODE
|
114
|
-
when 'monthly'
|
115
|
-
code = <<-CODE
|
116
|
-
def sufficiently_aged?
|
117
|
-
@age_fn_mtime ||= ::File.mtime(@age_fn)
|
118
|
-
now = Time.now
|
119
|
-
if (now.month != @age_fn_mtime.month) or (now - @age_fn_mtime) > 2678400
|
120
|
-
return true
|
121
|
-
end
|
122
|
-
false
|
123
|
-
end
|
124
|
-
CODE
|
125
|
-
when Integer, String
|
126
|
-
@age = Integer(@age)
|
127
|
-
code = <<-CODE
|
128
|
-
def sufficiently_aged?
|
129
|
-
@age_fn_mtime ||= ::File.mtime(@age_fn)
|
130
|
-
if (Time.now - @age_fn_mtime) > @age
|
131
|
-
return true
|
132
|
-
end
|
133
|
-
false
|
134
|
-
end
|
135
|
-
CODE
|
136
|
-
end
|
137
|
-
|
138
|
-
FileUtils.touch(@age_fn) if @age and !test(?f, @age_fn)
|
139
|
-
|
140
|
-
meta = class << self; self end
|
141
|
-
meta.class_eval code, __FILE__, __LINE__
|
97
|
+
# create our `sufficiently_aged?` method
|
98
|
+
build_singleton_methods
|
99
|
+
FileUtils.touch(@age_fn) if @age && !test(?f, @age_fn)
|
142
100
|
|
143
101
|
# we are opening the file in read/write mode so that a shared lock can
|
144
102
|
# be used on the file descriptor => http://pubs.opengroup.org/onlinepubs/009695399/functions/fcntl.html
|
145
103
|
@mode = encoding ? "a+:#{encoding}" : 'a+'
|
146
|
-
super(name, ::File.new(
|
147
|
-
|
148
|
-
# setup the file roller
|
149
|
-
@roller =
|
150
|
-
case opts.getopt(:roll_by)
|
151
|
-
when 'number'; NumberedRoller.new(@fn, opts)
|
152
|
-
when 'date'; DateRoller.new(@fn, opts)
|
153
|
-
else
|
154
|
-
(@age and !@size) ?
|
155
|
-
DateRoller.new(@fn, opts) :
|
156
|
-
NumberedRoller.new(@fn, opts)
|
157
|
-
end
|
104
|
+
super(name, ::File.new(filename, @mode), opts)
|
158
105
|
|
159
106
|
# if the truncate flag was set to true, then roll
|
160
|
-
roll_now = opts.
|
107
|
+
roll_now = opts.fetch(:truncate, false)
|
161
108
|
if roll_now
|
162
109
|
copy_truncate
|
163
110
|
@roller.roll_files
|
@@ -165,20 +112,20 @@ module Logging::Appenders
|
|
165
112
|
end
|
166
113
|
|
167
114
|
# Returns the path to the logfile.
|
168
|
-
|
169
|
-
|
115
|
+
def filename
|
116
|
+
@roller.filename
|
117
|
+
end
|
170
118
|
|
171
119
|
# Reopen the connection to the underlying logging destination. If the
|
172
120
|
# connection is currently closed then it will be opened. If the connection
|
173
121
|
# is currently open then it will be closed and immediately opened.
|
174
|
-
#
|
175
122
|
def reopen
|
176
123
|
@mutex.synchronize {
|
177
|
-
if defined?
|
124
|
+
if defined?(@io) && @io
|
178
125
|
flush
|
179
126
|
@io.close rescue nil
|
180
127
|
end
|
181
|
-
@io = ::File.new(
|
128
|
+
@io = ::File.new(filename, @mode)
|
182
129
|
}
|
183
130
|
super
|
184
131
|
self
|
@@ -187,14 +134,20 @@ module Logging::Appenders
|
|
187
134
|
|
188
135
|
private
|
189
136
|
|
137
|
+
# Returns the file name to use as the temporary copy location. We are
|
138
|
+
# using copy-and-truncate semantics for rolling files so that the IO
|
139
|
+
# file descriptor remains valid during rolling.
|
140
|
+
def copy_file
|
141
|
+
@roller.copy_file
|
142
|
+
end
|
143
|
+
|
190
144
|
# Write the given _event_ to the log file. The log file will be rolled
|
191
145
|
# if the maximum file size is exceeded or if the file is older than the
|
192
146
|
# maximum age.
|
193
|
-
#
|
194
147
|
def canonical_write( str )
|
195
148
|
return self if @io.nil?
|
196
149
|
|
197
|
-
str = str.force_encoding(encoding) if encoding
|
150
|
+
str = str.force_encoding(encoding) if encoding && str.encoding != encoding
|
198
151
|
@io.flock_sh { @io.syswrite str }
|
199
152
|
|
200
153
|
if roll_required?
|
@@ -208,16 +161,15 @@ module Logging::Appenders
|
|
208
161
|
rescue StandardError => err
|
209
162
|
self.level = :off
|
210
163
|
::Logging.log_internal {"appender #{name.inspect} has been disabled"}
|
211
|
-
::Logging.
|
164
|
+
::Logging.log_internal_error(err)
|
212
165
|
end
|
213
166
|
|
214
167
|
# Returns +true+ if the log file needs to be rolled.
|
215
|
-
#
|
216
168
|
def roll_required?
|
217
|
-
return false if ::File.exist?(
|
169
|
+
return false if ::File.exist?(copy_file) && (Time.now - ::File.mtime(copy_file)) < 180
|
218
170
|
|
219
171
|
# check if max size has been exceeded
|
220
|
-
s = @size ? ::File.size(
|
172
|
+
s = @size ? ::File.size(filename) > @size : false
|
221
173
|
|
222
174
|
# check if max age has been exceeded
|
223
175
|
a = sufficiently_aged?
|
@@ -228,10 +180,9 @@ module Logging::Appenders
|
|
228
180
|
# Copy the contents of the logfile to another file. Truncate the logfile
|
229
181
|
# to zero length. This method will set the roll flag so that all the
|
230
182
|
# current logfiles will be rolled along with the copied file.
|
231
|
-
#
|
232
183
|
def copy_truncate
|
233
|
-
return unless ::File.exist?(
|
234
|
-
FileUtils.concat
|
184
|
+
return unless ::File.exist?(filename)
|
185
|
+
FileUtils.concat filename, copy_file
|
235
186
|
@io.truncate 0
|
236
187
|
|
237
188
|
# touch the age file if needed
|
@@ -243,96 +194,208 @@ module Logging::Appenders
|
|
243
194
|
@roller.roll = true
|
244
195
|
end
|
245
196
|
|
197
|
+
# Returns the modification time of the age file.
|
198
|
+
def age_fn_mtime
|
199
|
+
@age_fn_mtime ||= ::File.mtime(@age_fn)
|
200
|
+
end
|
246
201
|
|
247
|
-
#
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
@
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
202
|
+
# We use meta-programming here to define the `sufficiently_aged?` method for
|
203
|
+
# the rolling appender. The `sufficiently_aged?` method is responsible for
|
204
|
+
# determining if the current log file is older than the rolling criteria -
|
205
|
+
# daily, weekly, etc.
|
206
|
+
#
|
207
|
+
# Returns this rolling file appender instance
|
208
|
+
def build_singleton_methods
|
209
|
+
method =
|
210
|
+
case @age
|
211
|
+
when 'daily'
|
212
|
+
-> {
|
213
|
+
now = Time.now
|
214
|
+
(now.day != age_fn_mtime.day) || (now - age_fn_mtime) > 86400
|
215
|
+
}
|
216
|
+
|
217
|
+
when 'weekly'
|
218
|
+
-> { (Time.now - age_fn_mtime) > 604800 }
|
219
|
+
|
220
|
+
when 'monthly'
|
221
|
+
-> {
|
222
|
+
now = Time.now
|
223
|
+
(now.month != age_fn_mtime.month) || (now - age_fn_mtime) > 2678400
|
224
|
+
}
|
225
|
+
|
226
|
+
when Integer, String
|
227
|
+
@age = Integer(@age)
|
228
|
+
-> { (Time.now - age_fn_mtime) > @age }
|
262
229
|
|
263
|
-
|
264
|
-
|
230
|
+
else
|
231
|
+
-> { false }
|
232
|
+
end
|
265
233
|
|
266
|
-
|
267
|
-
|
268
|
-
# sort the files in reverse order based on their count number
|
269
|
-
files = files.sort do |a,b|
|
270
|
-
a = Integer(@rgxp.match(a)[1])
|
271
|
-
b = Integer(@rgxp.match(b)[1])
|
272
|
-
b <=> a
|
273
|
-
end
|
234
|
+
self.define_singleton_method(:sufficiently_aged?, method)
|
235
|
+
end
|
274
236
|
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
237
|
+
# Not intended for general consumption, but the Roller class is used
|
238
|
+
# internally by the RollingFile appender to roll dem log files according
|
239
|
+
# to the user's desires.
|
240
|
+
class Roller
|
241
|
+
|
242
|
+
# The magic regex for finding user-defined roller patterns.
|
243
|
+
RGXP = %r/{{(([^%]+)?.*?)}}/
|
244
|
+
|
245
|
+
# Create a new roller. See the RollingFile#initialize documentation for
|
246
|
+
# the list of options.
|
247
|
+
#
|
248
|
+
# name - The appender name as a String
|
249
|
+
# opts - The options Hash
|
250
|
+
#
|
251
|
+
def initialize( name, opts )
|
252
|
+
# raise an error if a filename was not given
|
253
|
+
@fn = opts.fetch(:filename, name)
|
254
|
+
raise ArgumentError, 'no filename was given' if @fn.nil?
|
255
|
+
|
256
|
+
if (m = RGXP.match @fn)
|
257
|
+
@roll_by = ("#{m[2]}%d" == m[1]) ? :number : :date
|
258
|
+
else
|
259
|
+
age = opts.fetch(:age, nil)
|
260
|
+
size = opts.fetch(:size, nil)
|
261
|
+
|
262
|
+
@roll_by =
|
263
|
+
case opts.fetch(:roll_by, nil)
|
264
|
+
when 'number'; :number
|
265
|
+
when 'date'; :date
|
266
|
+
else
|
267
|
+
(age && !size) ? :date : :number
|
268
|
+
end
|
269
|
+
|
270
|
+
ext = ::File.extname(@fn)
|
271
|
+
bn = ::File.join(::File.dirname(@fn), ::File.basename(@fn, ext))
|
272
|
+
|
273
|
+
@fn = if :date == @roll_by && %w[daily weekly monthly].include?(age)
|
274
|
+
"#{bn}{{.%Y%m%d}}#{ext}"
|
275
|
+
elsif :date == @roll_by
|
276
|
+
"#{bn}{{.%Y%m%d-%H%M%S}}#{ext}"
|
277
|
+
else
|
278
|
+
"#{bn}{{.%d}}#{ext}"
|
279
|
+
end
|
284
280
|
end
|
285
281
|
|
286
|
-
|
287
|
-
::File.
|
288
|
-
|
282
|
+
@fn = ::File.expand_path(@fn)
|
283
|
+
::Logging::Appenders::File.assert_valid_logfile(filename)
|
284
|
+
|
289
285
|
@roll = false
|
286
|
+
@keep = opts.fetch(:keep, nil)
|
287
|
+
@keep = Integer(keep) unless keep.nil?
|
290
288
|
end
|
291
|
-
end
|
292
289
|
|
293
|
-
|
290
|
+
attr_reader :keep, :roll_by
|
294
291
|
attr_accessor :roll
|
295
292
|
|
296
|
-
|
297
|
-
|
298
|
-
@
|
299
|
-
@
|
293
|
+
# Returns the regular log file name without any roller text.
|
294
|
+
def filename
|
295
|
+
return @filename if defined? @filename
|
296
|
+
@filename = (@fn =~ RGXP ? @fn.sub(RGXP, '') : @fn.dup)
|
297
|
+
@filename.freeze
|
298
|
+
end
|
300
299
|
|
301
|
-
|
302
|
-
|
300
|
+
# Returns the file name to use as the temporary copy location. We are
|
301
|
+
# using copy-and-truncate semantics for rolling files so that the IO
|
302
|
+
# file descriptor remains valid during rolling.
|
303
|
+
def copy_file
|
304
|
+
return @copy_file if defined? @copy_file
|
305
|
+
@copy_file = filename + '._copy_'
|
306
|
+
@copy_file.freeze
|
307
|
+
end
|
303
308
|
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
309
|
+
# Returns the glob pattern used to find rolled log files. We use this
|
310
|
+
# list for pruning older log files and doing the numbered rolling.
|
311
|
+
def glob
|
312
|
+
return @glob if defined? @glob
|
313
|
+
m = RGXP.match @fn
|
314
|
+
@glob = @fn.sub(RGXP, (m[2] ? "#{m[2]}*" : '*'))
|
315
|
+
@glob.freeze
|
316
|
+
end
|
308
317
|
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
318
|
+
# Returns the format String used to generate rolled file names.
|
319
|
+
# Depending upon the `roll_by` type (:date or :number), this String will
|
320
|
+
# be processed by `sprintf` or `Time#strftime`.
|
321
|
+
def format
|
322
|
+
return @format if defined? @format
|
323
|
+
m = RGXP.match @fn
|
324
|
+
@format = @fn.sub(RGXP, m[1])
|
325
|
+
@format.freeze
|
314
326
|
end
|
315
327
|
|
328
|
+
# Roll the log files. This method will collect the list of rolled files
|
329
|
+
# and then pass that list to either `roll_by_number` or `roll_by_date`
|
330
|
+
# to perform the actual rolling.
|
331
|
+
#
|
332
|
+
# Returns nil
|
316
333
|
def roll_files
|
317
|
-
return unless
|
334
|
+
return unless roll && ::File.exist?(copy_file)
|
318
335
|
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
#
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
336
|
+
files = Dir.glob(glob)
|
337
|
+
files.delete copy_file
|
338
|
+
|
339
|
+
self.send "roll_by_#{roll_by}", files
|
340
|
+
|
341
|
+
nil
|
342
|
+
ensure
|
343
|
+
self.roll = false
|
344
|
+
end
|
345
|
+
|
346
|
+
# Roll the list of log files optionally removing older files. The "older
|
347
|
+
# files" are determined by extracting the number from the log file name
|
348
|
+
# and order by the number.
|
349
|
+
#
|
350
|
+
# files - The Array of filename Strings
|
351
|
+
#
|
352
|
+
# Returns nil
|
353
|
+
def roll_by_number( files )
|
354
|
+
@number_rgxp ||= Regexp.new(@fn.sub(RGXP, '\2(\d+)'))
|
355
|
+
|
356
|
+
# sort the files in reverse order based on their count number
|
357
|
+
files = files.sort do |a,b|
|
358
|
+
a = Integer(@number_rgxp.match(a)[1])
|
359
|
+
b = Integer(@number_rgxp.match(b)[1])
|
360
|
+
b <=> a
|
361
|
+
end
|
362
|
+
|
363
|
+
# for each file, roll its count number one higher
|
364
|
+
files.each do |fn|
|
365
|
+
cnt = Integer(@number_rgxp.match(fn)[1])
|
366
|
+
if keep && cnt >= keep
|
367
|
+
::File.delete fn
|
368
|
+
next
|
328
369
|
end
|
370
|
+
::File.rename fn, sprintf(format, cnt+1)
|
329
371
|
end
|
330
|
-
|
331
|
-
|
372
|
+
|
373
|
+
# finally rename the copied log file
|
374
|
+
::File.rename(copy_file, sprintf(format, 1))
|
332
375
|
end
|
333
|
-
end
|
334
|
-
# :startdoc:
|
335
376
|
|
336
|
-
|
337
|
-
|
377
|
+
# Roll the list of log files optionally removing older files. The "older
|
378
|
+
# files" are determined by the mtime of the log files. So touching log
|
379
|
+
# files or otherwise messing with them will screw this up.
|
380
|
+
#
|
381
|
+
# files - The Array of filename Strings
|
382
|
+
#
|
383
|
+
# Returns nil
|
384
|
+
def roll_by_date( files )
|
385
|
+
length = files.length
|
386
|
+
|
387
|
+
if keep && length >= keep
|
388
|
+
files = files.sort do |a,b|
|
389
|
+
a = ::File.mtime(a)
|
390
|
+
b = ::File.mtime(b)
|
391
|
+
b <=> a
|
392
|
+
end
|
393
|
+
files.last(length-keep+1).each { |fn| ::File.delete fn }
|
394
|
+
end
|
338
395
|
|
396
|
+
# rename the copied log file
|
397
|
+
::File.rename(copy_file, Time.now.strftime(format))
|
398
|
+
end
|
399
|
+
end
|
400
|
+
end
|
401
|
+
end
|