logging 1.8.2 → 2.0.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.
- 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
|