File Coverage

blib/lib/File/RotateLogs.pm
Criterion Covered Total %
statement 80 86 93.0
branch 22 34 64.7
condition 3 6 50.0
subroutine 14 14 100.0
pod 0 4 0.0
total 119 144 82.6


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