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.
@@ -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