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.
- checksums.yaml +4 -4
- data/lib/simple_rotate.rb +5 -1126
- data/lib/simple_rotate/accesser.rb +13 -0
- data/lib/simple_rotate/const.rb +7 -0
- data/lib/simple_rotate/core.rb +306 -0
- data/lib/simple_rotate/internal/error.rb +56 -0
- data/lib/simple_rotate/internal/log_level.rb +14 -0
- data/lib/simple_rotate/internal/process_sync.rb +115 -0
- data/lib/simple_rotate/internal/process_sync_mixin.rb +48 -0
- data/lib/simple_rotate/internal/rotate_term.rb +7 -0
- data/lib/simple_rotate/internal/validator.rb +45 -0
- data/lib/simple_rotate/private.rb +436 -0
- metadata +24 -15
- data/LICENSE.txt +0 -22
- data/README.txt +0 -2
@@ -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,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
|