File Coverage

blib/lib/AnyEvent/Multilog.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             package AnyEvent::Multilog;
2             # ABSTRACT: event-driven interface to a multilog process
3 3     3   237824 use Moose;
  0            
  0            
4             use MooseX::Types::Path::Class qw(File);
5              
6             use AnyEvent::Subprocess;
7             use AnyEvent::Subprocess::Job::Delegate::Handle;
8              
9             our $VERSION = '0.01';
10              
11             use namespace::autoclean;
12              
13             has 'multilog' => (
14             is => 'ro',
15             isa => File,
16             predicate => 'has_multilog_path',
17             coerce => 1,
18             documentation => q{path to multilog, if you don't want to use $PATH},
19             );
20              
21             has 'script' => (
22             is => 'ro',
23             isa => 'ArrayRef[Str]',
24             required => 1,
25             documentation => 'multilog "script", not escaped for the shell',
26             );
27              
28             has '_job' => (
29             init_arg => undef,
30             reader => '_job',
31             lazy => 1,
32             builder => '_build_job',
33             );
34              
35             has 'job_args' => (
36             is => 'ro',
37             isa => 'HashRef',
38             default => sub { +{} },
39             );
40              
41             has 'run' => (
42             init_arg => undef,
43             reader => 'run',
44             lazy_build => 1,
45             );
46              
47             has 'on_exit' => (
48             is => 'ro',
49             isa => 'CodeRef',
50             predicate => 'has_exit_handler',
51             documentation => 'optional callback called when multilog exists successfully',
52             );
53              
54             has 'on_error' => (
55             is => 'ro',
56             isa => 'CodeRef',
57             predicate => 'has_error_handler',
58             documentation => 'optional callback called when multilog emits an error',
59             );
60              
61             has 'errors' => (
62             is => 'bare', # uh, no it's not, but mooose bug
63             init_arg => undef,
64             traits => ['Array'],
65             default => sub { [] },
66             lazy => 1,
67             handles => {
68             push_error => 'push',
69             has_errors => 'count',
70             errors => 'elements',
71             },
72             );
73              
74             has 'is_shutdown' => (
75             init_arg => undef,
76             accessor => 'is_shutdown',
77             isa => 'Bool',
78             );
79              
80             has 'leftover_data' => (
81             init_arg => undef,
82             reader => 'leftover_data',
83             writer => 'set_leftover_data',
84             predicate => 'has_leftover_data',
85             );
86              
87              
88             sub ensure_validity {
89             my $self = shift;
90             confess 'already shutdown, cannot perform further operations' if $self->is_shutdown;
91             confess(join ', ', $self->errors) if $self->has_errors;
92             }
93              
94             sub _build_job {
95             my $self = shift;
96              
97             my $input = AnyEvent::Subprocess::Job::Delegate::Handle->new(
98             name => 'input_handle',
99             direction => 'w',
100             replace => 0,
101             want_leftovers => 1,
102             );
103              
104             my $errors = AnyEvent::Subprocess::Job::Delegate::Handle->new(
105             name => 'error_handle',
106             direction => 'r',
107             replace => 2,
108             );
109              
110             my $extra_delegates = delete $self->job_args->{delegates} || [];
111              
112             my $multilog = $self->has_multilog_path ? $self->multilog->stringify : 'multilog';
113              
114             return AnyEvent::Subprocess->new(
115             %{ $self->job_args },
116             delegates => [ @{$extra_delegates}, $input, $errors ],
117             on_completion => sub { $self->handle_completion($_[0]) },
118             code => [ $multilog, @{$self->script} ],
119             );
120             }
121              
122             sub _build_run {
123             my $self = shift;
124             my $run = $self->_job->run;
125              
126             my $errors = $run->delegate('error_handle');
127              
128             my $error_cb; $error_cb = sub {
129             my ($h, $line, $eol) = @_;
130             $self->handle_error($line);
131             $h->push_read( line => $error_cb );
132             };
133             $errors->handle->push_read( line => $error_cb );
134              
135             $run->delegate('input_handle')->handle->{linger} = 0;
136              
137             return $run;
138             }
139              
140             sub handle_error {
141             my ($self, $msg) = @_;
142             $self->on_error->($msg) if $self->has_error_handler;
143             $self->push_error($msg);
144             return;
145             }
146              
147             sub handle_completion {
148             my ($self, $done) = @_;
149             my ($success, $msg);
150              
151             $self->set_leftover_data( $done->delegate('input_handle')->wbuf )
152             if $done->delegate('input_handle')->has_wbuf;
153              
154             if($done->exit_value == 111){
155             $success = 0;
156             $msg = 'out of memory, or another multilog '.
157             'process is touching your files';
158             }
159              
160             elsif($done->exit_value == 0 && $self->has_leftover_data ){
161             $success = 1;
162             $msg = 'normal exit, with leftover data';
163             }
164              
165             elsif($done->is_success){
166             $success = 1;
167             $msg = 'normal exit';
168             }
169              
170             else {
171             $success = 0;
172             $msg = 'abnormal exit with signal '. $done->exit_signal;
173             }
174              
175             $self->on_exit->($success, $msg, $done) if $self->has_exit_handler;
176             return;
177             }
178              
179             sub start {
180             my $self = shift;
181             confess 'already started' if $self->has_run;
182             return $self->run;
183             }
184              
185             sub push_write {
186             my ($self, $line) = @_;
187             $self->ensure_validity;
188             $line .= "\n" if $line !~ /(?:\r\n|\r|\n)$/;
189             $self->run->delegate('input_handle')->handle->push_write($line);
190             }
191              
192             sub push_shutdown {
193             my $self = shift;
194             $self->run->kill('TERM');
195             }
196              
197             sub rotate {
198             my $self = shift;
199             $self->run->kill('ALRM');
200             }
201              
202             sub shutdown {
203             my $self = shift;
204             my $input = $self->run->delegate('input_handle');
205             confess 'already shutdown, cannot perform further operations' if $self->is_shutdown;
206             $input->handle->do_not_want;
207             }
208              
209             __PACKAGE__->meta->make_immutable;
210              
211             1;
212              
213              
214              
215             =pod
216              
217             =head1 NAME
218              
219             AnyEvent::Multilog - event-driven interface to a multilog process
220              
221             =head1 VERSION
222              
223             version 1.102861
224              
225             =head1 SYNOPSIS
226              
227             my $log = AnyEvent::Multilog->new(
228             script => [qw{t +* ./log}],
229             );
230              
231             $log->start;
232             $log->push_write('log message');
233             $log->push_write('another log message');
234             $log->rotate;
235             $log->push_write('another log message in a new log file');
236             $log->shutdown;
237              
238             =head1 DESCRIPTION
239              
240             This module makes it easy to log via a multilog process. It handles
241             spawning the multilog process and handling its errors.
242              
243             =head1 ATTRIBUTES
244              
245             =head2 script
246              
247             Required.
248              
249             This is an ArrayRef representing the multilog script that describes
250             how to log. See the L for more
251             information on what this script is and how to write one.
252              
253             Note that the shell is never invoked, so you don't need to escape
254             anything from the shell.
255              
256             To select all lines, add a tai64n timestamp, and log to a directory
257             called "log", your script should be C<['t', '+*', './log']>.
258              
259             =head2 multilog
260              
261             Optional.
262              
263             The path to the multilog binary. By default, checks C<$PATH> and uses
264             the one in there.
265              
266             =head2 on_exit
267              
268             Optional.
269              
270             Coderef that is called when the multilog process exists, successfully
271             or otherwise.
272              
273             Your coderef is passed three arguments, a boolean indicating
274             successful exit, a message indicating why multilog exited, and the
275             L object representing the exited
276             subprocess.
277              
278             =head2 on_error
279              
280             Optional.
281              
282             Coderef to be called when multilog writes something to stderr. It's
283             assumed that logging can't proceed after something is read from
284             stderr, so all methods will die regardless of whether or not you
285             handle this callback. Handling this event lets you proactively spawn
286             a new logger and kill this one without losing any messages.
287              
288             Patch welcome to make this automatic. I can't get multilog to die on
289             my machine.
290              
291             =head1 METHODS
292              
293             =head2 start
294              
295             Call this when you are ready to fork off the multilog and start
296             logging. If you call another method before calling this, the results
297             are undefined.
298              
299             =head2 push_write
300              
301             Send a line to multilog. If you don't provide a newline, one will be
302             provided for you.
303              
304             =head2 rotate
305              
306             Ask multilog to rotate the log right now.
307              
308             =head2 push_shutdown
309              
310             Ask multilog to shut down after writing the current line to disk. Any
311             pending data is made available via C<< $self->leftover_data >>, which
312             can be checked for with C<< $self->has_leftover_data >>.
313              
314             When multilog is done writing, your C callback will be
315             called.
316              
317             =head2 shutdown
318              
319             Shutdown immediately by closing the file descriptor that multilog is
320             reading from.
321              
322             When multilog is done writing, your C callback will be
323             called.
324              
325             =head2 is_shutdown
326              
327             Returns true if multilog is done.
328              
329             =head2 has_leftover_data
330              
331             Returns true if multilog exited without reading some data we asked it
332             to log.
333              
334             =head2 leftover_data
335              
336             Returns any data that multilog did not write before exiting.
337              
338             =head2 has_errors
339              
340             Returns true if errors were encourntered.
341              
342             =head2 errors
343              
344             Returns a list of errors that were encountered.
345              
346             =head1 AUTHOR
347              
348             Jonathan Rockway
349              
350             =head1 COPYRIGHT AND LICENSE
351              
352             This software is copyright (c) 2011 by Jonathan Rockway.
353              
354             This is free software; you can redistribute it and/or modify it under
355             the same terms as the Perl 5 programming language system itself.
356              
357             =cut
358              
359              
360             __END__