logrotate 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,2 +1,11 @@
1
+ === 1.1.0 / 2008-08-20
2
+ * Enhanced the rotate file methods to return status information
3
+ regarding the rotation: the newly rotated file, the list of rotated
4
+ files, the list of deleted files, etc.
5
+ * Refactored the implementation of the file rotation methods into a
6
+ separate class (LogRotate::Impl) and exposed a few of these
7
+ implementation methods for public use.
8
+
1
9
  === 1.0.0 / 2008-06-19
2
10
  Initial release of LogRotate. This package is unit tested fully.
11
+
@@ -4,4 +4,7 @@ History.txt
4
4
  README.txt
5
5
  Rakefile
6
6
  lib/logrotate.rb
7
+ lib/logrotate/impl.rb
8
+ lib/logrotate/logrotate.rb
9
+ lib/logrotate/rotateinfo.rb
7
10
  test/test_logrotate.rb
data/README.txt CHANGED
@@ -1,6 +1,4 @@
1
- = logrotate
2
- * Project Page: http://rubyforge.org/projects/logrotate/
3
- * Documentation: http://logrotate.rubyforge.org/
1
+ Object
4
2
 
5
3
  == DESCRIPTION:
6
4
 
@@ -24,27 +22,158 @@ None (known).
24
22
  == SYNOPSIS:
25
23
  Here are some sample invocations of the library.
26
24
 
27
- Rotate a log file with simple extensions: .1, .2, etc, keep 10 rotated
28
- files around (.1 .. .10), and gzip the rotated files (.1.gz, .2.gz, .. .10.gz):
29
- ======+examples/rotate_count.rb+:
25
+ Rotate a log file with simple extensions: .1, .2, etc, keep 3 rotated
26
+ files around (.1 .. .3), and gzip the rotated files (.1.gz, .. .3.gz):
27
+ ======<tt>examples/rotate_count.rb</tt>:
30
28
  #!/usr/bin/env ruby
31
29
 
30
+ require 'fileutils'
32
31
  require 'rubygems'
33
32
  require 'logrotate'
34
33
 
35
34
  options = {
36
- :count => 10,
35
+ :count => 2,
37
36
  :gzip => true
38
37
  }
39
38
 
40
- LogRotate.rotate_file("/tmp/erwin.dat", options)
39
+ 1.upto(3) do |iteration|
40
+
41
+ FileUtils.touch("/tmp/erwin.dat")
42
+ result = LogRotate.rotate_file("/tmp/erwin.dat", options)
43
+
44
+ print "=================================================\n"
45
+ print "Iteration \##{iteration}\n"
46
+ print "=================================================\n"
47
+ print result, "\n"
48
+ end
49
+
50
+ # clean up all files created by this example
51
+ FileUtils.rm_f(Dir.glob('/tmp/erwin.dat.*'))
52
+
53
+ ======<tt>Output</tt>:
54
+ =================================================
55
+ Iteration #1
56
+ =================================================
57
+ Rotated Files And Counts:
58
+ file -> /tmp/erwin.dat.1.gz, index -> 1
59
+ Rotated Files:
60
+ /tmp/erwin.dat.1.gz
61
+ Deleted Files:
62
+ New Rotated File:
63
+ /tmp/erwin.dat.1.gz
64
+
65
+ =================================================
66
+ Iteration #2
67
+ =================================================
68
+ Rotated Files And Counts:
69
+ file -> /tmp/erwin.dat.1.gz, index -> 1
70
+ file -> /tmp/erwin.dat.2.gz, index -> 2
71
+ Rotated Files:
72
+ /tmp/erwin.dat.1.gz
73
+ /tmp/erwin.dat.2.gz
74
+ Deleted Files:
75
+ New Rotated File:
76
+ /tmp/erwin.dat.1.gz
77
+
78
+ =================================================
79
+ Iteration #3
80
+ =================================================
81
+ Rotated Files And Counts:
82
+ file -> /tmp/erwin.dat.1.gz, index -> 1
83
+ file -> /tmp/erwin.dat.2.gz, index -> 2
84
+ Rotated Files:
85
+ /tmp/erwin.dat.1.gz
86
+ /tmp/erwin.dat.2.gz
87
+ Deleted Files:
88
+ /tmp/erwin.dat.2.gz
89
+ New Rotated File:
90
+ /tmp/erwin.dat.1.gz
91
+
92
+
93
+
94
+ Rotate a log file with date/time extensions.
95
+ ======<tt>examples/rotate_date_time.rb</tt>:
96
+ #!/usr/bin/env/ruby
97
+
98
+ require 'fileutils'
99
+ require 'rubygems'
100
+ require 'logrotate'
101
+
102
+ options = {
103
+ :date_time_ext => true,
104
+ :date_time_format => '%F_%T',
105
+ :count => 2
106
+ }
107
+
108
+ 1.upto(3) do |iteration|
109
+
110
+ FileUtils.touch("/tmp/erwin.dat")
111
+ result = LogRotate.rotate_file("/tmp/erwin.dat", options)
112
+
113
+ print "=================================================\n"
114
+ print "Iteration \##{iteration}\n"
115
+ print "=================================================\n"
116
+ print result, "\n"
117
+
118
+ # Sleep for a short period so that next time rotate_file is called,
119
+ # the current second will be different (and thus the rotated file
120
+ # name will be different than the last).
121
+ sleep(1.5)
122
+ end
123
+
124
+ # clean up all files created by this example
125
+ FileUtils.rm_f(Dir.glob('/tmp/erwin.dat.*'))
126
+
127
+ ======<tt>Output</tt>:
128
+ =================================================
129
+ Iteration #1
130
+ =================================================
131
+ Rotated Files And Dates:
132
+ file -> /tmp/erwin.dat.2008-08-21_12:37:39, date_time -> 2008-08-21T12:37:39+00:00
133
+ file -> /tmp/erwin.dat.2008-08-21_12:37:37, date_time -> 2008-08-21T12:37:37+00:00
134
+ Rotated Files:
135
+ /tmp/erwin.dat.2008-08-21_12:37:39
136
+ /tmp/erwin.dat.2008-08-21_12:37:37
137
+ Deleted Files:
138
+ New Rotated File:
139
+ /tmp/erwin.dat.2008-08-21_12:37:39
140
+
141
+ =================================================
142
+ Iteration #2
143
+ =================================================
144
+ Rotated Files And Dates:
145
+ file -> /tmp/erwin.dat.2008-08-21_12:37:41, date_time -> 2008-08-21T12:37:41+00:00
146
+ file -> /tmp/erwin.dat.2008-08-21_12:37:39, date_time -> 2008-08-21T12:37:39+00:00
147
+ Rotated Files:
148
+ /tmp/erwin.dat.2008-08-21_12:37:41
149
+ /tmp/erwin.dat.2008-08-21_12:37:39
150
+ Deleted Files:
151
+ /tmp/erwin.dat.2008-08-21_12:37:37
152
+ New Rotated File:
153
+ /tmp/erwin.dat.2008-08-21_12:37:41
154
+
155
+ =================================================
156
+ Iteration #3
157
+ =================================================
158
+ Rotated Files And Dates:
159
+ file -> /tmp/erwin.dat.2008-08-21_12:37:42, date_time -> 2008-08-21T12:37:42+00:00
160
+ file -> /tmp/erwin.dat.2008-08-21_12:37:41, date_time -> 2008-08-21T12:37:41+00:00
161
+ Rotated Files:
162
+ /tmp/erwin.dat.2008-08-21_12:37:42
163
+ /tmp/erwin.dat.2008-08-21_12:37:41
164
+ Deleted Files:
165
+ /tmp/erwin.dat.2008-08-21_12:37:39
166
+ New Rotated File:
167
+ /tmp/erwin.dat.2008-08-21_12:37:42
168
+
41
169
 
42
170
 
43
171
  Rotate a set of log files with date extensions, and move the rotated
44
172
  files to a specified directory:
45
- ======+examples/rotate_date.rb+:
173
+ ======<tt>examples/rotate_date.rb</tt>:
46
174
  #!/usr/bin/env ruby
47
175
 
176
+ require 'fileutils'
48
177
  require 'rubygems'
49
178
  require 'logrotate'
50
179
 
@@ -64,29 +193,19 @@ files to a specified directory:
64
193
  }
65
194
 
66
195
  LogRotate.rotate_files(files, options)
67
-
68
-
69
- Rotate a log file with date/time extensions.
70
- ======+examples/rotate_date_time.rb+:
71
- #!/usr/bin/env/ruby
72
196
 
73
- require 'rubygems'
74
- require 'logrotate'
75
-
76
- options = {
77
- :date_time_ext => true,
78
- :date_time_format => '%F_%T'
79
- }
80
-
81
- LogRotate.rotate_file("/tmp/erwin.dat", options)
197
+ # clean up all files and directories created by this example
198
+ FileUtils.rm_f(Dir.glob"/tmp/erwin*.dat")
199
+ FileUtils.rm_rf("/tmp/archives")
82
200
 
83
201
 
84
202
  Rotate a log file that is currently being written to by a live
85
203
  process. After the file is rotated, notify the live process to reopen
86
204
  its log file.
87
- ======+examples/rotate_live.rb+:
205
+ ======<tt>examples/rotate_live.rb</tt>:
88
206
  #!/usr/bin/env ruby
89
207
 
208
+ require 'fileutils'
90
209
  require 'rubygems'
91
210
  require 'logrotate'
92
211
 
@@ -104,6 +223,9 @@ its log file.
104
223
  }
105
224
 
106
225
  LogRotate.rotate_file("/tmp/hupper.dat", options)
226
+
227
+ # clean up all files created by this example
228
+ FileUtils.rm_rf(Dir.glob("/tmp/hupper*"))
107
229
 
108
230
 
109
231
  == REQUIREMENTS:
@@ -119,12 +241,12 @@ Hoe is required but only for running the tests.
119
241
  * Homepage: http://www.designingpatterns.com
120
242
  * Blogs: http://blogs.designingpatterns.com
121
243
 
122
- == SUPPORT
244
+ == SUPPORT:
123
245
  Please post questions, concerns, or requests for enhancement to the forums on
124
246
  the project page. Alternatively, direct contact information for
125
247
  Designing Patterns can be found on the project page for this gem.
126
248
 
127
- == ENHANCEMENTS
249
+ == ENHANCEMENTS:
128
250
  Please feel free to contact us with any ideas; we will try our best to
129
251
  enhance the software and respond to user requests. Of course, we are more
130
252
  likely to work on a particular enhancement if we know that there are users
data/Rakefile CHANGED
@@ -2,7 +2,7 @@
2
2
  require 'rubygems'
3
3
  require 'hoe'
4
4
 
5
- Hoe.new('logrotate', "1.0.0") do |p|
5
+ Hoe.new('logrotate', "1.1.0") do |p|
6
6
  p.remote_rdoc_dir = ''
7
7
  p.developer('DesigningPatterns', 'technical.inquiries@designingpatterns.com')
8
8
  end
@@ -1,246 +1,3 @@
1
- #!/usr/bin/env ruby
2
-
3
- require 'English'
4
- require 'zlib'
5
- require 'date'
6
-
7
- #
8
- # This module provides a few methods for log rotation.
9
- #
10
-
11
- module LogRotate
12
-
13
- DEFAULT_COUNT = 5
14
- DEFAULT_GZIP = false
15
- DEFAULT_DATE_TIME_EXTENSION = false
16
-
17
- DEFAULT_DATE_TIME_FORMAT = '%F'
18
-
19
- #
20
- # ====Description: This method rotates the given files.
21
- #
22
- # ====Parameters:
23
- # [file(s)]
24
- # The files to be rotated.
25
- # [options = {}]
26
- # A list of optional arguments.
27
- #
28
- # See rotate_file for details.
29
- #
30
- def self.rotate_files(files, options)
31
- files.each do |file|
32
- rotate_single_file(file, options)
33
- end
34
- end
35
-
36
- #
37
- # ====Description: This method rotates the given file(s).
38
- # There are 2 types of extensions that can be used: the default
39
- # extension which is an integer value (".1", ".2", ..), and a
40
- # date/time extension (".2008-08-01", ..). If a date/time extension
41
- # is chosen, the caller can specify a date/time format for this
42
- # extension.
43
- #
44
- # ====Parameters:
45
- # [file]
46
- # The file to be rotated.
47
- # [options = {}]
48
- # A list of optional arguments.
49
- #
50
- # The optional arguments are listed below:
51
- # +count+:: The number of rotated files to be kept.
52
- # +directory+:: The directory to store the newly rotated file. If this
53
- # option is unspecified, the original file's directory
54
- # will be used.
55
- # +date_time_ext+:: Whether the extension on the rotated files should be
56
- # a date. Possible values are: +true+, +false+.
57
- # +date_time_format+:: For use with +date_time_ext+. This specifies the
58
- # format of the date / time extension.
59
- # +date_time+:: For use with +date_time_ext+. The +DateTime+ that will be
60
- # used to construct the extension of the newly rotated file.
61
- # If this option is not specified, the current date and time
62
- # will be used.
63
- # +pre_rotate+:: A +Proc+ to be executed before the rotation is started.
64
- # +post_rotate+:: A +Proc+ to be executed after the rotation is finished,
65
- # and before the newly rotated file is zipped.
66
- # +gzip+:: Whether the newly rotated file should be gzipped. Possible
67
- # values are: +true+, +false+.
68
- #
69
- def self.rotate_file(file, options)
70
- rotate_single_file(file, options)
71
- end
72
-
73
- def self.rotate_single_file(file, options = {})
74
- gzip = options[:gzip] ? options[:gzip] : DEFAULT_GZIP
75
-
76
- # error checking
77
- if (!File.exist?(file)) then raise "File does not exist: #{file}." end
78
- if (!File.readable?(file)) then raise "File is not readable: #{file}." end
79
-
80
- if (options[:pre_rotate]) then options[:pre_rotate].call() end
81
-
82
- new_backup = ""
83
- if (options[:date_time_ext])
84
- new_backup = rotate_file_date_extension(file, options)
85
- else
86
- new_backup = rotate_file_integer_extension(file, options)
87
- end
88
-
89
- if (options[:post_rotate]) then options[:post_rotate].call() end
90
-
91
- if (gzip) then gzip_file(new_backup) end
92
- end
93
- private_class_method(:rotate_single_file)
94
-
95
- def self.rotate_file_date_extension(file, options)
96
- count = options[:count] ? options[:count] : DEFAULT_COUNT
97
-
98
- now = options[:date_time] ? options[:date_time] : DateTime.now()
99
-
100
- date_time_format = options[:date_time_format] ? options[:date_time_format] : DEFAULT_DATE_TIME_FORMAT
101
-
102
- # Get a list of the rotated files.
103
- (source_directory, base_name) = File.split(file)
104
- directory = options[:directory] ? options[:directory] : source_directory
105
-
106
- rotated_files = Dir.entries(directory).map do |entry|
107
- rslt = nil
108
-
109
- # Validate the rotated file. Since the caller is allowed to
110
- # specify the date/time format, here we do not have a regular
111
- # expression to match the date/time portion of the rotated
112
- # file's extension. As a result, some foreign files can sneak
113
- # into our list of rotated files (eg. base_file.GARBAGE.gz). Thus,
114
- # the rotated file is validated below, and if invalid, it is
115
- # skipped / left alone / not considered in the rotation.
116
-
117
- if (entry.match("^#{base_name}\..+(\.gz)?$"))
118
-
119
- # Validation #1: convert the date/time portion of the
120
- # extension to a DateTime object and skip the file if an
121
- # exception is thrown.
122
- begin
123
- match_data = entry.match("\.([^.]+)(\.gz)?$")
124
- date_time = DateTime.strptime(match_data[1], date_time_format)
125
- gz = match_data[2] ? match_data[2] : ""
126
-
127
- # Validation #2: convert the date/time portion of the
128
- # extension to a DateTime object and back to a string again.
129
- # Then reconstruct the rotated file name with this string,
130
- # and ensure the reconstructed rotated file matches the
131
- # original. This will take into account cases where
132
- # strptime succeeded but had additional garbage characters
133
- # present.
134
- # eg: date_time_format = '%F', file = 'mysql_backup.2008-08-04.BACK.gz'
135
- reconstructed_file = "#{base_name}.#{date_time.strftime(date_time_format)}#{gz}"
136
- if (entry == reconstructed_file)
137
- rslt = { :date_time => date_time, :file => entry }
138
- end
139
- rescue => e
140
- end
141
-
142
- rslt
143
- end
144
- end
145
-
146
- rotated_files = rotated_files.select { |rotated_file| rotated_file }
147
-
148
- # Sort files by date (newer to older).
149
- rotated_files.sort! { |left, right| right[:date_time] <=> left[:date_time] }
150
-
151
- # Remove the oldest files so that there are count rotated files remaining.
152
-
153
- # Determine how many old files to be removed.
154
- new_backup_base_name = base_name + "." + now.strftime(date_time_format)
155
-
156
- # Add 1 to account for the additional rotated file that will be
157
- # created after the original file passed in is rotated.
158
- num_old_files = rotated_files.length() - count + 1
159
-
160
- # There is a chance that the newly rotated file will have the same
161
- # name as one of the already existing rotated files (eg. if the
162
- # extension is simply the date, and the job is ran a second time
163
- # in the same day). In this case, there will be 1 less rotated
164
- # file (and thus 1 less to delete).
165
- if (rotated_files.find {|rotated_file| rotated_file[:file] == new_backup_base_name})
166
- num_old_files -= 1
167
- end
168
-
169
- if (num_old_files > 0)
170
- old_files = rotated_files.slice!(-num_old_files, num_old_files)
171
- File.unlink(*old_files.map {|old_file| File.join(directory, old_file[:file]) })
172
- end
173
-
174
- # Rename the original file.
175
- new_backup = File.join(directory, new_backup_base_name)
176
- File.rename(file, new_backup)
177
-
178
- return new_backup
179
- end
180
- private_class_method(:rotate_file_date_extension)
181
-
182
- def self.rotate_file_integer_extension(file, options)
183
- count = options[:count] ? options[:count] : DEFAULT_COUNT
184
-
185
- # Get a list of the backed up files.
186
- (source_directory, base_name) = File.split(file)
187
- directory = options[:directory] ? options[:directory] : source_directory
188
-
189
- rotated_files = Dir.entries(directory).select do |entry|
190
- entry.match("#{base_name}\.[0-9]+(\.gz)?$")
191
- end
192
-
193
- rotated_files.map! do |rotated_file|
194
- index = rotated_file.match("\.([0-9]+)(\.gz)?$")[1].to_i()
195
- { :index => index, :file => rotated_file }
196
- end
197
-
198
- # Delete old files.
199
- old_files = rotated_files.select { |rotated_file| rotated_file[:index] >= count }
200
- File.unlink(*old_files.map {|old_file| File.join(directory, old_file[:file]) })
201
-
202
- rotated_files = rotated_files - old_files
203
-
204
- # Sort files such that the back up count is descending (eg. [hello.txt.5, hello.txt.4, ..]).
205
- rotated_files.sort! { |left, right| right[:index] <=> left[:index] }
206
-
207
- # Rotate - Increment the back up index on each file.
208
- rotated_files.each do |rotated_file|
209
-
210
- new_index = rotated_file[:index] + 1
211
- new_file = rotated_file[:file].sub(/(^.*\.)(#{rotated_file[:index]})(.gz)?$/,
212
- "\\1#{new_index}\\3")
213
- File.rename(File.join(directory, rotated_file[:file]),
214
- File.join(directory, new_file))
215
- end
216
-
217
- # Rename the original file.
218
- new_backup = File.join(directory, base_name + ".1")
219
- File.rename(file, new_backup)
220
-
221
- return new_backup
222
- end
223
- private_class_method(:rotate_file_integer_extension)
224
-
225
- def self.gzip_file(file)
226
- # Zip the original file if necessary.
227
- gzip_file = file + ".gz"
228
-
229
- begin
230
- File.open(file) do |file_stream|
231
- Zlib::GzipWriter.open(gzip_file) do |gz|
232
- while (buffer = file_stream.read(1024))
233
- gz.write(buffer)
234
- end
235
- end
236
- end
237
- rescue => error
238
- raise "Unable to zip file: #{file}. Error: #{error}\n"
239
- end
240
-
241
- File.unlink(file)
242
- end
243
- private_class_method(:gzip_file)
244
-
245
- end
1
+ #!/usr/bin/env ruby
246
2
 
3
+ require 'logrotate/logrotate'