simple_rotate 1.0.0 → 1.1.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/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
|