File Coverage

blib/lib/App/RunCron.pm
Criterion Covered Total %
statement 130 149 87.2
branch 32 54 59.2
condition 30 48 62.5
subroutine 24 24 100.0
pod 6 7 85.7
total 222 282 78.7


line stmt bran cond sub pod time code
1             package App::RunCron;
2 6     6   71589 use 5.008001;
  6         20  
  6         239  
3 6     6   34 use strict;
  6         10  
  6         203  
4 6     6   29 use warnings;
  6         17  
  6         285  
5              
6             our $VERSION = "0.07";
7              
8 6     6   37 use Fcntl qw(SEEK_SET);
  6         13  
  6         480  
9 6     6   5606 use File::Temp qw(tempfile);
  6         112820  
  6         630  
10 6     6   3614 use Time::HiRes qw/gettimeofday/;
  6         8111  
  6         83  
11 6     6   6422 use Sys::Hostname;
  6         7556  
  6         438  
12              
13             use Class::Accessor::Lite (
14 6         61 new => 1,
15             ro => [qw/timestamp command reporter error_reporter common_reporter tag print/],
16             rw => [qw/logfile logpos exit_code _finished/],
17 6     6   6497 );
  6         7278  
18              
19             sub _logfh {
20 32     32   76 my $self = shift;
21              
22 32   66     228 $self->{_logfh} ||= do {
23 8         24 my $logfh;
24 8         13 my $logfile = $self->{logfile};
25 8 100       22 if ($logfile) {
26 2 50       134 open $logfh, '>>', $logfile or die "failed to open file:$logfile:$!";
27             } else {
28 6         21 ($logfh, $logfile) = tempfile(UNLINK => 1);
29 6         2751 $self->logfile($logfile);
30             }
31 8         123 autoflush $logfh 1;
32 8         1064 print $logfh '-'x78, "\n";
33 8         40 $self->logpos(tell $logfh);
34 8 50       61 die "failed to obtain position of logfile:$!" if $self->logpos == -1;
35 8 50       65 seek $logfh, $self->logpos, SEEK_SET or die "cannot seek within logfile:$!";
36 8         113 $logfh;
37             };
38             }
39              
40             sub run {
41 1     1 1 2 my $self = shift;
42 1 50       6 if (!$self->_finished) {
43 1         10 $self->_run;
44 1         3 exit $self->child_exit_code;
45             }
46             else {
47 0         0 warn "already run. can't rerun.\n";
48             }
49             }
50              
51             sub command_str {
52 8     8 0 1476 my $self = shift;
53 8   33     66 $self->{command_str} ||= join ' ', @{ $self->command };
  8         29  
54             }
55              
56             sub _run {
57 8     8   62687 my $self = shift;
58 8 50       15 die "no command specified" unless @{ $self->command };
  8         34  
59              
60 8         93 my $logfh = $self->_logfh;
61 8 50       248 pipe my $logrh, my $logwh or die "failed to create pipe:$!";
62              
63             # exec
64 8   50     73 $self->_log(sprintf("%s tag:[%s] starting: %s\n", hostname, $self->tag || '', $self->command_str));
65 8         158 $self->exit_code(-1);
66 8 50       9560 unless (my $pid = fork) {
67 0 0       0 if (defined $pid) {
68             # child process
69 0         0 close $logrh;
70 0         0 close $logfh;
71 0 0       0 open STDERR, '>&', $logwh or die "failed to redirect STDERR to logfile";
72 0 0       0 open STDOUT, '>&', $logwh or die "failed to redirect STDOUT to logfile";
73 0         0 close $logwh;
74 0         0 exec @{ $self->command };
  0         0  
75 0         0 die "exec(2) failed:$!:@{ $self->command }";
  0         0  
76             }
77             else {
78 0         0 close $logrh;
79 0         0 close $logwh;
80 0 0       0 print $logfh, "fork(2) failed:$!\n" unless defined $pid;
81             }
82             }
83             else {
84 8         385 close $logwh;
85 8 50       556 if ($self->print) {
86 0         0 require PerlIO::Util;
87 0         0 $self->_logfh->push_layer(tee => *STDOUT);
88             }
89 8         57788 $self->_log($_) while <$logrh>;
90 8         1439 close $logrh;
91 8 50       140 $self->_logfh->pop_layer if $self->print;
92 8         329 while (wait == -1) {}
93 8         91 $self->exit_code($?);
94             }
95              
96             # end
97 8         369 $self->_finished(1);
98 8         187 $self->_log($self->result_line. "\n");
99              
100 8 100       214 if ($self->is_success) {
101 7         125 $self->_send_report;
102             }
103             else {
104 1         34 $self->_send_error_report;
105             }
106             }
107              
108             sub child_exit_code {
109 9     9 1 15 my $self = shift;
110 9         33 my $exit_code = $self->exit_code;
111 9 100 66     246 return $exit_code if !$exit_code || $exit_code < 0;
112              
113 1         24 $self->exit_code >> 8;
114             }
115              
116             sub child_signal {
117 8     8 1 18 my $self = shift;
118 8         36 my $exit_code = $self->exit_code;
119 8 100 66     150 return $exit_code if !$exit_code || $exit_code < 0;
120              
121 1         59 $self->exit_code & 127;
122             }
123              
124 10     10 1 36 sub is_success { shift->exit_code == 0 }
125              
126             sub result_line {
127 8     8 1 20 my $self = shift;
128 8   33     161 $self->{result_line} ||= do {
129 8         30 my $exit_code = $self->exit_code;
130 8 50       118 if ($exit_code == -1) {
    50          
131 0         0 "failed to execute command:$!";
132             }
133             elsif ($self->child_signal) {
134 0         0 "command died with signal:" . $self->child_signal;
135             }
136             else {
137 8         44 "command exited with code:" . $self->child_exit_code;
138             }
139             };
140             }
141              
142             sub report {
143 7     7 1 16 my $self = shift;
144              
145 7   66     84 $self->{report} ||= do {
146 5 50       103 open my $fh, '<', $self->logfile or die "failed to open @{[$self->logfile]}:$!";
  0         0  
147 5 50       430 seek $fh, $self->logpos, SEEK_SET or die "failed to seek to the appropriate position in logfile:$!";
148 5         90 my $report = '';
149 5         170 $report .= $_ while <$fh>;
150 5         553 $report;
151             }
152             }
153              
154             sub _send_report {
155 7     7   21 my $self = shift;
156              
157 7   100     30 my $reporter = $self->reporter || 'None';
158 7   66     102 $self->_do_send_report($reporter, $self->common_reporter || ());
159             }
160              
161             sub _send_error_report {
162 1     1   6 my $self = shift;
163              
164 1   50     15 my $reporter = $self->error_reporter || 'Stdout';
165 1   33     36 $self->_do_send_report($reporter, $self->common_reporter || ());
166             }
167              
168             sub _do_send_report {
169 8     8   172 my ($self, @reporters) = @_;
170              
171 8         38 eval {
172             # XXX error handling
173 8         44 for my $reporter (@reporters) {
174 9 100 100     91 if (ref($reporter) && ref($reporter) eq 'CODE') {
175 1         31 $reporter->($self);
176             }
177             else {
178 8         199 my @reporters = _retrieve_reporters($reporter);
179              
180 8         20 for my $r (@reporters) {
181 9         21 my ($class, $arg) = @$r;
182 9   66     32 _load_reporter($class)->new($arg || ())->run($self);
183             }
184             }
185             }
186             };
187 8 100       2656 if (my $err = $@) {
188 1         16 warn $self->report;
189 1         41 warn $err;
190             }
191             }
192              
193             sub _retrieve_reporters {
194 11     11   62 my $reporter = shift;
195 11         1409 my @reporters;
196 11 100 66     80 if (ref $reporter && ref($reporter) eq 'ARRAY') {
197 4         28 my @stuffs = @$reporter;
198              
199 4         23 while (@stuffs) {
200 7         17 my $reporter_class = shift @stuffs;
201 7         10 my $arg;
202 7 100 100     47 if ($stuffs[0] && ref $stuffs[0]) {
203 3         9 $arg = shift @stuffs;
204             }
205 7   66     45 push @reporters, [$reporter_class, $arg || ()];
206             }
207             }
208             else {
209 7         37 push @reporters, [$reporter];
210             }
211 11         87 @reporters;
212             }
213              
214             sub _load_reporter {
215 14     14   26 my $class = shift;
216 14         45 my $prefix = 'App::RunCron::Reporter';
217 14 50 33     298 unless ($class =~ s/^\+// || $class =~ /^$prefix/) {
218 14         49 $class = "$prefix\::$class";
219             }
220              
221 14         33 my $file = $class;
222 14         127 $file =~ s!::!/!g;
223 14         7154 require "$file.pm"; ## no citic
224              
225 14         326 $class;
226             }
227              
228             sub _log {
229 24     24   246 my ($self, $line) = @_;
230 24         119 my $logfh = $self->_logfh;
231 24 100       261 print $logfh (
232             ($self->timestamp ? _timestamp() : ''),
233             $line,
234             );
235             }
236              
237             sub _timestamp {
238 3     3   54 my @tm = gettimeofday;
239 3         238 my @dt = localtime $tm[0];
240 3         198 sprintf('[%04d-%02d-%02d %02d:%02d:%02d.%06.0f] ',
241             $dt[5] + 1900,
242             $dt[4] + 1,
243             $dt[3],
244             $dt[2],
245             $dt[1],
246             $dt[0],
247             $tm[1],
248             );
249             }
250              
251             __END__