File Coverage

blib/lib/File/RotateLogs.pm
Criterion Covered Total %
statement 87 93 93.5
branch 24 36 66.6
condition 3 6 50.0
subroutine 16 16 100.0
pod 0 4 0.0
total 130 155 83.8


line stmt bran cond sub pod time code
1             package File::RotateLogs;
2              
3 4     4   51509 use strict;
  4         8  
  4         90  
4 4     4   11 use warnings;
  4         7  
  4         84  
5 4     4   1496 use POSIX qw//;
  4         17689  
  4         97  
6 4     4   21 use Fcntl qw/:DEFAULT/;
  4         4  
  4         1236  
7 4     4   2146 use Proc::Daemon;
  4         7524  
  4         97  
8 4     4   20 use File::Basename;
  4         5  
  4         269  
9 4     4   18 use File::Path;
  4         25  
  4         170  
10 4     4   15 use File::Spec;
  4         4  
  4         58  
11 4     4   1765 use Mouse;
  4         80054  
  4         16  
12 4     4   957 use Mouse::Util::TypeConstraints;
  4         6  
  4         11  
13              
14             our $VERSION = '0.08';
15              
16             subtype 'File::RotateLogs::Path'
17             => as 'Str'
18             => message { "This argument must be Str or Object that has a stringify method" };
19             coerce 'File::RotateLogs::Path'
20             => from 'Object' => via {
21             my $logfile = $_;
22             if ( my $stringify = overload::Method( $logfile, '""' ) ) {
23             return $stringify->($logfile);
24             }
25             $logfile;
26             };
27              
28 4     4   526 no Mouse::Util::TypeConstraints;
  4         3  
  4         14  
29              
30             has 'logfile' => (
31             is => 'ro',
32             isa => 'File::RotateLogs::Path',
33             required => 1,
34             coerce => 1,
35             );
36              
37             has 'linkname' => (
38             is => 'ro',
39             isa => 'File::RotateLogs::Path',
40             required => 0,
41             coerce => 1,
42             );
43              
44             has 'rotationtime' => (
45             is => 'ro',
46             isa => 'Int',
47             default => 86400
48             );
49              
50             has 'maxage' => (
51             is => 'ro',
52             isa => 'Int',
53             default => sub {
54             warn "[INFO] File::RotateLogs: 'maxage' was not configured. RotateLogs doesn't remove any log files\n";
55             return 0;
56             },
57             );
58              
59             has 'sleep_before_remove' => (
60             is => 'ro',
61             isa => 'Int',
62             default => 3,
63             );
64              
65             has 'offset' => (
66             is => 'ro',
67             isa => 'Int',
68             default => 0,
69             );
70              
71              
72             sub _gen_filename {
73 51     51   70 my $self = shift;
74 51         118 my $now = time;
75 51         738 my $time = $now - (($now + $self->offset) % $self->rotationtime);
76 51         428 return POSIX::strftime($self->logfile, localtime($time));
77             }
78              
79             sub print {
80 51     51 0 12408010 my ($self,$log) = @_;
81 51         134 my $fname = $self->_gen_filename;
82              
83 51         910 my $fh;
84 51 100       210 if ( $self->{fh} ) {
85 34 100 66     197 if ( $fname eq $self->{fname} && $self->{pid} == $$ ) {
86 16         35 $fh = delete $self->{fh};
87             }
88             else {
89 18         50 $fh = delete $self->{fh};
90 18 50       219 close $fh if $fh;
91 18         72 undef $fh;
92             }
93             }
94              
95 51 100       111 unless ($fh) {
96 35 50 33     770 my $is_new = ( ! -f $fname || ( $self->linkname && ! -l $self->linkname ) ) ? 1 : 0;
97 35 100       2613 File::Path::mkpath( File::Basename::dirname($fname) ) if ! -e File::Basename::dirname($fname);
98 35 50       2652 open $fh, '>>:utf8:unix', $fname or die "Cannot open file($fname): $!";
99 35 50       95 if ( $is_new ) {
100 35         65 eval {
101 35         167 $self->rotation($fname);
102             };
103 35 50       2221 warn "failed rotation or symlink: $@" if $@;
104             }
105             }
106              
107 51 50       236 $fh->print($log)
108             or die "Cannot write to $fname: $!";
109              
110 51         1789 $self->{fh} = $fh;
111 51         176 $self->{fname} = $fname;
112 51         174 $self->{pid} = $$;
113             }
114              
115             sub rotation {
116 35     35 0 80 my ($self, $fname) = @_;
117              
118 35         78 my $lock = $fname .'_lock';
119 35 50       2074 sysopen(my $lockfh, $lock, O_CREAT|O_EXCL) or return;
120 35         259 close($lockfh);
121 35 50       163 if ( $self->linkname ) {
122 35         88 my $symlink = $fname .'_symlink';
123 35 50       2100 symlink($fname, $symlink) or die $!;
124 35 50       1981 rename($symlink, $self->linkname) or die $!;
125             }
126              
127 35 100       128 if ( ! $self->maxage ) {
128 32         1488 unlink $lock;
129 32         158 return;
130             }
131              
132 3         6 my $time = time;
133 3         11 my @to_unlink = grep { $time - [stat($_)]->[9] > $self->maxage }
  9         94  
134             glob($self->logfile_pattern);
135 3 100       8 if ( ! @to_unlink ) {
136 2         91 unlink $lock;
137 2         11 return;
138             }
139              
140 1 50       6 if ( $self->sleep_before_remove ) {
141 1         5 $self->unlink_background(@to_unlink,$lock);
142             }
143             else {
144 0         0 unlink $_ for @to_unlink;
145 0         0 unlink $lock;
146             }
147             }
148              
149             sub logfile_pattern {
150 3     3 0 9 my $self = shift;
151 3         8 my $logfile = $self->logfile;
152 3         25 $logfile =~ s!%[%+A-Za-z]!*!g;
153 3         12 $logfile =~ s!\*+!*!g;
154 3         277 $logfile;
155             }
156              
157             sub unlink_background {
158 1     1 0 3 my ($self, @files) = @_;
159 1         9 my $daemon = Proc::Daemon->new();
160 1         16 @files = map { File::Spec->rel2abs($_) } @files;
  2         30  
161 1 50       4 if ( ! $daemon->Init ) {
162 0           $0 = "$0 rotatelogs unlink worker";
163 0           sleep $self->sleep_before_remove;
164 0           unlink $_ for @files;
165 0           POSIX::_exit(0);
166             }
167             }
168              
169             __PACKAGE__->meta->make_immutable();
170              
171             1;
172             __END__