simple_rotate 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,48 @@
1
+ module ProcessSyncMixin
2
+ @@scheduled_del_lockfile = false
3
+ @@tempf_name = nil
4
+ @@tempf = nil
5
+
6
+ def locked?
7
+ return false if !tempf_exists?
8
+
9
+ # return false, if locked by another
10
+ status = @@tempf.flock(File::LOCK_EX | File::LOCK_NB)
11
+
12
+ return status == false
13
+ end
14
+
15
+ # lock the temp file
16
+ def lock
17
+ create_tempfile if !tempf_exists?
18
+
19
+ reopen_temp_file
20
+
21
+ cnt = 0
22
+ begin
23
+ @@tempf.flock(File::LOCK_EX)
24
+
25
+ rescue
26
+ cnt += 1
27
+ if (cnt <= @try_limit)
28
+ sleep(0.5)
29
+ create_tempfile if !tempf_exists?
30
+ retry
31
+ else
32
+ SimpleRotate::Error.warning("It was not possible to lock (tried 3times) => #{@@tempf_name}")
33
+ return false
34
+ end
35
+ end
36
+ end
37
+
38
+ # unlock the temp file
39
+ def unlock
40
+ return nil if !tempf_exists?
41
+
42
+ begin
43
+ @@tempf.flock(File::LOCK_UN)
44
+ rescue
45
+ SimpleRotate::Error.warning("It was not possible to unlock => #{@@tempf_name}")
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,7 @@
1
+ class SimpleRotate
2
+ module RotateTerm
3
+ TERM_DAILY = 1
4
+ TERM_WEEKLY = 7
5
+ TERM_MONTHLY = 30
6
+ end
7
+ end
@@ -0,0 +1,45 @@
1
+ class SimpleRotate
2
+ module Validator
3
+ def valid_file_name
4
+ # stdout only
5
+ if @file_name.is_a?(Symbol) && @file_name == :STDOUT
6
+ @only_stdout = true
7
+ return true
8
+ end
9
+
10
+ # not string
11
+ if !@file_name.is_a?(String)
12
+ SimpleRotate::Error.argv("file_name", @file_name)
13
+ end
14
+
15
+ # directory?
16
+ if File.directory?(@file_name)
17
+ msg = "ERROR => #{@file_name} is a Directory!"
18
+ SimpleRotate::Error.warning(msg)
19
+ SimpleRotate::Error.argv("file_name", @file_name)
20
+ end
21
+
22
+ return true
23
+ end
24
+
25
+ def valid_int(param, argv)
26
+ if !argv.is_a?(Integer)
27
+ SimpleRotate::Error.argv(param, argv)
28
+
29
+ elsif argv < 0
30
+ msg = %{You can't specify the negative value!}
31
+ SimpleRotate::Error.warning(msg)
32
+ SimpleRotate::Error.argv(param, argv)
33
+ end
34
+ end
35
+
36
+ def valid_bool(param, argv)
37
+ argv = true if argv == 1
38
+ argv = false if argv == 0
39
+ if !(argv.instance_of?(TrueClass) || argv.instance_of?(FalseClass))
40
+ SimpleRotate::Error.argv(param, argv)
41
+ end
42
+ return true
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,436 @@
1
+ #----------------
2
+ # private methods
3
+ #----------------
4
+ class SimpleRotate
5
+ # log file is not IO class or stdout only
6
+ # @return bool
7
+ private
8
+ def logf_not_usable
9
+ !@logf.is_a?(IO) || @only_stdout
10
+ end
11
+
12
+ # load zlib lib
13
+ private
14
+ def use_zlib
15
+ begin
16
+ require "zlib"
17
+ @compress_level = Zlib::DEFAULT_COMPRESSION if @compress_level == nil
18
+
19
+ rescue LoadError
20
+ SimpleRotate::Error.load("zlib")
21
+ end
22
+ end
23
+
24
+ # open or generate the log file
25
+ private
26
+ def prepare_logf
27
+ if File.exist?(@file_name)
28
+ # open the exists file, add mode
29
+ openadd
30
+
31
+ # rotate it if necessary
32
+ rotate_if
33
+
34
+ else
35
+ gen_new_logf
36
+ end
37
+ end
38
+
39
+ # generate new log file
40
+ private
41
+ def gen_new_logf
42
+ begin
43
+ @logf = File.open(@file_name, File::RDWR|File::CREAT|File::TRUNC)
44
+ gtime = Time.new.to_i
45
+ @logf.puts("created@#{gtime}@Please don't delete this line")
46
+ @logf.close
47
+
48
+ rescue
49
+ SimpleRotate::Error.open(@file_name)
50
+ end
51
+
52
+ openadd
53
+ end
54
+
55
+ # if file or directory exist, call error
56
+ private
57
+ def exist_error(file)
58
+ SimpleRotate::Error.exist(file, "File") if File.exist?(file)
59
+ SimpleRotate::Error.exist(file, "Directory") if Dir.exist?(file)
60
+
61
+ return true
62
+ end
63
+
64
+ # define default instance vars
65
+ private
66
+ def include_defaults
67
+ que = []
68
+ que << [%{@threshold}, %{LOG_LEVEL_2}]
69
+ que << [%{@log_level}, %{2}]
70
+ que << [%{@logging_format}, %{"[$DATE] - $LEVEL : $LOG"}]
71
+ que << [%{@date_format}, %{"%Y/%m/%d %H:%M:%S"}]
72
+ que << [%{@term_format}, %{"%Y%m%d"}]
73
+ que << [%{@rename_format}, %{"."}]
74
+ que << [%{@with_stdout}, %{false}]
75
+ que << [%{@only_stdout}, %{false}]
76
+ que << [%{@no_wcheck}, %{false}]
77
+ que << [%{@sync_inode_limit}, %{3}]
78
+ que << [%{@no_sync_inode}, %{false}]
79
+ que << [%{@enable_wflush}, %{false}]
80
+ que << [%{@compress}, %{false}]
81
+ que << [%{@is_psync}, %{false}]
82
+ que << [%{@sleep_time}, %{0}]
83
+
84
+ que.each do |q|
85
+ if eval(%{#{q[0]} == nil})
86
+ eval(%{#{q[0]} = #{q[1]}})
87
+ end
88
+ end
89
+ end
90
+
91
+ # Whether to rotate by file age?
92
+ # @return bool
93
+ private
94
+ def rotate_by_term?
95
+ if @limit.is_a?(Integer)
96
+ return false
97
+
98
+ elsif @limit.is_a?(String)
99
+ return @limit.to_i == 0
100
+
101
+ else
102
+ SimpleRotate::Error.argv("limit", @limit)
103
+ end
104
+ end
105
+
106
+ # Open the log file, add mode
107
+ private
108
+ def openadd
109
+ @logf = File.open(@file_name, File::RDWR|File::CREAT|File::APPEND)
110
+
111
+ # refresh object
112
+ @psync = ProcessSync.new(self)
113
+ end
114
+
115
+ # get cretated time of the log file
116
+ # @return Time
117
+ private
118
+ def get_logf_generate_time
119
+ pos = @logf.pos
120
+ begin
121
+ @logf.rewind
122
+ stamp = @logf.readline.split("@")
123
+ @logf.seek(pos)
124
+ gtime = Time.at(stamp[1].to_i)
125
+
126
+ rescue StandardError, SyntaxError
127
+ msg = "Can't get file creation time!"
128
+ gtime = Time.now
129
+ SimpleRotate::Error.warning(msg)
130
+ end
131
+
132
+ return gtime
133
+ end
134
+
135
+ # @return int
136
+ private
137
+ def set_days_cnt_of_term
138
+ begin
139
+ @dayc = eval("TERM_#{@limit}")
140
+
141
+ rescue NameError
142
+ SimpleRotate::Error.argv("limit", @limit)
143
+ end
144
+ end
145
+
146
+ # log file size over 'limit_size'?
147
+ # @return bool|nil
148
+ private
149
+ def over_size?
150
+ return nil if logf_not_usable
151
+
152
+ begin
153
+ rst = File.size(@file_name) > @limit_size
154
+ rescue
155
+ rst = false
156
+ end
157
+
158
+ return rst
159
+ end
160
+
161
+ private
162
+ def safe_over_size?
163
+ rst = nil
164
+ synchronize do
165
+ @psync.lock
166
+ rst = over_size?
167
+ @psync.unlock
168
+ end
169
+
170
+ return rst
171
+ end
172
+
173
+ # logfile's elapsed days is over limit?
174
+ # @return bool
175
+ private
176
+ def over_term?
177
+ return nil if logf_not_usable
178
+
179
+ begin
180
+ now_time = Time.now
181
+ gen_time = get_logf_generate_time
182
+ estimated_time = gen_time + (60 * 60 * 24 * @dayc)
183
+ rst = estimated_time <= now_time
184
+
185
+ rescue
186
+ rst = false
187
+ end
188
+
189
+ return rst
190
+ end
191
+
192
+ private
193
+ def safe_over_term?
194
+ rst = nil
195
+ synchronize do
196
+ @psync.lock
197
+ rst = over_term?
198
+ @psync.unlock
199
+ end
200
+
201
+ return rst
202
+ end
203
+
204
+ # Format the text for logging
205
+ # the following characters are recognized
206
+ # $DATE => date
207
+ # $LEVEL => log's severity
208
+ # $LOG => your log message
209
+ # $PID => process ID
210
+ # $FILE => execute file name
211
+ #
212
+ # @param string $log
213
+ # @return string
214
+ private
215
+ def get_trimmed_log(log)
216
+ log = log.to_s
217
+ date = Time.now.strftime(@date_format)
218
+ level = eval("LOG_LEVEL_#{@log_level}")
219
+ return @logging_format.gsub("$DATE", date)
220
+ .gsub("$LEVEL", level)
221
+ .gsub("$LOG", log)
222
+ .gsub("$PID", $$.to_s)
223
+ .gsub("$FILE-FUL", File.absolute_path($0))
224
+ .gsub("$FILE", File.basename($0))
225
+ end
226
+
227
+ # Whether that is the output of the log level that exceeds the threshold
228
+ # @return boolean
229
+ private
230
+ def over_threshold?
231
+ begin
232
+ return (@log_level >= eval("LEVEL_ID_#{@threshold}"))
233
+
234
+ rescue NameError
235
+ SimpleRotate::Error.argv("threshold", @threshold)
236
+ end
237
+ end
238
+
239
+ # need rotate?
240
+ # @return bool
241
+ private
242
+ def reached_limit?(mode=:NO_LOCK)
243
+ # file age rotation
244
+ if @rotate_by_term
245
+ is_over_term = nil
246
+ if mode == :NO_LOCK
247
+ is_over_term = over_term?
248
+ elsif mode == :LOCK
249
+ is_over_term = safe_over_term?
250
+ end
251
+
252
+ return is_over_term
253
+ end
254
+
255
+ # file size rotation
256
+ is_over_size = nil
257
+ if mode == :NO_LOCK
258
+ is_over_size = over_size?
259
+ elsif mode == :LOCK
260
+ is_over_size = safe_over_size?
261
+ end
262
+
263
+ return is_over_size
264
+ end
265
+
266
+ # Rotates as necessary
267
+ # @return bool
268
+ private
269
+ def rotate_if
270
+ if reached_limit?(:LOCK)
271
+ rotation
272
+ return true
273
+
274
+ else
275
+ # no need to rotate
276
+ return false
277
+ end
278
+ end
279
+
280
+ # prepare & call #do_rotation
281
+ private
282
+ def rotation(mode=:NO_SPEC)
283
+ synchronize do
284
+ # if rotationing now by another process, return
285
+ if @psync.locked? #=> when didn't call #psync, will be return nil
286
+ return false
287
+ end
288
+
289
+ # lock another process if enable
290
+ @psync.lock
291
+
292
+ do_rotate(mode)
293
+
294
+ # unlock another process if enable
295
+ @psync.unlock
296
+ end
297
+ end
298
+
299
+ # rotate the log file and open a new one
300
+ private
301
+ def do_rotate(mode)
302
+ return nil if logf_not_usable
303
+
304
+ # check already executed rotation?
305
+ if mode != :FLUSH && !reached_limit?
306
+ return false
307
+ end
308
+
309
+ # file age rotation
310
+ if @rotate_by_term
311
+ rtn = do_term_rotate
312
+ return rtn
313
+ end
314
+
315
+ # file size rotation
316
+ cnt = 1
317
+ rotate_name = "#{@file_name}#{@rename_format}#{cnt}"
318
+ rotate_name += ".gz" if @compress
319
+
320
+ if File.exist?(rotate_name)
321
+ while File.exist?(rotate_name)
322
+ cnt += 1
323
+ rotate_name = "#{@file_name}#{@rename_format}#{cnt}"
324
+ rotate_name += ".gz" if @compress
325
+ end
326
+
327
+ rename_wait_que = Array.new
328
+ for nc in 1...cnt
329
+ break if @rotation == 1
330
+ if (@compress)
331
+ rename_wait_que << "File.rename('#{@file_name}#{@rename_format}#{nc}.gz', '#{@file_name}#{@rename_format}#{nc+1}.gz')"
332
+ else
333
+ rename_wait_que << "File.rename('#{@file_name}#{@rename_format}#{nc}', '#{@file_name}#{@rename_format}#{nc+1}')"
334
+ end
335
+
336
+ if @rotation
337
+ next if @rotation == 0
338
+ break if @rotation <= nc+1
339
+ end
340
+ end
341
+
342
+ rename_wait_que.reverse!
343
+
344
+ begin
345
+ rename_wait_que.each do |do_rename|
346
+ eval(do_rename)
347
+ end
348
+
349
+ rescue
350
+ SimpleRotate::Error.warning(SimpleRotate::Error::ROTATION_FAILED)
351
+ return false
352
+ end
353
+ end
354
+
355
+ most_recent_name = "#{@file_name}#{@rename_format}1"
356
+ post_execute_rotate(most_recent_name)
357
+ end
358
+
359
+ # Rotate the log file now, and open a new one
360
+ # for rotate by term
361
+ private
362
+ def do_term_rotate
363
+ date = Time.now.strftime(@term_format)
364
+ rotate_name = "#{@file_name}#{@rename_format}#{date}"
365
+
366
+ # Don't rotation If a file with the same name already exists
367
+ return false if File.exists?(rotate_name)
368
+ return false if File.exists?("#{rotate_name}.gz")
369
+
370
+ post_execute_rotate(rotate_name)
371
+ end
372
+
373
+ # rename log_file & generate new one
374
+ private
375
+ def post_execute_rotate(after_name)
376
+ begin
377
+ @logf.close
378
+ File.rename(@file_name, after_name)
379
+ do_compress(after_name) if @compress
380
+ prepare_logf
381
+
382
+ # sleep after rotation
383
+ sleep(@sleep_time) if @sleep_time > 0
384
+
385
+ rescue
386
+ SimpleRotate::Error.warning(SimpleRotate::Error::ROTATION_FAILED)
387
+ reopen if file_closed?
388
+ end
389
+ end
390
+
391
+ # compress rotated file
392
+ private
393
+ def do_compress(file)
394
+ contents = nil
395
+ File.open(file, File::RDONLY) do |f|
396
+ contents = f.read
397
+ end
398
+
399
+ newf = "#{file}.gz"
400
+
401
+ io = File.open(newf, File::WRONLY|File::CREAT|File::TRUNC)
402
+ Zlib::GzipWriter.wrap(io, @compress_level) do |writer|
403
+ writer.mtime = File.mtime(file).to_i
404
+ writer.orig_name = file
405
+ writer.write(contents)
406
+ end
407
+
408
+ File.delete(file)
409
+ end
410
+
411
+ # convert 'limit_size' to integer
412
+ # @param string|int $limit_size
413
+ # @return int
414
+ private
415
+ def trim_byte(limit)
416
+ return limit if limit.is_a?(Integer)
417
+
418
+ kiro = "000"
419
+ mega = kiro + "000"
420
+ giga = mega + "000"
421
+ tera = giga + "000"
422
+ limit_size = limit
423
+
424
+ if /K$/ =~ limit_size
425
+ limit_size = limit_size.sub(/K$/, "") + kiro
426
+ elsif /M$/ =~ limit_size
427
+ limit_size = limit_size.sub(/M$/, "") + mega
428
+ elsif /G$/ =~ limit_size
429
+ limit_size = limit_size.sub(/G$/, "") + giga
430
+ elsif /T$/ =~ limit_size
431
+ limit_size = limit_size.sub(/T$/, "") + tera
432
+ end
433
+
434
+ return limit_size.to_i
435
+ end
436
+ end