File-Write-Rotate
view release on metacpan or search on metacpan
lib/File/Write/Rotate.pm view on Meta::CPAN
## no critic: InputOutput::ProhibitOneArgSelect
package File::Write::Rotate;
our $DATE = '2019-06-27'; # DATE
our $VERSION = '0.321'; # VERSION
use 5.010001;
use strict;
use warnings;
# we must not use Log::Any, looping if we are used as log output
#use Log::Any '$log';
use File::Spec;
use IO::Compress::Gzip qw(gzip $GzipError);
use Scalar::Util qw(weaken);
#use Taint::Runtime qw(untaint is_tainted);
use Time::HiRes 'time';
our $Debug;
sub new {
my $class = shift;
my %args0 = @_;
my %args;
defined($args{dir} = delete $args0{dir})
or die "Please specify dir";
defined($args{prefix} = delete $args0{prefix})
or die "Please specify prefix";
$args{suffix} = delete($args0{suffix}) // "";
$args{size} = delete($args0{size}) // 0;
$args{period} = delete($args0{period});
if ($args{period}) {
$args{period} =~ /\A(daily|day|month|monthly|year|yearly)\z/
or die "Invalid period, please use daily/monthly/yearly";
}
for (map {"hook_$_"} qw(before_rotate after_rotate after_create
before_write a)) {
next unless $args0{$_};
$args{$_} = delete($args0{$_});
die "Invalid $_, please supply a coderef"
unless ref($args{$_}) eq 'CODE';
}
if (!$args{period} && !$args{size}) {
$args{size} = 10 * 1024 * 1024;
}
$args{histories} = delete($args0{histories}) // 10;
$args{binmode} = delete($args0{binmode});
$args{buffer_size} = delete($args0{buffer_size});
$args{lock_mode} = delete($args0{lock_mode}) // 'write';
$args{lock_mode} =~ /\A(none|write|exclusive)\z/
or die "Invalid lock_mode, please use none/write/exclusive";
$args{rotate_probability} = delete($args0{rotate_probability});
if (defined $args{rotate_probability}) {
$args{rotate_probability} > 0 && $args{rotate_probability} < 1.0
or die "Invalid rotate_probability, must be 0 < x < 1";
}
if (keys %args0) {
die "Unknown arguments to new(): ".join(", ", sort keys %args0);
}
$args{_buffer} = [];
my $self = bless \%args, $class;
lib/File/Write/Rotate.pm view on Meta::CPAN
my $self = shift;
$self->{_fh};
}
sub path {
my $self = shift;
$self->{_fp};
}
# file path, without the rotate suffix
sub _file_path {
my ($self) = @_;
# _now is calculated every time we access this method
$self->{_now} = time();
my @lt = localtime($self->{_now});
$lt[5] += 1900;
$lt[4]++;
my $period;
if ($self->{period}) {
if ($self->{period} =~ /year/i) {
$period = sprintf("%04d", $lt[5]);
} elsif ($self->{period} =~ /month/) {
$period = sprintf("%04d-%02d", $lt[5], $lt[4]);
} elsif ($self->{period} =~ /day|daily/) {
$period = sprintf("%04d-%02d-%02d", $lt[5], $lt[4], $lt[3]);
}
} else {
$period = "";
}
my $path = join(
'',
$self->{dir}, '/',
$self->{prefix},
length($period) ? ".$period" : "",
$self->{suffix},
);
if (wantarray) {
return ($path, $period);
} else {
return $path;
}
}
sub lock_file_path {
my ($self) = @_;
return File::Spec->catfile($self->{dir}, $self->{prefix} . '.lck');
}
sub _get_lock {
my ($self) = @_;
return undef if $self->{lock_mode} eq 'none';
return $self->{_weak_lock} if defined($self->{_weak_lock});
require File::Flock::Retry;
my $lock = File::Flock::Retry->lock($self->lock_file_path);
$self->{_weak_lock} = $lock;
weaken $self->{_weak_lock};
return $lock;
}
# will return \@files. each entry is [filename without compress suffix,
# rotate_suffix (for sorting), period (for sorting), compress suffix (for
# renaming back)]
sub _get_files {
my ($self) = @_;
opendir my ($dh), $self->{dir} or do {
warn "Can't opendir '$self->{dir}': $!";
return;
};
my @files;
while (my $e = readdir($dh)) {
my $cs; $cs = $1 if $e =~ s/(\.gz)\z//; # compress suffix
next unless $e =~ /\A\Q$self->{prefix}\E
(?:\. (?<period>\d{4}(?:-\d\d(?:-\d\d)?)?) )?
\Q$self->{suffix}\E
(?:\. (?<rotate_suffix>\d+) )?
\z
/x;
push @files,
[ $e, $+{rotate_suffix} // 0, $+{period} // "", $cs // "" ];
}
closedir($dh);
[ sort { $a->[2] cmp $b->[2] || $b->[1] <=> $a->[1] } @files ];
}
# rename (increase rotation suffix) and keep only n histories. note: failure in
# rotating should not be fatal, we just warn and return.
sub _rotate_and_delete {
my ($self, %opts) = @_;
my $delete_only = $opts{delete_only};
my $lock = $self->_get_lock;
CASE:
{
my $files = $self->_get_files or last CASE;
# is there a compression process in progress? this is marked by the
# existence of <prefix>-compress.pid PID file.
#
# XXX check validity of PID file, otherwise a stale PID file will always
# prevent rotation to be done
if (-f "$self->{dir}/$self->{prefix}-compress.pid") {
warn "Compression is in progress, rotation is postponed";
last CASE;
}
$self->{hook_before_rotate}->($self, [map {$_->[0]} @$files])
if $self->{hook_before_rotate};
my @deleted;
my @renamed;
my $i;
my $dir = $self->{dir};
( run in 1.237 second using v1.01-cache-2.11-cpan-39bf76dae61 )