File Coverage

blib/lib/App/RunCron.pm
Criterion Covered Total %
statement 142 168 84.5
branch 37 64 57.8
condition 34 51 66.6
subroutine 26 28 92.8
pod 6 9 66.6
total 245 320 76.5


line stmt bran cond sub pod time code
1             package App::RunCron;
2 6     6   43209 use 5.008001;
  6         18  
  6         217  
3 6     6   27 use strict;
  6         8  
  6         211  
4 6     6   39 use warnings;
  6         7  
  6         277  
5              
6             our $VERSION = "0.08";
7              
8 6     6   32 use Fcntl qw(SEEK_SET);
  6         9  
  6         392  
9 6     6   3244 use File::Temp qw(tempfile);
  6         77390  
  6         361  
10 6     6   3187 use Time::HiRes qw/gettimeofday/;
  6         5735  
  6         72  
11 6     6   4313 use Sys::Hostname;
  6         5743  
  6         412  
12              
13             use Class::Accessor::Lite (
14 6         53 new => 1,
15             ro => [qw/timestamp command reporter error_reporter common_reporter tag print announcer/],
16             rw => [qw/logfile logpos exit_code _finished _started pid child_pid/],
17 6     6   3168 );
  6         5792  
18              
19             sub _logfh {
20 52     52   96 my $self = shift;
21 52 50       117 return if $self->child_pid;
22              
23 52   66     404 $self->{_logfh} ||= do {
24 13         20 my $logfh;
25 13         24 my $logfile = $self->{logfile};
26 13 100       32 if ($logfile) {
27 2 50       121 open $logfh, '>>', $logfile or die "failed to open file:$logfile:$!";
28             } else {
29 11         66 ($logfh, $logfile) = tempfile(UNLINK => 1);
30 11         4084 $self->logfile($logfile);
31             }
32 13         168 autoflush $logfh 1;
33 13         1220 print $logfh '-'x78, "\n";
34 13         68 $self->logpos(tell $logfh);
35 13 50       93 die "failed to obtain position of logfile:$!" if $self->logpos == -1;
36 13 50       89 seek $logfh, $self->logpos, SEEK_SET or die "cannot seek within logfile:$!";
37 13         132 $logfh;
38             };
39             }
40              
41             sub run {
42 1     1 1 3 my $self = shift;
43 1 50       8 if (!$self->_started) {
44 1         16 $self->_run;
45 1         32 exit $self->child_exit_code;
46             }
47             else {
48 0         0 warn "already run. can't rerun.\n";
49             }
50             }
51              
52             sub command_str {
53 15     15 0 277 my $self = shift;
54 15   66     95 $self->{command_str} ||= join ' ', @{ $self->command };
  13         36  
55             }
56              
57             sub _run {
58 13     13   65852 my $self = shift;
59 13 50       27 die "no command specified" unless @{ $self->command };
  13         64  
60              
61 13         153 my $logfh = $self->_logfh;
62 13 50       265 pipe my $logrh, my $logwh or die "failed to create pipe:$!";
63              
64 13         57 $self->pid($$);
65 13         104 $self->_started(1);
66 13   50     135 $self->_log(sprintf("%s tag:[%s] starting: %s\n", hostname, $self->tag || '', $self->command_str));
67 13         242 $self->exit_code(-1);
68 13 50       8661 unless (my $pid = fork) {
69 0 0       0 if (defined $pid) {
70             # child process
71 0         0 close $logrh;
72 0         0 close $logfh;
73              
74 0         0 $self->child_pid($$);
75 0 0       0 if ($self->announcer) {
76 0         0 $self->_announce;
77             }
78 0 0       0 open STDERR, '>&', $logwh or die "failed to redirect STDERR to logfile";
79 0 0       0 open STDOUT, '>&', $logwh or die "failed to redirect STDOUT to logfile";
80 0         0 close $logwh;
81 0         0 exec @{ $self->command };
  0         0  
82 0         0 die "exec(2) failed:$!:@{ $self->command }";
  0         0  
83             }
84             else {
85 0         0 close $logrh;
86 0         0 close $logwh;
87 0 0       0 print $logfh, "fork(2) failed:$!\n" unless defined $pid;
88             }
89             }
90             else {
91 13         349 close $logwh;
92 13 50       474 if ($self->print) {
93 0         0 require PerlIO::Util;
94 0         0 $self->_logfh->push_layer(tee => *STDOUT);
95             }
96 13         71208 $self->_log($_) while <$logrh>;
97 13         1264 close $logrh;
98 13 50       70 $self->_logfh->pop_layer if $self->print;
99 13         388 while (wait == -1) {}
100 13         77 $self->exit_code($?);
101             }
102              
103             # end
104 13         147 $self->_finished(1);
105 13         138 $self->_log($self->result_line. "\n");
106              
107 13 100       247 if ($self->is_success) {
108 12         114 $self->_send_report;
109             }
110             else {
111 1         11 $self->_send_error_report;
112             }
113             }
114              
115             sub child_exit_code {
116 16     16 1 35 my $self = shift;
117 16         77 my $exit_code = $self->exit_code;
118 16 100 66     252 return $exit_code if !$exit_code || $exit_code < 0;
119              
120 1         7 $self->exit_code >> 8;
121             }
122              
123             sub child_signal {
124 15     15 1 32 my $self = shift;
125 15         38 my $exit_code = $self->exit_code;
126 15 100 66     141 return $exit_code if !$exit_code || $exit_code < 0;
127              
128 1         6 $self->exit_code & 127;
129             }
130              
131 17     17 1 58 sub is_success { shift->exit_code == 0 }
132              
133             sub result_line {
134 15     15 1 31 my $self = shift;
135 15   66     114 $self->{result_line} ||= do {
136 13         49 my $exit_code = $self->exit_code;
137 13 50       148 if ($exit_code == -1) {
    50          
138 0         0 "failed to execute command:$!";
139             }
140             elsif ($self->child_signal) {
141 0         0 "command died with signal:" . $self->child_signal;
142             }
143             else {
144 13         59 "command exited with code:" . $self->child_exit_code;
145             }
146             };
147             }
148              
149             sub report {
150 10     10 1 20 my $self = shift;
151              
152 10   66     82 $self->{report} ||= do {
153 8 50       86 open my $fh, '<', $self->logfile or die "failed to open @{[$self->logfile]}:$!";
  0         0  
154 8 50       432 seek $fh, $self->logpos, SEEK_SET or die "failed to seek to the appropriate position in logfile:$!";
155 8         72 my $report = '';
156 8         192 $report .= $_ while <$fh>;
157 8         300 $report;
158             }
159             }
160              
161             sub report_data {
162 2     2 0 9 my $self = shift;
163             +{
164 2 50       14 report => $self->report,
165             command => $self->command_str,
166             result_line => $self->result_line,
167             is_success => $self->is_success,
168             child_exit_code => $self->child_exit_code,
169             exit_code => $self->exit_code,
170             child_signal => $self->child_signal,
171             pid => $self->pid,
172             (defined $self->tag ? (tag => $self->tag) : ()),
173             };
174             }
175              
176             sub announce_data {
177 0     0 0 0 my $self = shift;
178             +{
179 0         0 command => $self->command_str,
180             pid => $self->pid,
181             child_pid => $self->child_pid,
182             logfile => $self->logfile,
183             };
184             }
185              
186             sub _send_report {
187 12     12   24 my $self = shift;
188              
189 12   100     38 my $reporter = $self->reporter || 'None';
190 12   66     151 $self->_do_send_report($reporter, $self->common_reporter || ());
191             }
192              
193             sub _send_error_report {
194 1     1   3 my $self = shift;
195              
196 1   50     6 my $reporter = $self->error_reporter || 'Stdout';
197 1   33     12 $self->_do_send_report($reporter, $self->common_reporter || ());
198             }
199              
200             sub _invoke_plugins {
201 13     13   51 my ($self, $type, @plugins) = @_;
202              
203 13         20 my $has_error;
204 13         44 my $prefix = 'App::RunCron::' . ucfirst($type);
205 13         45 for my $plugin (@plugins) {
206 14 100 100     99 if (ref($plugin) && ref($plugin) eq 'CODE') {
207 1         13 $plugin = [Code => $plugin];
208             }
209 14         46 my @plugins = _retrieve_plugins($plugin);
210 14         30 for my $r (@plugins) {
211 15         25 my ($class, $arg) = @$r;
212 15         36 eval {
213 15   66     44 _load_class_with_prefix($class, $prefix)->new($arg || ())->run($self);
214             };
215 15 100       812 if (my $err = $@) {
216 1         2 $has_error = 1;
217 1         39 warn "$type error occured! $err";
218             }
219             }
220             }
221 13         42 $has_error;
222             }
223              
224             sub _announce {
225 0     0   0 my $self = shift;
226              
227 0         0 $self->_invoke_plugins(announcer => $self->announcer);
228             }
229              
230             sub _do_send_report {
231 13     13   165 my ($self, @reporters) = @_;
232              
233 13         45 my $err = $self->_invoke_plugins(reporter => @reporters);
234 13 100       215 if ($err) {
235 1         14 warn $self->report;
236             }
237             }
238              
239             sub _retrieve_plugins {
240 17     17   27 my $plugin = shift;
241 17         22 my @plugins;
242 17 100 66     91 if (ref $plugin && ref($plugin) eq 'ARRAY') {
243 7         31 my @stuffs = @$plugin;
244              
245 7         25 while (@stuffs) {
246 10         17 my $plugin_class = shift @stuffs;
247 10         12 my $arg;
248 10 100 100     62 if ($stuffs[0] && (ref($stuffs[0]) || $plugin_class eq 'Command')) {
      66        
249 6         13 $arg = shift @stuffs;
250             }
251 10   66     59 push @plugins, [$plugin_class, $arg || ()];
252             }
253             }
254             else {
255 10         46 push @plugins, [$plugin];
256             }
257 17         46 @plugins;
258             }
259              
260             sub _load_class_with_prefix {
261 20     20   32 my ($class, $prefix) = @_;
262              
263 20 50 33     398 unless ($class =~ s/^\+// || $class =~ /^$prefix/) {
264 20         73 $class = "$prefix\::$class";
265             }
266              
267 20         34 my $file = $class;
268 20         134 $file =~ s!::!/!g;
269 20         4992 require "$file.pm"; ## no citic
270              
271 20         341 $class;
272             }
273              
274             sub _log {
275 39     39   316 my ($self, $line) = @_;
276 39 50       134 return if $self->child_pid;
277              
278 39         311 my $logfh = $self->_logfh;
279 39 100       156 print $logfh (
280             ($self->timestamp ? _timestamp() : ''),
281             $line,
282             );
283             }
284              
285             sub _timestamp {
286 3     3   39 my @tm = gettimeofday;
287 3         149 my @dt = localtime $tm[0];
288 3         1280 sprintf('[%04d-%02d-%02d %02d:%02d:%02d.%06.0f] ',
289             $dt[5] + 1900,
290             $dt[4] + 1,
291             $dt[3],
292             $dt[2],
293             $dt[1],
294             $dt[0],
295             $tm[1],
296             );
297             }
298              
299             __END__