line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
## no critic: InputOutput::ProhibitOneArgSelect |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
package File::Write::Rotate; |
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
our $DATE = '2019-06-27'; # DATE |
6
|
|
|
|
|
|
|
our $VERSION = '0.321'; # VERSION |
7
|
|
|
|
|
|
|
|
8
|
3
|
|
|
3
|
|
269388
|
use 5.010001; |
|
3
|
|
|
|
|
30
|
|
9
|
3
|
|
|
3
|
|
15
|
use strict; |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
60
|
|
10
|
3
|
|
|
3
|
|
14
|
use warnings; |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
80
|
|
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
# we must not use Log::Any, looping if we are used as log output |
13
|
|
|
|
|
|
|
#use Log::Any '$log'; |
14
|
|
|
|
|
|
|
|
15
|
3
|
|
|
3
|
|
16
|
use File::Spec; |
|
3
|
|
|
|
|
5
|
|
|
3
|
|
|
|
|
80
|
|
16
|
3
|
|
|
3
|
|
1737
|
use IO::Compress::Gzip qw(gzip $GzipError); |
|
3
|
|
|
|
|
103891
|
|
|
3
|
|
|
|
|
364
|
|
17
|
3
|
|
|
3
|
|
29
|
use Scalar::Util qw(weaken); |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
141
|
|
18
|
|
|
|
|
|
|
#use Taint::Runtime qw(untaint is_tainted); |
19
|
3
|
|
|
3
|
|
1604
|
use Time::HiRes 'time'; |
|
3
|
|
|
|
|
4100
|
|
|
3
|
|
|
|
|
14
|
|
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
our $Debug; |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
sub new { |
24
|
278
|
|
|
278
|
1
|
201746
|
my $class = shift; |
25
|
278
|
|
|
|
|
1074
|
my %args0 = @_; |
26
|
|
|
|
|
|
|
|
27
|
278
|
|
|
|
|
461
|
my %args; |
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
defined($args{dir} = delete $args0{dir}) |
30
|
278
|
50
|
|
|
|
1088
|
or die "Please specify dir"; |
31
|
|
|
|
|
|
|
defined($args{prefix} = delete $args0{prefix}) |
32
|
278
|
50
|
|
|
|
752
|
or die "Please specify prefix"; |
33
|
278
|
|
100
|
|
|
1046
|
$args{suffix} = delete($args0{suffix}) // ""; |
34
|
|
|
|
|
|
|
|
35
|
278
|
|
100
|
|
|
703
|
$args{size} = delete($args0{size}) // 0; |
36
|
|
|
|
|
|
|
|
37
|
278
|
|
|
|
|
474
|
$args{period} = delete($args0{period}); |
38
|
278
|
100
|
|
|
|
556
|
if ($args{period}) { |
39
|
39
|
50
|
|
|
|
229
|
$args{period} =~ /\A(daily|day|month|monthly|year|yearly)\z/ |
40
|
|
|
|
|
|
|
or die "Invalid period, please use daily/monthly/yearly"; |
41
|
|
|
|
|
|
|
} |
42
|
|
|
|
|
|
|
|
43
|
278
|
|
|
|
|
633
|
for (map {"hook_$_"} qw(before_rotate after_rotate after_create |
|
1390
|
|
|
|
|
2981
|
|
44
|
|
|
|
|
|
|
before_write a)) { |
45
|
1390
|
100
|
|
|
|
2741
|
next unless $args0{$_}; |
46
|
17
|
|
|
|
|
36
|
$args{$_} = delete($args0{$_}); |
47
|
|
|
|
|
|
|
die "Invalid $_, please supply a coderef" |
48
|
17
|
50
|
|
|
|
49
|
unless ref($args{$_}) eq 'CODE'; |
49
|
|
|
|
|
|
|
} |
50
|
|
|
|
|
|
|
|
51
|
278
|
100
|
100
|
|
|
1106
|
if (!$args{period} && !$args{size}) { |
52
|
21
|
|
|
|
|
40
|
$args{size} = 10 * 1024 * 1024; |
53
|
|
|
|
|
|
|
} |
54
|
|
|
|
|
|
|
|
55
|
278
|
|
100
|
|
|
836
|
$args{histories} = delete($args0{histories}) // 10; |
56
|
|
|
|
|
|
|
|
57
|
278
|
|
|
|
|
465
|
$args{binmode} = delete($args0{binmode}); |
58
|
|
|
|
|
|
|
|
59
|
278
|
|
|
|
|
600
|
$args{buffer_size} = delete($args0{buffer_size}); |
60
|
|
|
|
|
|
|
|
61
|
278
|
|
100
|
|
|
737
|
$args{lock_mode} = delete($args0{lock_mode}) // 'write'; |
62
|
278
|
50
|
|
|
|
1424
|
$args{lock_mode} =~ /\A(none|write|exclusive)\z/ |
63
|
|
|
|
|
|
|
or die "Invalid lock_mode, please use none/write/exclusive"; |
64
|
|
|
|
|
|
|
|
65
|
278
|
|
|
|
|
485
|
$args{rotate_probability} = delete($args0{rotate_probability}); |
66
|
278
|
50
|
|
|
|
532
|
if (defined $args{rotate_probability}) { |
67
|
0
|
0
|
0
|
|
|
0
|
$args{rotate_probability} > 0 && $args{rotate_probability} < 1.0 |
68
|
|
|
|
|
|
|
or die "Invalid rotate_probability, must be 0 < x < 1"; |
69
|
|
|
|
|
|
|
} |
70
|
|
|
|
|
|
|
|
71
|
278
|
50
|
|
|
|
619
|
if (keys %args0) { |
72
|
0
|
|
|
|
|
0
|
die "Unknown arguments to new(): ".join(", ", sort keys %args0); |
73
|
|
|
|
|
|
|
} |
74
|
|
|
|
|
|
|
|
75
|
278
|
|
|
|
|
513
|
$args{_buffer} = []; |
76
|
|
|
|
|
|
|
|
77
|
278
|
|
|
|
|
586
|
my $self = bless \%args, $class; |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
$self->{_exclusive_lock} = $self->_get_lock |
80
|
278
|
100
|
|
|
|
654
|
if $self->{lock_mode} eq 'exclusive'; |
81
|
|
|
|
|
|
|
|
82
|
278
|
|
|
|
|
822
|
$self; |
83
|
|
|
|
|
|
|
} |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
sub buffer_size { |
86
|
2
|
|
|
2
|
1
|
11
|
my $self = shift; |
87
|
2
|
100
|
|
|
|
7
|
if (@_) { |
88
|
1
|
|
|
|
|
3
|
my $old = $self->{buffer_size}; |
89
|
1
|
|
|
|
|
2
|
$self->{buffer_size} = $_[0]; |
90
|
1
|
|
|
|
|
2
|
return $old; |
91
|
|
|
|
|
|
|
} else { |
92
|
1
|
|
|
|
|
5
|
return $self->{buffer_size}; |
93
|
|
|
|
|
|
|
} |
94
|
|
|
|
|
|
|
} |
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
sub handle { |
97
|
1
|
|
|
1
|
1
|
8
|
my $self = shift; |
98
|
1
|
|
|
|
|
3
|
$self->{_fh}; |
99
|
|
|
|
|
|
|
} |
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
sub path { |
102
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
103
|
0
|
|
|
|
|
0
|
$self->{_fp}; |
104
|
|
|
|
|
|
|
} |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
# file path, without the rotate suffix |
107
|
|
|
|
|
|
|
sub _file_path { |
108
|
801
|
|
|
801
|
|
3413
|
my ($self) = @_; |
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
# _now is calculated every time we access this method |
111
|
801
|
|
|
|
|
2086
|
$self->{_now} = time(); |
112
|
|
|
|
|
|
|
|
113
|
801
|
|
|
|
|
4383
|
my @lt = localtime($self->{_now}); |
114
|
801
|
|
|
|
|
1806
|
$lt[5] += 1900; |
115
|
801
|
|
|
|
|
1033
|
$lt[4]++; |
116
|
|
|
|
|
|
|
|
117
|
801
|
|
|
|
|
1012
|
my $period; |
118
|
801
|
100
|
|
|
|
1699
|
if ($self->{period}) { |
119
|
110
|
100
|
|
|
|
714
|
if ($self->{period} =~ /year/i) { |
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
120
|
13
|
|
|
|
|
51
|
$period = sprintf("%04d", $lt[5]); |
121
|
|
|
|
|
|
|
} elsif ($self->{period} =~ /month/) { |
122
|
13
|
|
|
|
|
62
|
$period = sprintf("%04d-%02d", $lt[5], $lt[4]); |
123
|
|
|
|
|
|
|
} elsif ($self->{period} =~ /day|daily/) { |
124
|
84
|
|
|
|
|
407
|
$period = sprintf("%04d-%02d-%02d", $lt[5], $lt[4], $lt[3]); |
125
|
|
|
|
|
|
|
} |
126
|
|
|
|
|
|
|
} else { |
127
|
691
|
|
|
|
|
1028
|
$period = ""; |
128
|
|
|
|
|
|
|
} |
129
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
my $path = join( |
131
|
|
|
|
|
|
|
'', |
132
|
|
|
|
|
|
|
$self->{dir}, '/', |
133
|
|
|
|
|
|
|
$self->{prefix}, |
134
|
|
|
|
|
|
|
length($period) ? ".$period" : "", |
135
|
|
|
|
|
|
|
$self->{suffix}, |
136
|
801
|
100
|
|
|
|
2715
|
); |
137
|
801
|
100
|
|
|
|
1536
|
if (wantarray) { |
138
|
308
|
|
|
|
|
1050
|
return ($path, $period); |
139
|
|
|
|
|
|
|
} else { |
140
|
493
|
|
|
|
|
1378
|
return $path; |
141
|
|
|
|
|
|
|
} |
142
|
|
|
|
|
|
|
} |
143
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
sub lock_file_path { |
145
|
996
|
|
|
996
|
1
|
3477
|
my ($self) = @_; |
146
|
996
|
|
|
|
|
20508
|
return File::Spec->catfile($self->{dir}, $self->{prefix} . '.lck'); |
147
|
|
|
|
|
|
|
} |
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
sub _get_lock { |
150
|
592
|
|
|
592
|
|
987
|
my ($self) = @_; |
151
|
592
|
100
|
|
|
|
1398
|
return undef if $self->{lock_mode} eq 'none'; |
152
|
588
|
100
|
|
|
|
1153
|
return $self->{_weak_lock} if defined($self->{_weak_lock}); |
153
|
|
|
|
|
|
|
|
154
|
510
|
|
|
|
|
4642
|
require File::Flock::Retry; |
155
|
510
|
|
|
|
|
4827
|
my $lock = File::Flock::Retry->lock($self->lock_file_path); |
156
|
510
|
|
|
|
|
67191
|
$self->{_weak_lock} = $lock; |
157
|
510
|
|
|
|
|
2155
|
weaken $self->{_weak_lock}; |
158
|
510
|
|
|
|
|
1131
|
return $lock; |
159
|
|
|
|
|
|
|
} |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
# will return \@files. each entry is [filename without compress suffix, |
162
|
|
|
|
|
|
|
# rotate_suffix (for sorting), period (for sorting), compress suffix (for |
163
|
|
|
|
|
|
|
# renaming back)] |
164
|
|
|
|
|
|
|
sub _get_files { |
165
|
98
|
|
|
98
|
|
155
|
my ($self) = @_; |
166
|
|
|
|
|
|
|
|
167
|
98
|
50
|
|
|
|
2636
|
opendir my ($dh), $self->{dir} or do { |
168
|
0
|
|
|
|
|
0
|
warn "Can't opendir '$self->{dir}': $!"; |
169
|
0
|
|
|
|
|
0
|
return; |
170
|
|
|
|
|
|
|
}; |
171
|
|
|
|
|
|
|
|
172
|
98
|
|
|
|
|
245
|
my @files; |
173
|
98
|
|
|
|
|
1568
|
while (my $e = readdir($dh)) { |
174
|
463
|
100
|
|
|
|
822
|
my $cs; $cs = $1 if $e =~ s/(\.gz)\z//; # compress suffix |
|
463
|
|
|
|
|
942
|
|
175
|
463
|
100
|
|
|
|
3898
|
next unless $e =~ /\A\Q$self->{prefix}\E |
176
|
|
|
|
|
|
|
(?:\. (?<period>\d{4}(?:-\d\d(?:-\d\d)?)?) )? |
177
|
|
|
|
|
|
|
\Q$self->{suffix}\E |
178
|
|
|
|
|
|
|
(?:\. (?<rotate_suffix>\d+) )? |
179
|
|
|
|
|
|
|
\z |
180
|
|
|
|
|
|
|
/x; |
181
|
|
|
|
|
|
|
push @files, |
182
|
3
|
|
100
|
3
|
|
5345
|
[ $e, $+{rotate_suffix} // 0, $+{period} // "", $cs // "" ]; |
|
3
|
|
100
|
|
|
1042
|
|
|
3
|
|
100
|
|
|
5140
|
|
|
152
|
|
|
|
|
2538
|
|
183
|
|
|
|
|
|
|
} |
184
|
98
|
|
|
|
|
1088
|
closedir($dh); |
185
|
|
|
|
|
|
|
|
186
|
98
|
50
|
|
|
|
635
|
[ sort { $a->[2] cmp $b->[2] || $b->[1] <=> $a->[1] } @files ]; |
|
123
|
|
|
|
|
622
|
|
187
|
|
|
|
|
|
|
} |
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
# rename (increase rotation suffix) and keep only n histories. note: failure in |
190
|
|
|
|
|
|
|
# rotating should not be fatal, we just warn and return. |
191
|
|
|
|
|
|
|
sub _rotate_and_delete { |
192
|
85
|
|
|
85
|
|
9516
|
my ($self, %opts) = @_; |
193
|
|
|
|
|
|
|
|
194
|
85
|
|
|
|
|
211
|
my $delete_only = $opts{delete_only}; |
195
|
85
|
|
|
|
|
173
|
my $lock = $self->_get_lock; |
196
|
|
|
|
|
|
|
CASE: |
197
|
|
|
|
|
|
|
{ |
198
|
85
|
50
|
|
|
|
148
|
my $files = $self->_get_files or last CASE; |
|
85
|
|
|
|
|
168
|
|
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
# is there a compression process in progress? this is marked by the |
201
|
|
|
|
|
|
|
# existence of <prefix>-compress.pid PID file. |
202
|
|
|
|
|
|
|
# |
203
|
|
|
|
|
|
|
# XXX check validity of PID file, otherwise a stale PID file will always |
204
|
|
|
|
|
|
|
# prevent rotation to be done |
205
|
85
|
50
|
|
|
|
1069
|
if (-f "$self->{dir}/$self->{prefix}-compress.pid") { |
206
|
0
|
|
|
|
|
0
|
warn "Compression is in progress, rotation is postponed"; |
207
|
0
|
|
|
|
|
0
|
last CASE; |
208
|
|
|
|
|
|
|
} |
209
|
|
|
|
|
|
|
|
210
|
7
|
|
|
|
|
35
|
$self->{hook_before_rotate}->($self, [map {$_->[0]} @$files]) |
211
|
85
|
100
|
|
|
|
324
|
if $self->{hook_before_rotate}; |
212
|
|
|
|
|
|
|
|
213
|
85
|
|
|
|
|
3702
|
my @deleted; |
214
|
|
|
|
|
|
|
my @renamed; |
215
|
|
|
|
|
|
|
|
216
|
85
|
|
|
|
|
0
|
my $i; |
217
|
85
|
|
|
|
|
178
|
my $dir = $self->{dir}; |
218
|
85
|
100
|
|
|
|
239
|
my $rotating_period = @$files ? $files->[-1][2] : undef; |
219
|
85
|
|
|
|
|
199
|
for my $f (@$files) { |
220
|
122
|
|
|
|
|
336
|
my ($orig, $rs, $period, $cs) = @$f; |
221
|
122
|
|
|
|
|
539
|
$i++; |
222
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
#say "DEBUG: is_tainted \$dir? ".is_tainted($dir); |
224
|
|
|
|
|
|
|
#say "DEBUG: is_tainted \$orig? ".is_tainted($orig); |
225
|
|
|
|
|
|
|
#say "DEBUG: is_tainted \$cs? ".is_tainted($cs); |
226
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
# TODO actually, it's more proper to taint near the source (in this |
228
|
|
|
|
|
|
|
# case, _get_files) |
229
|
|
|
|
|
|
|
#untaint \$orig; |
230
|
122
|
|
|
|
|
541
|
($orig) = $orig =~ /(.*)/s; # we use this instead, no module needed |
231
|
|
|
|
|
|
|
|
232
|
122
|
100
|
|
|
|
376
|
if ($i <= @$files - $self->{histories}) { |
233
|
22
|
50
|
|
|
|
49
|
say "DEBUG: Deleting old rotated file $dir/$orig$cs ..." |
234
|
|
|
|
|
|
|
if $Debug; |
235
|
22
|
50
|
|
|
|
913
|
if (unlink "$dir/$orig$cs") { |
236
|
22
|
|
|
|
|
118
|
push @deleted, "$orig$cs"; |
237
|
|
|
|
|
|
|
} else { |
238
|
0
|
|
|
|
|
0
|
warn "Can't delete $dir/$orig$cs: $!"; |
239
|
|
|
|
|
|
|
} |
240
|
22
|
|
|
|
|
73
|
next; |
241
|
|
|
|
|
|
|
} |
242
|
100
|
100
|
66
|
|
|
455
|
if (!$delete_only && defined($rotating_period) && $period eq $rotating_period) { |
|
|
|
100
|
|
|
|
|
243
|
48
|
|
|
|
|
89
|
my $new = $orig; |
244
|
48
|
100
|
|
|
|
107
|
if ($rs) { |
245
|
14
|
|
|
|
|
90
|
$new =~ s/\.(\d+)\z/"." . ($1+1)/e; |
|
14
|
|
|
|
|
113
|
|
246
|
|
|
|
|
|
|
} else { |
247
|
34
|
|
|
|
|
64
|
$new .= ".1"; |
248
|
|
|
|
|
|
|
} |
249
|
48
|
50
|
|
|
|
111
|
if ($new ne $orig) { |
250
|
48
|
50
|
|
|
|
112
|
say "DEBUG: Renaming rotated file $dir/$orig$cs -> ". |
251
|
|
|
|
|
|
|
"$dir/$new$cs ..." if $Debug; |
252
|
48
|
50
|
|
|
|
1576
|
if (rename "$dir/$orig$cs", "$dir/$new$cs") { |
253
|
48
|
|
|
|
|
315
|
push @renamed, "$new$cs"; |
254
|
|
|
|
|
|
|
} else { |
255
|
0
|
|
|
|
|
0
|
warn "Can't rename '$dir/$orig$cs' -> '$dir/$new$cs': $!"; |
256
|
|
|
|
|
|
|
} |
257
|
|
|
|
|
|
|
} |
258
|
|
|
|
|
|
|
} |
259
|
|
|
|
|
|
|
} |
260
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
$self->{hook_after_rotate}->($self, \@renamed, \@deleted) |
262
|
85
|
100
|
|
|
|
466
|
if $self->{hook_after_rotate}; |
263
|
|
|
|
|
|
|
} # CASE |
264
|
|
|
|
|
|
|
} |
265
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
sub _open { |
267
|
298
|
|
|
298
|
|
485
|
my $self = shift; |
268
|
|
|
|
|
|
|
|
269
|
298
|
|
|
|
|
542
|
my ($fp, $period) = $self->_file_path; |
270
|
298
|
50
|
|
|
|
11314
|
open $self->{_fh}, ">>", $fp or die "Can't open '$fp': $!"; |
271
|
298
|
100
|
|
|
|
1186
|
if (defined $self->{binmode}) { |
272
|
2
|
50
|
|
|
|
8
|
if ($self->{binmode} eq "1") { |
273
|
0
|
|
|
|
|
0
|
binmode $self->{_fh}; |
274
|
|
|
|
|
|
|
} else { |
275
|
|
|
|
|
|
|
binmode $self->{_fh}, $self->{binmode} |
276
|
2
|
50
|
|
|
|
30
|
or die "Can't set PerlIO layer on '$fp' ". |
277
|
|
|
|
|
|
|
"to '$self->{binmode}': $!"; |
278
|
|
|
|
|
|
|
} |
279
|
|
|
|
|
|
|
} |
280
|
298
|
|
|
|
|
1151
|
my $oldfh = select $self->{_fh}; |
281
|
298
|
|
|
|
|
866
|
$| = 1; |
282
|
298
|
|
|
|
|
945
|
select $oldfh; # set autoflush |
283
|
298
|
|
|
|
|
689
|
$self->{_fp} = $fp; |
284
|
298
|
100
|
|
|
|
1147
|
$self->{hook_after_create}->($self) if $self->{hook_after_create}; |
285
|
|
|
|
|
|
|
} |
286
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
# (re)open file and optionally rotate if necessary |
288
|
|
|
|
|
|
|
sub _rotate_and_open { |
289
|
|
|
|
|
|
|
|
290
|
493
|
|
|
493
|
|
787
|
my $self = shift; |
291
|
493
|
|
|
|
|
896
|
my ($do_open, $do_rotate) = @_; |
292
|
493
|
|
|
|
|
787
|
my $fp; |
293
|
|
|
|
|
|
|
my %rotate_params; |
294
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
CASE: |
296
|
|
|
|
|
|
|
{ |
297
|
|
|
|
|
|
|
# if instructed, only do rotate some of the time to shave overhead |
298
|
493
|
0
|
33
|
|
|
675
|
if ($self->{rotate_probability} && $self->{_fh}) { |
|
493
|
|
|
|
|
1255
|
|
299
|
0
|
0
|
|
|
|
0
|
last CASE if rand() > $self->{rotate_probability}; |
300
|
|
|
|
|
|
|
} |
301
|
|
|
|
|
|
|
|
302
|
493
|
|
|
|
|
1033
|
$fp = $self->_file_path; |
303
|
493
|
100
|
|
|
|
6320
|
unless (-e $fp) { |
304
|
50
|
|
|
|
|
133
|
$do_open++; |
305
|
50
|
|
|
|
|
80
|
$do_rotate++; |
306
|
50
|
|
|
|
|
134
|
$rotate_params{delete_only} = 1; |
307
|
50
|
|
|
|
|
103
|
last CASE; |
308
|
|
|
|
|
|
|
} |
309
|
|
|
|
|
|
|
|
310
|
|
|
|
|
|
|
# file is not opened yet, open |
311
|
443
|
100
|
|
|
|
1584
|
unless ($self->{_fh}) { |
312
|
219
|
|
|
|
|
510
|
$self->_open; |
313
|
|
|
|
|
|
|
} |
314
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
# period has changed, rotate |
316
|
443
|
50
|
|
|
|
1031
|
if ($self->{_fp} ne $fp) { |
317
|
0
|
|
|
|
|
0
|
$do_rotate++; |
318
|
0
|
|
|
|
|
0
|
$rotate_params{delete_only} = 1; |
319
|
0
|
|
|
|
|
0
|
last CASE; |
320
|
|
|
|
|
|
|
} |
321
|
|
|
|
|
|
|
|
322
|
|
|
|
|
|
|
# check whether size has been exceeded |
323
|
443
|
|
|
|
|
619
|
my $inode; |
324
|
|
|
|
|
|
|
|
325
|
443
|
100
|
|
|
|
1003
|
if ($self->{size} > 0) { |
326
|
|
|
|
|
|
|
|
327
|
437
|
|
|
|
|
3896
|
my @st = stat($self->{_fh}); |
328
|
437
|
|
|
|
|
1048
|
my $size = $st[7]; |
329
|
437
|
|
|
|
|
592
|
$inode = $st[1]; |
330
|
|
|
|
|
|
|
|
331
|
437
|
100
|
|
|
|
1089
|
if ($size >= $self->{size}) { |
332
|
28
|
50
|
|
|
|
89
|
say "DEBUG: Size of $self->{_fp} is $size, exceeds $self->{size}, rotating ..." |
333
|
|
|
|
|
|
|
if $Debug; |
334
|
28
|
|
|
|
|
53
|
$do_rotate++; |
335
|
28
|
|
|
|
|
77
|
last CASE; |
336
|
|
|
|
|
|
|
} else { |
337
|
|
|
|
|
|
|
# stat the current file (not our handle _fp) |
338
|
409
|
|
|
|
|
4132
|
my @st = stat($fp); |
339
|
409
|
50
|
|
|
|
1141
|
die "Can't stat '$fp': $!" unless @st; |
340
|
409
|
|
|
|
|
676
|
my $finode = $st[1]; |
341
|
|
|
|
|
|
|
|
342
|
|
|
|
|
|
|
# check whether other process has rename/rotate under us (for |
343
|
|
|
|
|
|
|
# example, 'prefix' has been moved to 'prefix.1'), in which case |
344
|
|
|
|
|
|
|
# we need to reopen |
345
|
409
|
100
|
66
|
|
|
2072
|
if (defined($inode) && $finode != $inode) { |
346
|
1
|
|
|
|
|
4
|
$do_open++; |
347
|
|
|
|
|
|
|
} |
348
|
|
|
|
|
|
|
} |
349
|
|
|
|
|
|
|
|
350
|
|
|
|
|
|
|
} |
351
|
|
|
|
|
|
|
} # CASE |
352
|
|
|
|
|
|
|
|
353
|
493
|
100
|
|
|
|
1133
|
$self->_rotate_and_delete(%rotate_params) if $do_rotate; |
354
|
493
|
100
|
100
|
|
|
5017
|
$self->_open if $do_rotate || $do_open; # (re)open |
355
|
|
|
|
|
|
|
} |
356
|
|
|
|
|
|
|
|
357
|
|
|
|
|
|
|
sub write { |
358
|
493
|
|
|
493
|
1
|
131656
|
my $self = shift; |
359
|
|
|
|
|
|
|
|
360
|
|
|
|
|
|
|
# the buffering implementation is currently pretty naive. it assume any |
361
|
|
|
|
|
|
|
# die() as a write failure and store the message to buffer. |
362
|
|
|
|
|
|
|
|
363
|
|
|
|
|
|
|
# FYI: if privilege is dropped from superuser, the failure is usually at |
364
|
|
|
|
|
|
|
# locking the lock file (permission denied). |
365
|
|
|
|
|
|
|
|
366
|
493
|
|
|
|
|
803
|
my @msg = (map( {@$_} @{ $self->{_buffer} } ), @_); |
|
6
|
|
|
|
|
18
|
|
|
493
|
|
|
|
|
1385
|
|
367
|
|
|
|
|
|
|
|
368
|
493
|
|
|
|
|
868
|
eval { |
369
|
493
|
|
|
|
|
1052
|
my $lock = $self->_get_lock; |
370
|
|
|
|
|
|
|
|
371
|
493
|
|
|
|
|
1543
|
$self->_rotate_and_open; |
372
|
|
|
|
|
|
|
|
373
|
|
|
|
|
|
|
$self->{hook_before_write}->($self, \@msg, $self->{_fh}) |
374
|
493
|
100
|
|
|
|
3765
|
if $self->{hook_before_write}; |
375
|
|
|
|
|
|
|
|
376
|
488
|
|
|
|
|
3208
|
print { $self->{_fh} } @msg; |
|
488
|
|
|
|
|
8371
|
|
377
|
488
|
|
|
|
|
3384
|
$self->{_buffer} = []; |
378
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
}; |
380
|
493
|
|
|
|
|
38096
|
my $err = $@; |
381
|
|
|
|
|
|
|
|
382
|
493
|
100
|
|
|
|
1889
|
if ($err) { |
383
|
5
|
100
|
50
|
|
|
18
|
if (($self->{buffer_size} // 0) > @{ $self->{_buffer} }) { |
|
5
|
|
|
|
|
17
|
|
384
|
|
|
|
|
|
|
# put message to buffer temporarily |
385
|
4
|
|
|
|
|
6
|
push @{ $self->{_buffer} }, [@_]; |
|
4
|
|
|
|
|
30
|
|
386
|
|
|
|
|
|
|
} else { |
387
|
|
|
|
|
|
|
# buffer is already full, let's dump the buffered + current message |
388
|
|
|
|
|
|
|
# to the die message anyway. |
389
|
|
|
|
|
|
|
die join( |
390
|
|
|
|
|
|
|
"", |
391
|
|
|
|
|
|
|
"Can't write", |
392
|
|
|
|
|
|
|
( |
393
|
1
|
|
|
|
|
4
|
@{ $self->{_buffer} } |
394
|
|
|
|
|
|
|
? " (buffer is full, " |
395
|
1
|
50
|
|
|
|
3
|
. scalar(@{ $self->{_buffer} }) |
|
1
|
|
|
|
|
15
|
|
396
|
|
|
|
|
|
|
. " message(s))" |
397
|
|
|
|
|
|
|
: "" |
398
|
|
|
|
|
|
|
), |
399
|
|
|
|
|
|
|
": $err, message(s)=", |
400
|
|
|
|
|
|
|
@msg |
401
|
|
|
|
|
|
|
); |
402
|
|
|
|
|
|
|
} |
403
|
|
|
|
|
|
|
} |
404
|
|
|
|
|
|
|
} |
405
|
|
|
|
|
|
|
|
406
|
|
|
|
|
|
|
sub compress { |
407
|
13
|
|
|
13
|
1
|
10510
|
my ($self) = shift; |
408
|
|
|
|
|
|
|
|
409
|
13
|
|
|
|
|
38
|
my $lock = $self->_get_lock; |
410
|
13
|
|
|
|
|
43
|
my $files_ref = $self->_get_files; |
411
|
13
|
|
|
|
|
28
|
my $done_compression = 0; |
412
|
|
|
|
|
|
|
|
413
|
13
|
50
|
|
|
|
24
|
if (@{$files_ref}) { |
|
13
|
|
|
|
|
37
|
|
414
|
13
|
|
|
|
|
707
|
require Proc::PID::File; |
415
|
|
|
|
|
|
|
|
416
|
|
|
|
|
|
|
my $pid = Proc::PID::File->new( |
417
|
|
|
|
|
|
|
dir => $self->{dir}, |
418
|
13
|
|
|
|
|
2430
|
name => "$self->{prefix}-compress", |
419
|
|
|
|
|
|
|
verify => 1, |
420
|
|
|
|
|
|
|
); |
421
|
13
|
|
|
|
|
453
|
my $latest_period = $files_ref->[-1][2]; |
422
|
|
|
|
|
|
|
|
423
|
13
|
50
|
|
|
|
38
|
if ($pid->alive) { |
424
|
0
|
|
|
|
|
0
|
warn "Another compression is in progress"; |
425
|
|
|
|
|
|
|
} else { |
426
|
13
|
|
|
|
|
1629
|
my @tocompress; |
427
|
|
|
|
|
|
|
#use DD; dd $self; |
428
|
13
|
|
|
|
|
24
|
for my $file_ref (@{$files_ref}) { |
|
13
|
|
|
|
|
34
|
|
429
|
30
|
|
|
|
|
51
|
my ($orig, $rs, $period, $cs) = @{ $file_ref }; |
|
30
|
|
|
|
|
73
|
|
430
|
|
|
|
|
|
|
#say "D:compress: orig=<$orig> rs=<$rs> period=<$period> cs=<$cs>"; |
431
|
30
|
50
|
|
|
|
68
|
next if $cs; # already compressed |
432
|
30
|
100
|
100
|
|
|
143
|
next if !$self->{period} && !$rs; # not old file |
433
|
25
|
100
|
100
|
|
|
94
|
next if $self->{period} && $period eq $latest_period; # not old file |
434
|
17
|
|
|
|
|
206
|
push @tocompress, File::Spec->catfile($self->{dir}, $orig); |
435
|
|
|
|
|
|
|
} |
436
|
|
|
|
|
|
|
|
437
|
13
|
50
|
|
|
|
34
|
if (@tocompress) { |
438
|
13
|
|
|
|
|
24
|
for my $file (@tocompress) { |
439
|
|
|
|
|
|
|
gzip($file => "$file.gz") |
440
|
17
|
50
|
|
|
|
86
|
or do { warn "gzip failed: $GzipError\n"; next }; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
441
|
17
|
|
|
|
|
40152
|
unlink $file; |
442
|
|
|
|
|
|
|
} |
443
|
13
|
|
|
|
|
108
|
$done_compression = 1; |
444
|
|
|
|
|
|
|
} |
445
|
|
|
|
|
|
|
} |
446
|
|
|
|
|
|
|
} |
447
|
|
|
|
|
|
|
|
448
|
13
|
|
|
|
|
1309
|
return $done_compression; |
449
|
|
|
|
|
|
|
|
450
|
|
|
|
|
|
|
} |
451
|
|
|
|
|
|
|
|
452
|
|
|
|
|
|
|
sub DESTROY { |
453
|
278
|
|
|
278
|
|
141267
|
my ($self) = @_; |
454
|
|
|
|
|
|
|
|
455
|
|
|
|
|
|
|
# Proc::PID::File's DESTROY seem to create an empty PID file, remove it. |
456
|
278
|
|
|
|
|
8139
|
unlink "$self->{dir}/$self->{prefix}-compress.pid"; |
457
|
|
|
|
|
|
|
} |
458
|
|
|
|
|
|
|
|
459
|
|
|
|
|
|
|
1; |
460
|
|
|
|
|
|
|
|
461
|
|
|
|
|
|
|
# ABSTRACT: Write to files that archive/rotate themselves |
462
|
|
|
|
|
|
|
|
463
|
|
|
|
|
|
|
__END__ |
464
|
|
|
|
|
|
|
|
465
|
|
|
|
|
|
|
=pod |
466
|
|
|
|
|
|
|
|
467
|
|
|
|
|
|
|
=encoding UTF-8 |
468
|
|
|
|
|
|
|
|
469
|
|
|
|
|
|
|
=head1 NAME |
470
|
|
|
|
|
|
|
|
471
|
|
|
|
|
|
|
File::Write::Rotate - Write to files that archive/rotate themselves |
472
|
|
|
|
|
|
|
|
473
|
|
|
|
|
|
|
=head1 VERSION |
474
|
|
|
|
|
|
|
|
475
|
|
|
|
|
|
|
This document describes version 0.321 of File::Write::Rotate (from Perl distribution File-Write-Rotate), released on 2019-06-27. |
476
|
|
|
|
|
|
|
|
477
|
|
|
|
|
|
|
=head1 SYNOPSIS |
478
|
|
|
|
|
|
|
|
479
|
|
|
|
|
|
|
use File::Write::Rotate; |
480
|
|
|
|
|
|
|
|
481
|
|
|
|
|
|
|
my $fwr = File::Write::Rotate->new( |
482
|
|
|
|
|
|
|
dir => '/var/log', # required |
483
|
|
|
|
|
|
|
prefix => 'myapp', # required |
484
|
|
|
|
|
|
|
#suffix => '.log', # default is '' |
485
|
|
|
|
|
|
|
size => 25*1024*1024, # default is 10MB, unless period is set |
486
|
|
|
|
|
|
|
histories => 12, # default is 10 |
487
|
|
|
|
|
|
|
#buffer_size => 100, # default is none |
488
|
|
|
|
|
|
|
); |
489
|
|
|
|
|
|
|
|
490
|
|
|
|
|
|
|
# write, will write to /var/log/myapp.log, automatically rotate old log files |
491
|
|
|
|
|
|
|
# to myapp.log.1 when myapp.log reaches 25MB. will keep old log files up to |
492
|
|
|
|
|
|
|
# myapp.log.12. |
493
|
|
|
|
|
|
|
$fwr->write("This is a line\n"); |
494
|
|
|
|
|
|
|
$fwr->write("This is", " another line\n"); |
495
|
|
|
|
|
|
|
|
496
|
|
|
|
|
|
|
To compressing old log files: |
497
|
|
|
|
|
|
|
|
498
|
|
|
|
|
|
|
$fwr->compress; |
499
|
|
|
|
|
|
|
|
500
|
|
|
|
|
|
|
This is usually done in a separate process, because it potentially takes a long |
501
|
|
|
|
|
|
|
time if the files to compress are large; we are rotating automatically in |
502
|
|
|
|
|
|
|
write() so doing automatic compression too would annoyingly block writer for a |
503
|
|
|
|
|
|
|
potentially long time. |
504
|
|
|
|
|
|
|
|
505
|
|
|
|
|
|
|
=head1 DESCRIPTION |
506
|
|
|
|
|
|
|
|
507
|
|
|
|
|
|
|
This module can be used to write to file, usually for logging, that can rotate |
508
|
|
|
|
|
|
|
itself. File will be opened in append mode. By default, locking will be done to |
509
|
|
|
|
|
|
|
avoid conflict when there are multiple writers. Rotation can be done by size |
510
|
|
|
|
|
|
|
(after a certain size is reached), by time (daily/monthly/yearly), or both. |
511
|
|
|
|
|
|
|
|
512
|
|
|
|
|
|
|
I first wrote this module for logging script STDERR output to files (see |
513
|
|
|
|
|
|
|
L<Tie::Handle::FileWriteRotate>). |
514
|
|
|
|
|
|
|
|
515
|
|
|
|
|
|
|
=for Pod::Coverage ^(file_path|DESTROY)$ |
516
|
|
|
|
|
|
|
|
517
|
|
|
|
|
|
|
=head1 ATTRIBUTES |
518
|
|
|
|
|
|
|
|
519
|
|
|
|
|
|
|
=head2 buffer_size => int |
520
|
|
|
|
|
|
|
|
521
|
|
|
|
|
|
|
Get or set buffer size. If set to a value larger than 0, then when a write() |
522
|
|
|
|
|
|
|
failed, instead of dying, the message will be stored in an internal buffer first |
523
|
|
|
|
|
|
|
(a regular Perl array). When the number of items in the buffer exceeds this |
524
|
|
|
|
|
|
|
size, then write() will die upon failure. Otherwise, every write() will try to |
525
|
|
|
|
|
|
|
flush the buffer. |
526
|
|
|
|
|
|
|
|
527
|
|
|
|
|
|
|
Can be used for example when a program runs as superuser/root then temporarily |
528
|
|
|
|
|
|
|
drops privilege to a normal user. During this period, logging can fail because |
529
|
|
|
|
|
|
|
the program cannot lock the lock file or write to the logging directory. Before |
530
|
|
|
|
|
|
|
dropping privilege, the program can set buffer_size to some larger-than-zero |
531
|
|
|
|
|
|
|
value to hold the messages emitted during dropping privilege. The next write() |
532
|
|
|
|
|
|
|
as the superuser/root will succeed and flush the buffer to disk (provided there |
533
|
|
|
|
|
|
|
is no other error condition, of course). |
534
|
|
|
|
|
|
|
|
535
|
|
|
|
|
|
|
=head2 path => str (ro) |
536
|
|
|
|
|
|
|
|
537
|
|
|
|
|
|
|
Current file's path. |
538
|
|
|
|
|
|
|
|
539
|
|
|
|
|
|
|
=head2 handle => (ro) |
540
|
|
|
|
|
|
|
|
541
|
|
|
|
|
|
|
Current file handle. You should not use this directly, but use write() instead. |
542
|
|
|
|
|
|
|
This attribute is provided for special circumstances (e.g. in hooks, see example |
543
|
|
|
|
|
|
|
in the hook section). |
544
|
|
|
|
|
|
|
|
545
|
|
|
|
|
|
|
=head2 hook_before_write => code |
546
|
|
|
|
|
|
|
|
547
|
|
|
|
|
|
|
Will be called by write() before actually writing to filehandle (but after |
548
|
|
|
|
|
|
|
locking is done). Code will be passed ($self, \@msgs, $fh) where @msgs is an |
549
|
|
|
|
|
|
|
array of strings to be written (the contents of buffer, if any, plus arguments |
550
|
|
|
|
|
|
|
passed to write()) and $fh is the filehandle. |
551
|
|
|
|
|
|
|
|
552
|
|
|
|
|
|
|
=head2 hook_before_rotate => code |
553
|
|
|
|
|
|
|
|
554
|
|
|
|
|
|
|
Will be called by the rotating routine before actually doing rotating. Code will |
555
|
|
|
|
|
|
|
be passed ($self). |
556
|
|
|
|
|
|
|
|
557
|
|
|
|
|
|
|
This can be used to write a footer to the end of each file, e.g.: |
558
|
|
|
|
|
|
|
|
559
|
|
|
|
|
|
|
# hook_before_rotate |
560
|
|
|
|
|
|
|
my ($self) = @_; |
561
|
|
|
|
|
|
|
my $fh = $self->handle; |
562
|
|
|
|
|
|
|
print $fh "Some footer\n"; |
563
|
|
|
|
|
|
|
|
564
|
|
|
|
|
|
|
Since this hook is indirectly called by write(), locking is already done. |
565
|
|
|
|
|
|
|
|
566
|
|
|
|
|
|
|
=head2 hook_after_rotate => code |
567
|
|
|
|
|
|
|
|
568
|
|
|
|
|
|
|
Will be called by the rotating routine after the rotating process. Code will be |
569
|
|
|
|
|
|
|
passed ($self, \@renamed, \@deleted) where @renamed is array of new filenames |
570
|
|
|
|
|
|
|
that have been renamed, @deleted is array of new filenames that have been |
571
|
|
|
|
|
|
|
deleted. |
572
|
|
|
|
|
|
|
|
573
|
|
|
|
|
|
|
=head2 hook_after_create => code |
574
|
|
|
|
|
|
|
|
575
|
|
|
|
|
|
|
Will be called by after a new file is created. Code will be passed ($self). |
576
|
|
|
|
|
|
|
|
577
|
|
|
|
|
|
|
This hook can be used to write a header to each file, e.g.: |
578
|
|
|
|
|
|
|
|
579
|
|
|
|
|
|
|
# hook_after_create |
580
|
|
|
|
|
|
|
my ($self) = @_; |
581
|
|
|
|
|
|
|
my $fh $self->handle; |
582
|
|
|
|
|
|
|
print $fh "header\n"; |
583
|
|
|
|
|
|
|
|
584
|
|
|
|
|
|
|
Since this is called indirectly by write(), locking is also already done. |
585
|
|
|
|
|
|
|
|
586
|
|
|
|
|
|
|
=head2 binmode => str |
587
|
|
|
|
|
|
|
|
588
|
|
|
|
|
|
|
If set to "1", will cause the file handle to be set: |
589
|
|
|
|
|
|
|
|
590
|
|
|
|
|
|
|
binmode $fh; |
591
|
|
|
|
|
|
|
|
592
|
|
|
|
|
|
|
which might be necessary on some OS, e.g. Windows when writing binary data. |
593
|
|
|
|
|
|
|
Otherwise, other defined values will cause the file handle to be set: |
594
|
|
|
|
|
|
|
|
595
|
|
|
|
|
|
|
binmode $fh, $value |
596
|
|
|
|
|
|
|
|
597
|
|
|
|
|
|
|
which can be used to set PerlIO layer(s). |
598
|
|
|
|
|
|
|
|
599
|
|
|
|
|
|
|
=head1 METHODS |
600
|
|
|
|
|
|
|
|
601
|
|
|
|
|
|
|
=head2 $obj = File::Write::Rotate->new(%args) |
602
|
|
|
|
|
|
|
|
603
|
|
|
|
|
|
|
Create new object. Known arguments: |
604
|
|
|
|
|
|
|
|
605
|
|
|
|
|
|
|
=over |
606
|
|
|
|
|
|
|
|
607
|
|
|
|
|
|
|
=item * dir => STR (required) |
608
|
|
|
|
|
|
|
|
609
|
|
|
|
|
|
|
Directory to put the files in. |
610
|
|
|
|
|
|
|
|
611
|
|
|
|
|
|
|
=item * prefix => STR (required) |
612
|
|
|
|
|
|
|
|
613
|
|
|
|
|
|
|
Name of files. The files will be named like the following: |
614
|
|
|
|
|
|
|
|
615
|
|
|
|
|
|
|
<prefix><period><suffix><rotate_suffix> |
616
|
|
|
|
|
|
|
|
617
|
|
|
|
|
|
|
C<< <period> >> will only be given if the C<period> argument is set. If |
618
|
|
|
|
|
|
|
C<period> is set to C<yearly>, C<< <period> >> will be C<YYYY> (4-digit year). |
619
|
|
|
|
|
|
|
If C<period> is C<monthly>, C<< <period> >> will be C<YYYY-MM> (4-digit year and |
620
|
|
|
|
|
|
|
2-digit month). If C<period> is C<daily>, C<< <period> >> will be C<YYYY-MM-DD> |
621
|
|
|
|
|
|
|
(4-digit year, 2-digit month, and 2-digit day). |
622
|
|
|
|
|
|
|
|
623
|
|
|
|
|
|
|
C<< <rotate_suffix> >> is either empty string for current file; or C<.1>, C<.2> |
624
|
|
|
|
|
|
|
and so on for rotated files. C<.1> is the most recent rotated file, C<.2> is the |
625
|
|
|
|
|
|
|
next most recent, and so on. |
626
|
|
|
|
|
|
|
|
627
|
|
|
|
|
|
|
An example, with C<prefix> set to C<myapp>: |
628
|
|
|
|
|
|
|
|
629
|
|
|
|
|
|
|
myapp # current file |
630
|
|
|
|
|
|
|
myapp.1 # most recently rotated |
631
|
|
|
|
|
|
|
myapp.2 # the next most recently rotated |
632
|
|
|
|
|
|
|
|
633
|
|
|
|
|
|
|
With C<prefix> set to C<myapp>, C<period> set to C<monthly>, C<suffix> set to |
634
|
|
|
|
|
|
|
C<.log>: |
635
|
|
|
|
|
|
|
|
636
|
|
|
|
|
|
|
myapp.2012-12.log # file name for december 2012 |
637
|
|
|
|
|
|
|
myapp.2013-01.log # file name for january 2013 |
638
|
|
|
|
|
|
|
|
639
|
|
|
|
|
|
|
Like previous, but additionally with C<size> also set (which will also rotate |
640
|
|
|
|
|
|
|
each period file if it exceeds specified size): |
641
|
|
|
|
|
|
|
|
642
|
|
|
|
|
|
|
myapp.2012-12.log # file(s) for december 2012 |
643
|
|
|
|
|
|
|
myapp.2012-12.log.1 |
644
|
|
|
|
|
|
|
myapp.2012-12.log.2 |
645
|
|
|
|
|
|
|
myapp.2013-01.log # file(s) for january 2013 |
646
|
|
|
|
|
|
|
|
647
|
|
|
|
|
|
|
All times will use local time, so you probably want to set C<TZ> environment |
648
|
|
|
|
|
|
|
variable or equivalent methods to set time zone. |
649
|
|
|
|
|
|
|
|
650
|
|
|
|
|
|
|
=item * suffix => STR (default: '') |
651
|
|
|
|
|
|
|
|
652
|
|
|
|
|
|
|
Suffix to give to file names, usually file extension like C<.log>. See C<prefix> |
653
|
|
|
|
|
|
|
for more details. |
654
|
|
|
|
|
|
|
|
655
|
|
|
|
|
|
|
If you use a yearly period, setting suffix is advised to avoid ambiguity with |
656
|
|
|
|
|
|
|
rotate suffix (for example, is C<myapp.2012> the current file for year 2012 or |
657
|
|
|
|
|
|
|
file with C<2012> rotate suffix?) |
658
|
|
|
|
|
|
|
|
659
|
|
|
|
|
|
|
=item * size => INT (default: 10*1024*1024) |
660
|
|
|
|
|
|
|
|
661
|
|
|
|
|
|
|
Maximum file size, in bytes, before rotation is triggered. The default is 10MB |
662
|
|
|
|
|
|
|
(10*1024*1024) I<if> C<period> is not set. If C<period> is set, no default for |
663
|
|
|
|
|
|
|
C<size> is provided, which means files will not be rotated for size (only for |
664
|
|
|
|
|
|
|
period). |
665
|
|
|
|
|
|
|
|
666
|
|
|
|
|
|
|
=item * period => STR |
667
|
|
|
|
|
|
|
|
668
|
|
|
|
|
|
|
Can be set to either C<daily>, C<monthly>, or C<yearly>. If set, will |
669
|
|
|
|
|
|
|
automatically rotate after period change. See C<prefix> for more details. |
670
|
|
|
|
|
|
|
|
671
|
|
|
|
|
|
|
=item * histories => INT (default: 10) |
672
|
|
|
|
|
|
|
|
673
|
|
|
|
|
|
|
Number of rotated files to keep. After the number of files exceeds this, the |
674
|
|
|
|
|
|
|
oldest one will be deleted. 0 means not to keep any history, 1 means to only |
675
|
|
|
|
|
|
|
keep C<.1> file, and so on. |
676
|
|
|
|
|
|
|
|
677
|
|
|
|
|
|
|
=item * buffer_size => INT (default: 0) |
678
|
|
|
|
|
|
|
|
679
|
|
|
|
|
|
|
Set initial value of buffer. See the C<buffer_size> attribute for more |
680
|
|
|
|
|
|
|
information. |
681
|
|
|
|
|
|
|
|
682
|
|
|
|
|
|
|
=item * lock_mode => STR (default: 'write') |
683
|
|
|
|
|
|
|
|
684
|
|
|
|
|
|
|
Can be set to either C<none>, C<write>, or C<exclusive>. C<none> disables |
685
|
|
|
|
|
|
|
locking and increases write performance, but should only be used when there is |
686
|
|
|
|
|
|
|
only one writer. C<write> acquires and holds the lock for each write. |
687
|
|
|
|
|
|
|
C<exclusive> acquires the lock at object creation and holds it until the the |
688
|
|
|
|
|
|
|
object is destroyed. |
689
|
|
|
|
|
|
|
|
690
|
|
|
|
|
|
|
Lock file is named C<< <prefix> >>C<.lck>. Will wait for up to 1 minute to |
691
|
|
|
|
|
|
|
acquire lock, will die if failed to acquire lock. |
692
|
|
|
|
|
|
|
|
693
|
|
|
|
|
|
|
=item * hook_before_write => CODE |
694
|
|
|
|
|
|
|
|
695
|
|
|
|
|
|
|
=item * hook_before_rotate => CODE |
696
|
|
|
|
|
|
|
|
697
|
|
|
|
|
|
|
=item * hook_after_rotate => CODE |
698
|
|
|
|
|
|
|
|
699
|
|
|
|
|
|
|
=item * hook_after_create => CODE |
700
|
|
|
|
|
|
|
|
701
|
|
|
|
|
|
|
See L</ATTRIBUTES>. |
702
|
|
|
|
|
|
|
|
703
|
|
|
|
|
|
|
=item * buffer_size => int |
704
|
|
|
|
|
|
|
|
705
|
|
|
|
|
|
|
=item * rotate_probability => float (between 0 < x < 1) |
706
|
|
|
|
|
|
|
|
707
|
|
|
|
|
|
|
If set, instruct to only check for rotation under a certain probability, for |
708
|
|
|
|
|
|
|
example if value is set to 0.1 then will only check for rotation 10% of the |
709
|
|
|
|
|
|
|
time. |
710
|
|
|
|
|
|
|
|
711
|
|
|
|
|
|
|
=back |
712
|
|
|
|
|
|
|
|
713
|
|
|
|
|
|
|
=head2 lock_file_path => STR |
714
|
|
|
|
|
|
|
|
715
|
|
|
|
|
|
|
Returns a string representing the complete pathname to the lock file, based |
716
|
|
|
|
|
|
|
on C<dir> and C<prefix> attributes. |
717
|
|
|
|
|
|
|
|
718
|
|
|
|
|
|
|
=head2 $fwr->write(@args) |
719
|
|
|
|
|
|
|
|
720
|
|
|
|
|
|
|
Write to file. Will automatically rotate file if period changes or file size |
721
|
|
|
|
|
|
|
exceeds specified limit. When rotating, will only keep a specified number of |
722
|
|
|
|
|
|
|
histories and delete the older ones. |
723
|
|
|
|
|
|
|
|
724
|
|
|
|
|
|
|
Does not append newline so you'll have to do it yourself. |
725
|
|
|
|
|
|
|
|
726
|
|
|
|
|
|
|
=head2 $fwr->compress |
727
|
|
|
|
|
|
|
|
728
|
|
|
|
|
|
|
Compress old rotated files and remove the uncompressed originals. Currently uses |
729
|
|
|
|
|
|
|
L<IO::Compress::Gzip> to do the compression. Extension given to compressed file |
730
|
|
|
|
|
|
|
is C<.gz>. |
731
|
|
|
|
|
|
|
|
732
|
|
|
|
|
|
|
Will not lock writers, but will create C<< <prefix> >>C<-compress.pid> PID file |
733
|
|
|
|
|
|
|
to prevent multiple compression processes running and to signal the writers to |
734
|
|
|
|
|
|
|
postpone rotation. |
735
|
|
|
|
|
|
|
|
736
|
|
|
|
|
|
|
After compression is finished, will remove the PID file, so rotation can be done |
737
|
|
|
|
|
|
|
again on the next C<write()> if necessary. |
738
|
|
|
|
|
|
|
|
739
|
|
|
|
|
|
|
=head1 FAQ |
740
|
|
|
|
|
|
|
|
741
|
|
|
|
|
|
|
=head2 Why use autorotating file? |
742
|
|
|
|
|
|
|
|
743
|
|
|
|
|
|
|
Mainly convenience and low maintenance. You no longer need a separate rotator |
744
|
|
|
|
|
|
|
process like the Unix B<logrotate> utility (which when accidentally disabled or |
745
|
|
|
|
|
|
|
misconfigured will cause your logs to stop being rotated and grow indefinitely). |
746
|
|
|
|
|
|
|
|
747
|
|
|
|
|
|
|
=head2 What is the downside of using FWR (and LDFR)? |
748
|
|
|
|
|
|
|
|
749
|
|
|
|
|
|
|
Mainly (significant) performance overhead. At (almost) every C<write()>, FWR |
750
|
|
|
|
|
|
|
needs to check file sizes and/or dates for rotation. Under default configuration |
751
|
|
|
|
|
|
|
(where C<lock_mode> is C<write>), it also performs locking on each C<write()> to |
752
|
|
|
|
|
|
|
make it safe to use with multiple processes. Below is a casual benchmark to give |
753
|
|
|
|
|
|
|
a sense of the overhead, tested on my Core i5-2400 3.1GHz desktop: |
754
|
|
|
|
|
|
|
|
755
|
|
|
|
|
|
|
Writing lines in the size of ~ 200 bytes, raw writing to disk (SSD) has the |
756
|
|
|
|
|
|
|
speed of around 3.4mil/s, while using FWR it goes down to around ~13k/s. Using |
757
|
|
|
|
|
|
|
C<lock_mode> C<none> or C<exclusive>, the speed is ~52k/s. |
758
|
|
|
|
|
|
|
|
759
|
|
|
|
|
|
|
However, this is not something you'll notice or need to worry about unless |
760
|
|
|
|
|
|
|
you're writing near that speed. |
761
|
|
|
|
|
|
|
|
762
|
|
|
|
|
|
|
If you need more speed, you can try setting C<rotate_probability> which will |
763
|
|
|
|
|
|
|
cause FWR to only check for rotation probabilistically, e.g. if you set this to |
764
|
|
|
|
|
|
|
0.1 then checks will only be done in about 1 of 10 writes. This can |
765
|
|
|
|
|
|
|
significantly reduce the overhead and increase write speed several times (e.g. |
766
|
|
|
|
|
|
|
5-8 times), but understand that this will make the writes "overflow" a bit, e.g. |
767
|
|
|
|
|
|
|
file sizes will exceed for a bit if you do size-based rotation. More suitable if |
768
|
|
|
|
|
|
|
you only do size-based rotation since it is usually okay to exceed sizes for a |
769
|
|
|
|
|
|
|
bit. |
770
|
|
|
|
|
|
|
|
771
|
|
|
|
|
|
|
=head2 I want a filehandle instead of a File::Write::Rotate object! |
772
|
|
|
|
|
|
|
|
773
|
|
|
|
|
|
|
Use L<Tie::Handle::FileWriteRotate>. |
774
|
|
|
|
|
|
|
|
775
|
|
|
|
|
|
|
=head1 HOMEPAGE |
776
|
|
|
|
|
|
|
|
777
|
|
|
|
|
|
|
Please visit the project's homepage at L<https://metacpan.org/release/File-Write-Rotate>. |
778
|
|
|
|
|
|
|
|
779
|
|
|
|
|
|
|
=head1 SOURCE |
780
|
|
|
|
|
|
|
|
781
|
|
|
|
|
|
|
Source repository is at L<https://github.com/perlancar/perl-File-Write-Rotate>. |
782
|
|
|
|
|
|
|
|
783
|
|
|
|
|
|
|
=head1 BUGS |
784
|
|
|
|
|
|
|
|
785
|
|
|
|
|
|
|
Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=File-Write-Rotate> |
786
|
|
|
|
|
|
|
|
787
|
|
|
|
|
|
|
When submitting a bug or request, please include a test-file or a |
788
|
|
|
|
|
|
|
patch to an existing test-file that illustrates the bug or desired |
789
|
|
|
|
|
|
|
feature. |
790
|
|
|
|
|
|
|
|
791
|
|
|
|
|
|
|
=head1 SEE ALSO |
792
|
|
|
|
|
|
|
|
793
|
|
|
|
|
|
|
L<Log::Dispatch::FileRotate>, which inspires this module. Differences between |
794
|
|
|
|
|
|
|
File::Write::Rotate (FWR) and Log::Dispatch::FileRotate (LDFR) are as follows: |
795
|
|
|
|
|
|
|
|
796
|
|
|
|
|
|
|
=over |
797
|
|
|
|
|
|
|
|
798
|
|
|
|
|
|
|
=item * FWR is not part of the L<Log::Dispatch> family. |
799
|
|
|
|
|
|
|
|
800
|
|
|
|
|
|
|
This makes FWR more general to use. |
801
|
|
|
|
|
|
|
|
802
|
|
|
|
|
|
|
For using together with Log::Dispatch/Log4perl, I have also written |
803
|
|
|
|
|
|
|
L<Log::Dispatch::FileWriteRotate> which is a direct (although not a perfect |
804
|
|
|
|
|
|
|
drop-in) replacement for Log::Dispatch::FileRotate. |
805
|
|
|
|
|
|
|
|
806
|
|
|
|
|
|
|
=item * Secondly, FWR does not use L<Date::Manip>. |
807
|
|
|
|
|
|
|
|
808
|
|
|
|
|
|
|
Date::Manip is relatively large (loading Date::Manip 6.37 equals to loading 34 |
809
|
|
|
|
|
|
|
files and ~ 22k lines; while FWR itself is only < 1k lines!) |
810
|
|
|
|
|
|
|
|
811
|
|
|
|
|
|
|
As a consequence of this, FWR does not support DatePattern; instead, FWR |
812
|
|
|
|
|
|
|
replaces it with a simple daily/monthly/yearly period. |
813
|
|
|
|
|
|
|
|
814
|
|
|
|
|
|
|
=item * And lastly, FWR supports compressing and rotating compressed old files. |
815
|
|
|
|
|
|
|
|
816
|
|
|
|
|
|
|
Using separate processes like the Unix B<logrotate> utility means having to deal |
817
|
|
|
|
|
|
|
with yet another race condition. FWR takes care of that for you (see the |
818
|
|
|
|
|
|
|
compress() method). You also have the option to do file compression in the same |
819
|
|
|
|
|
|
|
script/process if you want, which is convenient. |
820
|
|
|
|
|
|
|
|
821
|
|
|
|
|
|
|
=back |
822
|
|
|
|
|
|
|
|
823
|
|
|
|
|
|
|
There is no significant overhead difference between FWR and LDFR (FWR is |
824
|
|
|
|
|
|
|
slightly faster than LDFR on my testing). |
825
|
|
|
|
|
|
|
|
826
|
|
|
|
|
|
|
L<Tie::Handle::FileWriteRotate> and Log::Dispatch::FileWriteRotate, which use |
827
|
|
|
|
|
|
|
this module. |
828
|
|
|
|
|
|
|
|
829
|
|
|
|
|
|
|
=head1 AUTHOR |
830
|
|
|
|
|
|
|
|
831
|
|
|
|
|
|
|
perlancar <perlancar@cpan.org> |
832
|
|
|
|
|
|
|
|
833
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE |
834
|
|
|
|
|
|
|
|
835
|
|
|
|
|
|
|
This software is copyright (c) 2019, 2016, 2015, 2014, 2013, 2012 by perlancar@cpan.org. |
836
|
|
|
|
|
|
|
|
837
|
|
|
|
|
|
|
This is free software; you can redistribute it and/or modify it under |
838
|
|
|
|
|
|
|
the same terms as the Perl 5 programming language system itself. |
839
|
|
|
|
|
|
|
|
840
|
|
|
|
|
|
|
=cut |