logsly 1.2.0 → 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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -3
  3. data/lib/logsly/colors.rb +2 -2
  4. data/lib/logsly/logging182/appender.rb +290 -0
  5. data/lib/logsly/logging182/appenders/buffering.rb +398 -0
  6. data/lib/logsly/logging182/appenders/console.rb +81 -0
  7. data/lib/logsly/logging182/appenders/email.rb +178 -0
  8. data/lib/logsly/logging182/appenders/file.rb +85 -0
  9. data/lib/logsly/logging182/appenders/growl.rb +200 -0
  10. data/lib/logsly/logging182/appenders/io.rb +84 -0
  11. data/lib/logsly/logging182/appenders/rolling_file.rb +338 -0
  12. data/lib/logsly/logging182/appenders/string_io.rb +92 -0
  13. data/lib/logsly/logging182/appenders/syslog.rb +215 -0
  14. data/lib/logsly/logging182/appenders.rb +64 -0
  15. data/lib/logsly/logging182/color_scheme.rb +248 -0
  16. data/lib/logsly/logging182/config/configurator.rb +187 -0
  17. data/lib/logsly/logging182/config/yaml_configurator.rb +190 -0
  18. data/lib/logsly/logging182/diagnostic_context.rb +332 -0
  19. data/lib/logsly/logging182/layout.rb +132 -0
  20. data/lib/logsly/logging182/layouts/basic.rb +38 -0
  21. data/lib/logsly/logging182/layouts/parseable.rb +256 -0
  22. data/lib/logsly/logging182/layouts/pattern.rb +568 -0
  23. data/lib/logsly/logging182/layouts.rb +9 -0
  24. data/lib/logsly/logging182/log_event.rb +44 -0
  25. data/lib/logsly/logging182/logger.rb +509 -0
  26. data/lib/logsly/logging182/proxy.rb +59 -0
  27. data/lib/logsly/logging182/rails_compat.rb +36 -0
  28. data/lib/logsly/logging182/repository.rb +231 -0
  29. data/lib/logsly/logging182/root_logger.rb +60 -0
  30. data/lib/logsly/logging182/stats.rb +277 -0
  31. data/lib/logsly/logging182/utils.rb +231 -0
  32. data/lib/logsly/logging182.rb +559 -0
  33. data/lib/logsly/outputs.rb +5 -5
  34. data/lib/logsly/version.rb +1 -1
  35. data/lib/logsly.rb +6 -6
  36. data/logsly.gemspec +4 -2
  37. data/test/unit/colors_tests.rb +3 -3
  38. data/test/unit/logsly_tests.rb +14 -14
  39. data/test/unit/outputs_tests.rb +34 -24
  40. metadata +45 -6
@@ -0,0 +1,85 @@
1
+
2
+ module Logsly::Logging182::Appenders
3
+
4
+ # Accessor / Factory for the File appender.
5
+ #
6
+ def self.file( *args )
7
+ return ::Logsly::Logging182::Appenders::File if args.empty?
8
+ ::Logsly::Logging182::Appenders::File.new(*args)
9
+ end
10
+
11
+ # This class provides an Appender that can write to a File.
12
+ #
13
+ class File < ::Logsly::Logging182::Appenders::IO
14
+
15
+ # call-seq:
16
+ # File.assert_valid_logfile( filename ) => true
17
+ #
18
+ # Asserts that the given _filename_ can be used as a log file by ensuring
19
+ # that if the file exists it is a regular file and it is writable. If
20
+ # the file does not exist, then the directory is checked to see if it is
21
+ # writable.
22
+ #
23
+ # An +ArgumentError+ is raised if any of these assertions fail.
24
+ #
25
+ def self.assert_valid_logfile( fn )
26
+ if ::File.exist?(fn)
27
+ if not ::File.file?(fn)
28
+ raise ArgumentError, "#{fn} is not a regular file"
29
+ elsif not ::File.writable?(fn)
30
+ raise ArgumentError, "#{fn} is not writeable"
31
+ end
32
+ elsif not ::File.writable?(::File.dirname(fn))
33
+ raise ArgumentError, "#{::File.dirname(fn)} is not writable"
34
+ end
35
+ true
36
+ end
37
+
38
+ # call-seq:
39
+ # File.new( name, :filename => 'file' )
40
+ # File.new( name, :filename => 'file', :truncate => true )
41
+ # File.new( name, :filename => 'file', :layout => layout )
42
+ #
43
+ # Creates a new File Appender that will use the given filename as the
44
+ # logging destination. If the file does not already exist it will be
45
+ # created. If the :truncate option is set to +true+ then the file will
46
+ # be truncated before writing begins; otherwise, log messages will be
47
+ # appended to the file.
48
+ #
49
+ def initialize( name, opts = {} )
50
+ @fn = opts.getopt(:filename, name)
51
+ raise ArgumentError, 'no filename was given' if @fn.nil?
52
+
53
+ @fn = ::File.expand_path(@fn)
54
+ self.class.assert_valid_logfile(@fn)
55
+ @mode = opts.getopt(:truncate) ? 'w' : 'a'
56
+
57
+ self.encoding = opts.fetch(:encoding, self.encoding)
58
+ @mode = "#{@mode}:#{self.encoding}" if self.encoding
59
+
60
+ super(name, ::File.new(@fn, @mode), opts)
61
+ end
62
+
63
+ # Returns the path to the logfile.
64
+ #
65
+ def filename() @fn.dup end
66
+
67
+ # Reopen the connection to the underlying logging destination. If the
68
+ # connection is currently closed then it will be opened. If the connection
69
+ # is currently open then it will be closed and immediately opened.
70
+ #
71
+ def reopen
72
+ @mutex.synchronize {
73
+ if defined? @io and @io
74
+ flush
75
+ @io.close rescue nil
76
+ end
77
+ @io = ::File.new(@fn, @mode)
78
+ }
79
+ super
80
+ self
81
+ end
82
+
83
+ end # FileAppender
84
+ end # Logsly::Logging182::Appenders
85
+
@@ -0,0 +1,200 @@
1
+
2
+ module Logsly::Logging182::Appenders
3
+
4
+ # Accessor / Factory for the Growl appender.
5
+ #
6
+ def self.growl( *args )
7
+ return ::Logsly::Logging182::Appenders::Growl if args.empty?
8
+ ::Logsly::Logging182::Appenders::Growl.new(*args)
9
+ end
10
+
11
+ # This class provides an Appender that can send notifications to the Growl
12
+ # notification system on Mac OS X.
13
+ #
14
+ # +growlnotify+ must be installed somewhere in the path in order for the
15
+ # appender to function properly.
16
+ #
17
+ class Growl < ::Logsly::Logging182::Appender
18
+
19
+ # :stopdoc:
20
+ ColoredRegexp = %r/\e\[([34][0-7]|[0-9])m/
21
+ # :startdoc:
22
+
23
+ # call-seq:
24
+ # Growl.new( name, opts = {} )
25
+ #
26
+ # Create an appender that will log messages to the Growl framework on a
27
+ # Mac OS X machine.
28
+ #
29
+ def initialize( name, opts = {} )
30
+ super
31
+
32
+ @growl = "growlnotify -w -n \"#{@name}\" -t \"%s\" -m \"%s\" -p %d &"
33
+
34
+ @coalesce = opts.getopt(:coalesce, false)
35
+ @title_sep = opts.getopt(:separator)
36
+
37
+ # provides a mapping from the default Logsly::Logging182 levels
38
+ # to the Growl notification levels
39
+ @map = [-2, -1, 0, 1, 2]
40
+
41
+ map = opts.getopt(:map)
42
+ self.map = map unless map.nil?
43
+ setup_coalescing if @coalesce
44
+
45
+ # make sure the growlnotify command can be called
46
+ unless system('growlnotify -v >> /dev/null 2>&1')
47
+ self.level = :off
48
+ ::Logsly::Logging182.log_internal {'growl notifications have been disabled'}
49
+ end
50
+ end
51
+
52
+ # call-seq:
53
+ # map = { logging_levels => growl_levels }
54
+ #
55
+ # Configure the mapping from the Logsly::Logging182 levels to the Growl
56
+ # notification levels. This is needed in order to log events at the
57
+ # proper Growl level.
58
+ #
59
+ # Without any configuration, the following mapping will be used:
60
+ #
61
+ # :debug => -2
62
+ # :info => -1
63
+ # :warn => 0
64
+ # :error => 1
65
+ # :fatal => 2
66
+ #
67
+ def map=( levels )
68
+ map = []
69
+ levels.keys.each do |lvl|
70
+ num = ::Logsly::Logging182.level_num(lvl)
71
+ map[num] = growl_level_num(levels[lvl])
72
+ end
73
+ @map = map
74
+ end
75
+
76
+
77
+ private
78
+
79
+ # call-seq:
80
+ # write( event )
81
+ #
82
+ # Write the given _event_ to the growl notification facility. The log
83
+ # event will be processed through the Layout associated with this
84
+ # appender. The message will be logged at the level specified by the
85
+ # event.
86
+ #
87
+ def write( event )
88
+ title = ''
89
+ priority = 0
90
+ message = if event.instance_of?(::Logsly::Logging182::LogEvent)
91
+ priority = @map[event.level]
92
+ @layout.format(event)
93
+ else
94
+ event.to_s
95
+ end
96
+ return if message.empty?
97
+
98
+ message = message.gsub(ColoredRegexp, '')
99
+ if @title_sep
100
+ title, message = message.split(@title_sep)
101
+ title, message = '', title if message.nil?
102
+ end
103
+
104
+ growl(title.strip, message.strip, priority)
105
+ self
106
+ end
107
+
108
+ # call-seq:
109
+ # growl_level_num( level ) => integer
110
+ #
111
+ # Takes the given _level_ as a string or integer and returns the
112
+ # corresponding Growl notification level number.
113
+ #
114
+ def growl_level_num( level )
115
+ level = Integer(level)
116
+ if level < -2 or level > 2
117
+ raise ArgumentError, "level '#{level}' is not in range -2..2"
118
+ end
119
+ level
120
+ end
121
+
122
+ # call-seq:
123
+ # growl( title, message, priority )
124
+ #
125
+ # Send the _message_ to the growl notifier using the given _title_ and
126
+ # _priority_.
127
+ #
128
+ def growl( title, message, priority )
129
+ message.tr!("`", "'")
130
+ if @coalesce then coalesce(title, message, priority)
131
+ else call_growl(title, message, priority) end
132
+ end
133
+
134
+ # call-seq:
135
+ # coalesce( title, message, priority )
136
+ #
137
+ # Attempt to coalesce the given _message_ with any that might be pending
138
+ # in the queue to send to the growl notifier. Messages are coalesced
139
+ # with any in the queue that have the same _title_ and _priority_.
140
+ #
141
+ # There can be only one message in the queue, so if the _title_ and/or
142
+ # _priority_ don't match, the message in the queue is sent immediately
143
+ # to the growl notifier, and the current _message_ is queued.
144
+ #
145
+ def coalesce( *msg )
146
+ @c_mutex.synchronize do
147
+ if @c_queue.empty?
148
+ @c_queue << msg
149
+ @c_thread.run
150
+
151
+ else
152
+ qmsg = @c_queue.last
153
+ if qmsg.first != msg.first or qmsg.last != msg.last
154
+ @c_queue << msg
155
+ else
156
+ qmsg[1] << "\n" << msg[1]
157
+ end
158
+ end
159
+ end
160
+ end
161
+
162
+ # call-seq:
163
+ # setup_coalescing
164
+ #
165
+ # Setup the appender to handle coalescing of messages before sending
166
+ # them to the growl notifier. This requires the creation of a thread and
167
+ # mutex for passing messages from the appender thread to the growl
168
+ # notifier thread.
169
+ #
170
+ def setup_coalescing
171
+ @c_mutex = Mutex.new
172
+ @c_queue = []
173
+
174
+ @c_thread = Thread.new do
175
+ loop do
176
+ Thread.stop if @c_queue.empty?
177
+ sleep 1
178
+ @c_mutex.synchronize {
179
+ call_growl(*@c_queue.shift) until @c_queue.empty?
180
+ }
181
+ end # loop
182
+ end # Thread.new
183
+ end
184
+
185
+ # call-seq:
186
+ # call_growl( title, message, priority )
187
+ #
188
+ # Call the growlnotify application with the given parameters. If the
189
+ # system call fails, the growl appender will be disabled.
190
+ #
191
+ def call_growl( *args )
192
+ unless system(@growl % args)
193
+ self.level = :off
194
+ ::Logsly::Logging182.log_internal {'growl notifications have been disabled'}
195
+ end
196
+ end
197
+
198
+ end # Growl
199
+ end # Logsly::Logging182::Appenders
200
+
@@ -0,0 +1,84 @@
1
+
2
+ module Logsly::Logging182::Appenders
3
+
4
+ # Accessor / Factory for the IO appender.
5
+ #
6
+ def self.io( *args )
7
+ return ::Logsly::Logging182::Appenders::IO if args.empty?
8
+ ::Logsly::Logging182::Appenders::IO.new(*args)
9
+ end
10
+
11
+ # This class provides an Appender that can write to any IO stream
12
+ # configured for writing.
13
+ #
14
+ class IO < ::Logsly::Logging182::Appender
15
+ include Buffering
16
+
17
+ # The method that will be used to close the IO stream. Defaults to :close
18
+ # but can be :close_read, :close_write or nil. When nil, the IO stream
19
+ # will not be closed when the appender's close method is called.
20
+ #
21
+ attr_accessor :close_method
22
+
23
+ # call-seq:
24
+ # IO.new( name, io )
25
+ # IO.new( name, io, :layout => layout )
26
+ #
27
+ # Creates a new IO Appender using the given name that will use the _io_
28
+ # stream as the logging destination.
29
+ #
30
+ def initialize( name, io, opts = {} )
31
+ unless io.respond_to? :syswrite
32
+ raise TypeError, "expecting an IO object but got '#{io.class.name}'"
33
+ end
34
+
35
+ @io = io
36
+ @io.sync = true if io.respond_to? :sync= # syswrite complains if the IO stream is buffered
37
+ @io.flush rescue nil # syswrite also complains if in unbuffered mode and buffer isn't empty
38
+ @close_method = :close
39
+
40
+ super(name, opts)
41
+ configure_buffering(opts)
42
+ end
43
+
44
+ # call-seq:
45
+ # close( footer = true )
46
+ #
47
+ # Close the appender and writes the layout footer to the logging
48
+ # destination if the _footer_ flag is set to +true+. Log events will
49
+ # no longer be written to the logging destination after the appender
50
+ # is closed.
51
+ #
52
+ def close( *args )
53
+ return self if @io.nil?
54
+ super
55
+
56
+ io, @io = @io, nil
57
+ unless [STDIN, STDERR, STDOUT].include?(io)
58
+ io.send(@close_method) if @close_method and io.respond_to? @close_method
59
+ end
60
+ rescue IOError
61
+ ensure
62
+ return self
63
+ end
64
+
65
+
66
+ private
67
+
68
+ # This method is called by the buffering code when messages need to be
69
+ # written to the logging destination.
70
+ #
71
+ def canonical_write( str )
72
+ return self if @io.nil?
73
+ str = str.force_encoding(encoding) if encoding and str.encoding != encoding
74
+ @io.syswrite str
75
+ self
76
+ rescue StandardError => err
77
+ self.level = :off
78
+ ::Logsly::Logging182.log_internal {"appender #{name.inspect} has been disabled"}
79
+ ::Logsly::Logging182.log_internal(-2) {err}
80
+ end
81
+
82
+ end # IO
83
+ end # Logsly::Logging182::Appenders
84
+
@@ -0,0 +1,338 @@
1
+
2
+ module Logsly::Logging182::Appenders
3
+
4
+ # Accessor / Factory for the RollingFile appender.
5
+ #
6
+ def self.rolling_file( *args )
7
+ return ::Logsly::Logging182::Appenders::RollingFile if args.empty?
8
+ ::Logsly::Logging182::Appenders::RollingFile.new(*args)
9
+ end
10
+
11
+ # An appender that writes to a file and ensures that the file size or age
12
+ # never exceeds some user specified level.
13
+ #
14
+ # The goal of this class is to write log messages to a file. When the file
15
+ # age or size exceeds a given limit then the log file is copied and then
16
+ # truncated. The name of the copy indicates it is an older log file.
17
+ #
18
+ # The name of the log file is changed by inserting the age of the log file
19
+ # (as a single number) between the log file name and the extension. If the
20
+ # file has no extension then the number is appended to the filename. Here
21
+ # is a simple example:
22
+ #
23
+ # /var/log/ruby.log => /var/log/ruby.1.log
24
+ #
25
+ # New log messages will continue to be appended to the same log file
26
+ # (<tt>/var/log/ruby.log</tt> in our example above). The age number for all
27
+ # older log files is incremented when the log file is rolled. The number of
28
+ # older log files to keep can be given, otherwise all the log files are
29
+ # kept.
30
+ #
31
+ # The actual process of rolling all the log file names can be expensive if
32
+ # there are many, many older log files to process.
33
+ #
34
+ # If you do not wish to use numbered files when rolling, you can specify the
35
+ # :roll_by option as 'date'. This will use a date/time stamp to
36
+ # differentiate the older files from one another. If you configure your
37
+ # rolling file appender to roll daily and ignore the file size:
38
+ #
39
+ # /var/log/ruby.log => /var/log/ruby.20091225.log
40
+ #
41
+ # Where the date is expressed as <tt>%Y%m%d</tt> in the Time#strftime format.
42
+ #
43
+ # NOTE: this class is not safe to use when log messages are written to files
44
+ # on NFS mounts or other remote file system. It should only be used for log
45
+ # files on the local file system. The exception to this is when a single
46
+ # process is writing to the log file; remote file systems are safe to
47
+ # use in this case but still not recommended.
48
+ #
49
+ class RollingFile < ::Logsly::Logging182::Appenders::IO
50
+
51
+ # call-seq:
52
+ # RollingFile.new( name, opts )
53
+ #
54
+ # Creates a new Rolling File Appender. The _name_ is the unique Appender
55
+ # name used to retrieve this appender from the Appender hash. The only
56
+ # required option is the filename to use for creating log files.
57
+ #
58
+ # [:filename] The base filename to use when constructing new log
59
+ # filenames.
60
+ #
61
+ # The following options are optional:
62
+ #
63
+ # [:layout] The Layout that will be used by this appender. The Basic
64
+ # layout will be used if none is given.
65
+ # [:truncate] When set to true any existing log files will be rolled
66
+ # immediately and a new, empty log file will be created.
67
+ # [:size] The maximum allowed size (in bytes) of a log file before
68
+ # it is rolled.
69
+ # [:age] The maximum age (in seconds) of a log file before it is
70
+ # rolled. The age can also be given as 'daily', 'weekly',
71
+ # or 'monthly'.
72
+ # [:keep] The number of rolled log files to keep.
73
+ # [:roll_by] How to name the rolled log files. This can be 'number' or
74
+ # 'date'.
75
+ #
76
+ def initialize( name, opts = {} )
77
+ # raise an error if a filename was not given
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
+ ::Logsly::Logging182::Appenders::File.assert_valid_logfile(@fn)
84
+
85
+ # grab our options
86
+ @size = opts.getopt(:size, :as => Integer)
87
+
88
+ code = 'def sufficiently_aged?() false end'
89
+ @age_fn = @fn + '.age'
90
+ @age_fn_mtime = nil
91
+
92
+ case @age = opts.getopt(:age)
93
+ when 'daily'
94
+ code = <<-CODE
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__
142
+
143
+ # we are opening the file in read/write mode so that a shared lock can
144
+ # be used on the file descriptor => http://pubs.opengroup.org/onlinepubs/009695399/functions/fcntl.html
145
+ @mode = encoding ? "a+:#{encoding}" : 'a+'
146
+ super(name, ::File.new(@fn, @mode), opts)
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
158
+
159
+ # if the truncate flag was set to true, then roll
160
+ roll_now = opts.getopt(:truncate, false)
161
+ if roll_now
162
+ copy_truncate
163
+ @roller.roll_files
164
+ end
165
+ end
166
+
167
+ # Returns the path to the logfile.
168
+ #
169
+ def filename() @fn.dup end
170
+
171
+ # Reopen the connection to the underlying logging destination. If the
172
+ # connection is currently closed then it will be opened. If the connection
173
+ # is currently open then it will be closed and immediately opened.
174
+ #
175
+ def reopen
176
+ @mutex.synchronize {
177
+ if defined? @io and @io
178
+ flush
179
+ @io.close rescue nil
180
+ end
181
+ @io = ::File.new(@fn, @mode)
182
+ }
183
+ super
184
+ self
185
+ end
186
+
187
+
188
+ private
189
+
190
+ # Write the given _event_ to the log file. The log file will be rolled
191
+ # if the maximum file size is exceeded or if the file is older than the
192
+ # maximum age.
193
+ #
194
+ def canonical_write( str )
195
+ return self if @io.nil?
196
+
197
+ str = str.force_encoding(encoding) if encoding and str.encoding != encoding
198
+ @io.flock_sh { @io.syswrite str }
199
+
200
+ if roll_required?
201
+ @io.flock? {
202
+ @age_fn_mtime = nil
203
+ copy_truncate if roll_required?
204
+ }
205
+ @roller.roll_files
206
+ end
207
+ self
208
+ rescue StandardError => err
209
+ self.level = :off
210
+ ::Logsly::Logging182.log_internal {"appender #{name.inspect} has been disabled"}
211
+ ::Logsly::Logging182.log_internal(-2) {err}
212
+ end
213
+
214
+ # Returns +true+ if the log file needs to be rolled.
215
+ #
216
+ def roll_required?
217
+ return false if ::File.exist?(@fn_copy) and (Time.now - ::File.mtime(@fn_copy)) < 180
218
+
219
+ # check if max size has been exceeded
220
+ s = @size ? ::File.size(@fn) > @size : false
221
+
222
+ # check if max age has been exceeded
223
+ a = sufficiently_aged?
224
+
225
+ return (s || a)
226
+ end
227
+
228
+ # Copy the contents of the logfile to another file. Truncate the logfile
229
+ # to zero length. This method will set the roll flag so that all the
230
+ # current logfiles will be rolled along with the copied file.
231
+ #
232
+ def copy_truncate
233
+ return unless ::File.exist?(@fn)
234
+ FileUtils.concat @fn, @fn_copy
235
+ @io.truncate 0
236
+
237
+ # touch the age file if needed
238
+ if @age
239
+ FileUtils.touch @age_fn
240
+ @age_fn_mtime = nil
241
+ end
242
+
243
+ @roller.roll = true
244
+ end
245
+
246
+
247
+ # :stopdoc:
248
+ class NumberedRoller
249
+ attr_accessor :roll
250
+
251
+ def initialize( fn, opts )
252
+ # grab the information we need to properly roll files
253
+ ext = ::File.extname(fn)
254
+ bn = ::File.join(::File.dirname(fn), ::File.basename(fn, ext))
255
+ @rgxp = %r/\.(\d+)#{Regexp.escape(ext)}\z/
256
+ @glob = "#{bn}.*#{ext}"
257
+ @logname_fmt = "#{bn}.%d#{ext}"
258
+ @fn_copy = fn + '._copy_'
259
+ @keep = opts.getopt(:keep, :as => Integer)
260
+ @roll = false
261
+ end
262
+
263
+ def roll_files
264
+ return unless @roll and ::File.exist?(@fn_copy)
265
+
266
+ files = Dir.glob(@glob).find_all {|fn| @rgxp =~ fn}
267
+ unless files.empty?
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
274
+
275
+ # for each file, roll its count number one higher
276
+ files.each do |fn|
277
+ cnt = Integer(@rgxp.match(fn)[1])
278
+ if @keep and cnt >= @keep
279
+ ::File.delete fn
280
+ next
281
+ end
282
+ ::File.rename fn, sprintf(@logname_fmt, cnt+1)
283
+ end
284
+ end
285
+
286
+ # finally rename the copied log file
287
+ ::File.rename(@fn_copy, sprintf(@logname_fmt, 1))
288
+ ensure
289
+ @roll = false
290
+ end
291
+ end
292
+
293
+ class DateRoller
294
+ attr_accessor :roll
295
+
296
+ def initialize( fn, opts )
297
+ @fn_copy = fn + '._copy_'
298
+ @roll = false
299
+ @keep = opts.getopt(:keep, :as => Integer)
300
+
301
+ ext = ::File.extname(fn)
302
+ bn = ::File.join(::File.dirname(fn), ::File.basename(fn, ext))
303
+
304
+ if @keep
305
+ @rgxp = %r/\.(\d+)(-\d+)?#{Regexp.escape(ext)}\z/
306
+ @glob = "#{bn}.*#{ext}"
307
+ end
308
+
309
+ if %w[daily weekly monthly].include?(opts.getopt(:age)) and !opts.getopt(:size)
310
+ @logname_fmt = "#{bn}.%Y%m%d#{ext}"
311
+ else
312
+ @logname_fmt = "#{bn}.%Y%m%d-%H%M%S#{ext}"
313
+ end
314
+ end
315
+
316
+ def roll_files
317
+ return unless @roll and ::File.exist?(@fn_copy)
318
+
319
+ # rename the copied log file
320
+ ::File.rename(@fn_copy, Time.now.strftime(@logname_fmt))
321
+
322
+ # prune old log files
323
+ if @keep
324
+ files = Dir.glob(@glob).find_all {|fn| @rgxp =~ fn}
325
+ length = files.length
326
+ if length > @keep
327
+ files.sort {|a,b| b <=> a}.last(length-@keep).each {|fn| ::File.delete fn}
328
+ end
329
+ end
330
+ ensure
331
+ @roll = false
332
+ end
333
+ end
334
+ # :startdoc:
335
+
336
+ end # RollingFile
337
+ end # Logsly::Logging182::Appenders
338
+