File Coverage

blib/lib/Dist/Zilla/Role/ErrorLogger.pm
Criterion Covered Total %
statement 94 94 100.0
branch 21 24 87.5
condition 3 3 100.0
subroutine 17 17 100.0
pod 4 4 100.0
total 139 142 97.8


line stmt bran cond sub pod time code
1             # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
2             #
3             # file: Dist/Zilla/Role/ErrorLogger.pm
4             #
5             # Copyright © 2015 Van de Bugger
6             #
7             # This file is part of perl-Dist-Zilla-Role-ErrorLogger.
8             #
9             # perl-Dist-Zilla-Role-ErrorLogger is free software: you can redistribute it and/or modify it
10             # under the terms of the GNU General Public License as published by the Free Software Foundation,
11             # either version 3 of the License, or (at your option) any later version.
12             #
13             # perl-Dist-Zilla-Role-ErrorLogger is distributed in the hope that it will be useful, but WITHOUT
14             # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15             # PURPOSE. See the GNU General Public License for more details.
16             #
17             # You should have received a copy of the GNU General Public License along with
18             # perl-Dist-Zilla-Role-ErrorLogger. If not, see <http://www.gnu.org/licenses/>.
19             #
20             # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
21              
22             #pod =for :this This is C<Dist::Zilla::Role::ErrorLogger> role documentation. Read this if you want to
23             #pod have error logging capabilities in your Dist::Zilla plugin.
24             #pod
25             #pod =head1 SYNOPSIS
26             #pod
27             #pod =for test_synopsis
28             #pod my ( $cond, $file );
29             #pod sub Dist::Zilla::Plugin::YourPlugin::do_something;
30             #pod sub Dist::Zilla::Plugin::YourPlugin::do_something_else;
31             #pod
32             #pod package Dist::Zilla::Plugin::YourPlugin;
33             #pod use Moose;
34             #pod use namespace::autoclean;
35             #pod with 'Dist::Zilla::Role::Plugin';
36             #pod with 'Dist::Zilla::Role::ErrorLogger';
37             #pod
38             #pod sub method {
39             #pod my $self = shift( @_ );
40             #pod
41             #pod if ( $cond ) { $self->log_error( 'error message' ); };
42             #pod
43             #pod do_something or $self->log_error( 'another error message' );
44             #pod
45             #pod while ( $cond ) {
46             #pod do_something_else or $self->log_error( 'error message' ) and next;
47             #pod ...;
48             #pod };
49             #pod
50             #pod $self->log_errors_in_file(
51             #pod $file,
52             #pod 1 => 'error message', # Error at file line 1.
53             #pod 5 => 'another error message', # Error at file line 5.
54             #pod );
55             #pod
56             #pod $self->abort_if_errors( 'errors found' );
57             #pod };
58             #pod
59             #pod __PACKAGE__->meta->make_immutable;
60             #pod 1;
61             #pod
62             #pod =cut
63              
64             # --------------------------------------------------------------------------------------------------
65              
66             #pod =head1 DESCRIPTION
67             #pod
68             #pod The role extends standard C<Dist::Zilla> logging capabilities with few methods a bit more
69             #pod convenient for reporting (multiple) errors than brutal C<log_fatal>. See L</"WHY?"> for more
70             #pod details.
71             #pod
72             #pod The role requires C<log> method in the consumer.
73             #pod
74             #pod =cut
75              
76             # --------------------------------------------------------------------------------------------------
77              
78             package Dist::Zilla::Role::ErrorLogger;
79              
80 1     1   1537444 use Moose::Role;
  1         1  
  1         7  
81 1     1   3169 use namespace::autoclean;
  1         1  
  1         6  
82 1     1   51 use version 0.77;
  1         23  
  1         7  
83              
84             # ABSTRACT: Have error logging capabilities in your Dist::Zilla plugin
85             our $VERSION = 'v0.9.0_01'; # TRIAL VERSION
86              
87             requires qw{ log };
88              
89 1     1   76 use List::Util qw{ min max };
  1         2  
  1         699  
90              
91             # --------------------------------------------------------------------------------------------------
92              
93             #pod =attr error_count
94             #pod
95             #pod $int = $self->error_count;
96             #pod
97             #pod C<Int>, read-only. Number of logged errors (i. e. number of made C<log_error> calls).
98             #pod
99             #pod =cut
100              
101             has error_count => (
102             is => 'ro',
103             isa => 'Int',
104             default => 0,
105             init_arg => undef,
106             );
107              
108             # --------------------------------------------------------------------------------------------------
109              
110             #pod =method log_error
111             #pod
112             #pod $self->log_error( @items );
113             #pod $self->log_error( \%args, @items );
114             #pod
115             #pod This method calls C<log> method, passing all the arguments, and increments value of C<error_count>
116             #pod attribute. The method returns true value, so can be used in following constructs:
117             #pod
118             #pod while ( ... ) {
119             #pod do_something or $self->log_error( 'message' ) and next;
120             #pod ...
121             #pod };
122             #pod
123             #pod =cut
124              
125             sub log_error { ## no critic ( RequireArgUnpacking )
126 46     46 1 66847 my $self = shift( @_ );
127             # If the first argument is a hashref, it is treated as extra arguments to logging, not as
128             # message, see <https://metacpan.org/pod/Log::Dispatchouli#log>. These extra arguments
129             # include `level` argument. It seems natural that error messages have `'error'` level.
130             # However, such messages do not appear in `dzil` output. So, do not try to set level, just
131             # pass all the arguments to `log`.
132 46         109 $self->log( @_ );
133 46         9561 ++ $self->{ error_count };
134 46         94 return $self->{ error_count };
135             };
136              
137             # --------------------------------------------------------------------------------------------------
138              
139             #pod =method abort
140             #pod
141             #pod $self->abort( @items );
142             #pod $self->abort( \%args, @items );
143             #pod
144             #pod This is an attempt to workaround L<C<log_fatal>
145             #pod drawback|https://github.com/rjbs/Dist-Zilla/issues/397>: in contrast to C<log_fatal>, C<abort>
146             #pod guarantees the message (which can be quite long) appears on the screen only once.
147             #pod
148             #pod The method log the message (via C<log>), then flush C<STDOUT>, then throws an exception of
149             #pod C<Dist::Zilla::Role::ErrorLogger::Exception::Abort> class (which being stringified gives short
150             #pod message C<"Aborting...\n">).
151             #pod
152             #pod =cut
153              
154             sub abort { ## no critic ( RequireArgUnpacking, RequireFinalReturn )
155 7     7 1 98131 my $self = shift( @_ );
156 7 100       23 if ( @_ ) {
157 2         8 $self->log( @_ );
158             };
159 7         453 STDOUT->flush();
160 7         60 Dist::Zilla::Role::ErrorLogger::Exception::Abort->throw();
161             };
162              
163             # --------------------------------------------------------------------------------------------------
164              
165             #pod =method abort_if_error
166             #pod
167             #pod =method abort_if_errors
168             #pod
169             #pod $self->abort_if_errors( @items );
170             #pod $self->abort_if_errors( \%args, @items );
171             #pod
172             #pod If there was any errors (i. e. C<error_count> is greater than zero), the logs all the arguments and
173             #pod aborts execution. Both actions (logging and aborting) are implemented by calling C<abort>.
174             #pod
175             #pod C<abort_if_error> is an alias for C<abort_if_errors>.
176             #pod
177             #pod =cut
178              
179             sub abort_if_errors { ## no critic ( RequireArgUnpacking )
180 7     7 1 36267 my $self = shift( @_ );
181 7 100       226 if ( $self->error_count ) {
182 5         17 $self->abort( @_ );
183             };
184 2         4 return;
185             };
186              
187             *abort_if_error = \&abort_if_errors;
188              
189             # --------------------------------------------------------------------------------------------------
190              
191             #pod =method log_errors_in_file
192             #pod
193             #pod The method intended to report errors against a file. It prints file name (and colon after it), then
194             #pod prints line-numbered file content annotated by error messages. The method does not print entire
195             #pod file content, but only error lines with surrounding context (2 lines above and below each error
196             #pod line).
197             #pod
198             #pod $self->log_errors_in_file(
199             #pod $file,
200             #pod $linenum1 => $message1,
201             #pod $linenum2 => $message2,
202             #pod $linenum3 => [ $message3a, $message3b, ... ],
203             #pod ...
204             #pod );
205             #pod
206             #pod C<$file> should be a C<Dist::Zilla> file (e. g. C<Dist::Zilla::File::OnDisk>,
207             #pod C<Dist::Zilla::File::InMemory>, or does role C<Dist::Zilla::Role::File>).
208             #pod
209             #pod Errors are specified by pairs C<< $linenum => $message >>, where C<$linenum> is a number of problem
210             #pod line (one-based), and C<$message> is an error message (C<Str>) or array of messages
211             #pod (C<ArrayRef[Str]>). Order of errors does not matter usually. However, if errors are associated with
212             #pod the same line (the same line number may appear multiple times), they will be printed in order of
213             #pod appearance.
214             #pod
215             #pod Zero or negative line numbers, or line numbers beyond the last line are invalid. Messages
216             #pod associated with invalid line numbers are reported in unspecified way.
217             #pod
218             #pod Normally, the method prints all the information by calling C<log_error> method and returns a
219             #pod positive integer. However, If any invalid line numbers are specified, the method returns negative
220             #pod integer. If no errors are specified, the method prints "No errors found at I<file>." by calling
221             #pod C<log> (not C<log_error>!) and returns zero.
222             #pod
223             #pod TODO: Example.
224             #pod
225             #pod =cut
226              
227             sub log_errors_in_file {
228              
229 4     4 1 166143 my ( $self, $file, @errors ) = @_;
230              
231             # Corner case: no errors specified.
232 4 100       16 if ( not @errors ) {
233 1         6 $self->log( [ 'No errors at %s.', $file->name ] );
234 1         299 return 0;
235             };
236              
237             # TODO: Chop too long lines?
238 3         15 my $text = [ split( "\n", $file->content ) ];
239              
240             # Parse `@errors`.
241 3         227 my %errors; # Key is line number, value is arrayref to error messages.
242             my %invalid; # The same but for invalid line numbers.
243 3         29 while ( @errors ) {
244 10         16 my ( $n, $msg ) = splice( @errors, 0, 2 );
245 10 100 100     35 if ( 1 <= $n and $n <= @$text ) {
246 6         8 my $ctx = 2; # TODO: Parametrize it?
247 6         31 for my $k ( max( $n - $ctx, 1 ) .. min( $n + $ctx, @$text + 0 ) ) {
248 27 100       38 if ( not $errors{ $k } ) {
249 19         31 $errors{ $k } = [];
250             };
251             };
252 6 100       7 push( @{ $errors{ $n } }, ref( $msg ) ? @$msg : $msg );
  6         24  
253             } else {
254 4 50       4 push( @{ $invalid{ $n } }, ref( $msg ) ? @$msg : $msg );
  4         15  
255             };
256             };
257              
258 3         6 my $t = ' ' x 4; # Indent for text lines.
259              
260 3 50       9 if ( %errors ) {
261              
262 3         6 my $w = length( 0 + @$text ); # Width of linenumber column.
263 3         8 my $e = $t . ( ' ' x ( $w + 2 ) ); # Indent for error messages.
264 3         3 my $last = 0; # Number of the last printed text line.
265              
266             my $log_line = sub { # Log text line number and content.
267 20     20   19 my ( $n ) = @_;
268 20 50       42 my $line = $n <= @$text ? $text->[ $n - 1 ] : '';
269 20         33 chomp( $line );
270 20         49 $self->log_error( [ '%s%0*d: %s', $t, $w, $n, $line ] );
271 3         13 };
272             my $log_messages = sub { # Log error messahes.
273 19     19   18 my ( $n ) = @_;
274 19         14 $self->log_error( [ '%s^^^ %s ^^^', $e, $_ ] ) for @{ $errors{ $n } };
  19         46  
275 3         8 };
276             my $log_skipped = sub { # Log number of skipped lines.
277 22     22   21 my ( $n ) = @_;
278 22 100       56 if ( $n > $last + 1 ) { # There are skipped lines.
279 6         7 my $count = $n - $last - 1; # Number of skipped lines.
280 6 100       10 if ( $count == 1 ) {
281 1         3 $log_line->( $n - 1 ); # There is no sense to skip one line.
282             } else {
283 5         12 $self->log_error( [ '%s... skipped %d lines ...', $e, $count ] );
284             };
285             };
286 3         19 };
287              
288             # Do actual logging.
289 3         14 $self->log_error( [ '%s:', $file->name ] );
290 3         19 for my $n ( sort( { $a <=> $b } keys( %errors ) ) ) {
  35         34  
291 19         24 $log_skipped->( $n );
292 19         30 $log_line->( $n );
293 19         34 $log_messages->( $n );
294 19         21 $last = $n;
295             };
296 3         7 $log_skipped->( @$text + 1 );
297              
298             };
299              
300 3 100       9 if ( %invalid ) {
301 1         3 $self->log_error( 'Following errors are reported against non-existing lines of the file:' );
302 1         4 for my $n ( sort( { $a <=> $b } keys( %invalid ) ) ) {
  4         6  
303             $self->log_error( [ '%s%s at %s line %d.', $t, $_, $file->name, $n ] )
304 4         4 for @{ $invalid{ $n } };
  4         17  
305             };
306 1         5 return -1;
307             };
308              
309 2         10 return 1;
310              
311             };
312              
313             # --------------------------------------------------------------------------------------------------
314              
315             ## no critic ( ProhibitMultiplePackages )
316              
317             package Dist::Zilla::Role::ErrorLogger::Exception::Abort;
318              
319 1     1   5 use strict;
  1         2  
  1         15  
320 1     1   3 use warnings;
  1         1  
  1         25  
321 1     1   3 use version 0.77;
  1         13  
  1         4  
322              
323             # ABSTRACT: Exception class which C<ErrorLogger> throws to abort C<Dist::Zilla>
324             our $VERSION = 'v0.9.0_01'; # TRIAL VERSION
325              
326 1     1   75 use overload '""' => sub { return "Aborting...\n"; };
  1     14   1  
  1         8  
  14         2024  
327              
328             sub throw {
329 7     7   10 my ( $class ) = @_;
330 7         49 die bless( {} => $class ); ## no critic ( RequireCarping )
331             };
332              
333             # --------------------------------------------------------------------------------------------------
334              
335             1;
336              
337             # --------------------------------------------------------------------------------------------------
338              
339             #pod =head1 NOTES
340             #pod
341             #pod All the methods defined in the role log items through the C<log> method. C<Dist::Zilla> takes this
342             #pod method from C<Log::Dispatchouli>, the latter uses C<String::Flogger> to process the messages. It
343             #pod means you can use C<String::Flogger> tricks, e. g.:
344             #pod
345             #pod $self->log_error( [ 'oops at %s line %d', $file, $line ] );
346             #pod # [] are shorter than sprintf.
347             #pod
348             #pod Also note how C<Log::Dispatchouli> describes the C<log> method:
349             #pod
350             #pod $logger->log( @messages );
351             #pod
352             #pod and says:
353             #pod
354             #pod Each message is flogged individually, then joined with spaces.
355             #pod
356             #pod So beware. A call
357             #pod
358             #pod $self->log_error( 'error 1', 'error 2' );
359             #pod
360             #pod logs I<one> message "error 1 error 2", I<not> I<two> messages "error 1" and "error 2", and bumps
361             #pod C<error_count> by 1, not 2.
362             #pod
363             #pod =head1 SEE ALSO
364             #pod
365             #pod =for :list
366             #pod = L<Dist::Zilla>
367             #pod = L<Dist::Zilla::Role>
368             #pod = L<Dist::Zilla::Plugin>
369             #pod = L<Log::Dispatchouli>
370             #pod = L<String::Flogger>
371             #pod
372             #pod =head1 COPYRIGHT AND LICENSE
373             #pod
374             #pod Copyright (C) 2015 Van de Bugger
375             #pod
376             #pod License GPLv3+: The GNU General Public License version 3 or later
377             #pod <http://www.gnu.org/licenses/gpl-3.0.txt>.
378             #pod
379             #pod This is free software: you are free to change and redistribute it. There is
380             #pod NO WARRANTY, to the extent permitted by law.
381             #pod
382             #pod
383             #pod =cut
384              
385             # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
386             #
387             # file: doc/what.pod
388             #
389             # This file is part of perl-Dist-Zilla-Role-ErrorLogger.
390             #
391             # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
392              
393             #pod =encoding UTF-8
394             #pod
395             #pod =head1 WHAT?
396             #pod
397             #pod C<Dist-Zilla-Role-ErrorLogger> is a C<Dist::Zilla> role. It provides C<log_error>, C<abort>, and
398             #pod C<abort_if_errors> methods to consuming plugins.
399             #pod
400             #pod =cut
401              
402             # end of file #
403             # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
404             #
405             # file: doc/why.pod
406             #
407             # This file is part of perl-Dist-Zilla-Role-ErrorLogger.
408             #
409             # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
410              
411             #pod =encoding UTF-8
412             #pod
413             #pod =head1 WHY?
414             #pod
415             #pod C<Dist::Zilla> limits logging capabilities with 3 logging levels available in plugins through
416             #pod C<log_debug>, C<log>, and C<log_fatal> methods. Debug level messages are turned off by default, the
417             #pod first fatal message terminates C<Dist::Zilla>. This is simple, but sometimes you may want to report
418             #pod all the errors, instead of stopping at the first found one. In such a case C<log_fatal> cannot be
419             #pod used, obviously. There are few alternatives:
420             #pod
421             #pod Collect error messages in an array, then report all the errors with single C<log_fatal> call:
422             #pod
423             #pod my @errors;
424             #pod ...
425             #pod push( @errors, ... );
426             #pod ...
427             #pod if ( @errors ) {
428             #pod $self->log_fatal( join( "\n", @errors ) );
429             #pod };
430             #pod
431             #pod This works, but current implementation of C<log_fatal> has a disadvantage: it prints the message
432             #pod twice, so output looks ugly. (See L<message handling in log_fatal is
433             #pod suboptimal|https://github.com/rjbs/Dist-Zilla/issues/397>.)
434             #pod
435             #pod Another approach is reporting each error immediately with C<log>, counting number of reported
436             #pod errors, and calling C<log_fatal> once at the end:
437             #pod
438             #pod my $error_count = 0;
439             #pod ...
440             #pod $self->log( 'error' );
441             #pod ++ $error_count;
442             #pod ...
443             #pod if ( $error_count ) {
444             #pod $self->log_fatal( 'Aborting...' );
445             #pod };
446             #pod
447             #pod This works, but incrementing the counter after each C<log> call is boring and error-prone.
448             #pod C<Dist-Zilla-Role-ErrorLogger> role automates it, making plugin code shorter and more readable:
449             #pod
450             #pod with 'Dist-Zilla-Role-ErrorLogger';
451             #pod ...
452             #pod $self->log_error( 'error' );
453             #pod ...
454             #pod $self->abort_if_errors();
455             #pod
456             #pod =cut
457              
458             # end of file #
459              
460              
461             # end of file #
462              
463             __END__
464              
465             =pod
466              
467             =encoding UTF-8
468              
469             =head1 NAME
470              
471             Dist::Zilla::Role::ErrorLogger - Have error logging capabilities in your Dist::Zilla plugin
472              
473             =head1 VERSION
474              
475             Version v0.9.0_01, released on 2016-10-11 18:51 UTC.
476             This is a B<trial release>.
477              
478             =head1 WHAT?
479              
480             C<Dist-Zilla-Role-ErrorLogger> is a C<Dist::Zilla> role. It provides C<log_error>, C<abort>, and
481             C<abort_if_errors> methods to consuming plugins.
482              
483             This is C<Dist::Zilla::Role::ErrorLogger> role documentation. Read this if you want to
484             have error logging capabilities in your Dist::Zilla plugin.
485              
486             =head1 SYNOPSIS
487              
488             =head1 DESCRIPTION
489              
490             The role extends standard C<Dist::Zilla> logging capabilities with few methods a bit more
491             convenient for reporting (multiple) errors than brutal C<log_fatal>. See L</"WHY?"> for more
492             details.
493              
494             The role requires C<log> method in the consumer.
495              
496             =head1 OBJECT ATTRIBUTES
497              
498             =head2 error_count
499              
500             $int = $self->error_count;
501              
502             C<Int>, read-only. Number of logged errors (i. e. number of made C<log_error> calls).
503              
504             =head1 OBJECT METHODS
505              
506             =head2 log_error
507              
508             $self->log_error( @items );
509             $self->log_error( \%args, @items );
510              
511             This method calls C<log> method, passing all the arguments, and increments value of C<error_count>
512             attribute. The method returns true value, so can be used in following constructs:
513              
514             while ( ... ) {
515             do_something or $self->log_error( 'message' ) and next;
516             ...
517             };
518              
519             =head2 abort
520              
521             $self->abort( @items );
522             $self->abort( \%args, @items );
523              
524             This is an attempt to workaround L<C<log_fatal>
525             drawback|https://github.com/rjbs/Dist-Zilla/issues/397>: in contrast to C<log_fatal>, C<abort>
526             guarantees the message (which can be quite long) appears on the screen only once.
527              
528             The method log the message (via C<log>), then flush C<STDOUT>, then throws an exception of
529             C<Dist::Zilla::Role::ErrorLogger::Exception::Abort> class (which being stringified gives short
530             message C<"Aborting...\n">).
531              
532             =head2 abort_if_error
533              
534             =head2 abort_if_errors
535              
536             $self->abort_if_errors( @items );
537             $self->abort_if_errors( \%args, @items );
538              
539             If there was any errors (i. e. C<error_count> is greater than zero), the logs all the arguments and
540             aborts execution. Both actions (logging and aborting) are implemented by calling C<abort>.
541              
542             C<abort_if_error> is an alias for C<abort_if_errors>.
543              
544             =head2 log_errors_in_file
545              
546             The method intended to report errors against a file. It prints file name (and colon after it), then
547             prints line-numbered file content annotated by error messages. The method does not print entire
548             file content, but only error lines with surrounding context (2 lines above and below each error
549             line).
550              
551             $self->log_errors_in_file(
552             $file,
553             $linenum1 => $message1,
554             $linenum2 => $message2,
555             $linenum3 => [ $message3a, $message3b, ... ],
556             ...
557             );
558              
559             C<$file> should be a C<Dist::Zilla> file (e. g. C<Dist::Zilla::File::OnDisk>,
560             C<Dist::Zilla::File::InMemory>, or does role C<Dist::Zilla::Role::File>).
561              
562             Errors are specified by pairs C<< $linenum => $message >>, where C<$linenum> is a number of problem
563             line (one-based), and C<$message> is an error message (C<Str>) or array of messages
564             (C<ArrayRef[Str]>). Order of errors does not matter usually. However, if errors are associated with
565             the same line (the same line number may appear multiple times), they will be printed in order of
566             appearance.
567              
568             Zero or negative line numbers, or line numbers beyond the last line are invalid. Messages
569             associated with invalid line numbers are reported in unspecified way.
570              
571             Normally, the method prints all the information by calling C<log_error> method and returns a
572             positive integer. However, If any invalid line numbers are specified, the method returns negative
573             integer. If no errors are specified, the method prints "No errors found at I<file>." by calling
574             C<log> (not C<log_error>!) and returns zero.
575              
576             TODO: Example.
577              
578             =head1 WHY?
579              
580             C<Dist::Zilla> limits logging capabilities with 3 logging levels available in plugins through
581             C<log_debug>, C<log>, and C<log_fatal> methods. Debug level messages are turned off by default, the
582             first fatal message terminates C<Dist::Zilla>. This is simple, but sometimes you may want to report
583             all the errors, instead of stopping at the first found one. In such a case C<log_fatal> cannot be
584             used, obviously. There are few alternatives:
585              
586             Collect error messages in an array, then report all the errors with single C<log_fatal> call:
587              
588             my @errors;
589             ...
590             push( @errors, ... );
591             ...
592             if ( @errors ) {
593             $self->log_fatal( join( "\n", @errors ) );
594             };
595              
596             This works, but current implementation of C<log_fatal> has a disadvantage: it prints the message
597             twice, so output looks ugly. (See L<message handling in log_fatal is
598             suboptimal|https://github.com/rjbs/Dist-Zilla/issues/397>.)
599              
600             Another approach is reporting each error immediately with C<log>, counting number of reported
601             errors, and calling C<log_fatal> once at the end:
602              
603             my $error_count = 0;
604             ...
605             $self->log( 'error' );
606             ++ $error_count;
607             ...
608             if ( $error_count ) {
609             $self->log_fatal( 'Aborting...' );
610             };
611              
612             This works, but incrementing the counter after each C<log> call is boring and error-prone.
613             C<Dist-Zilla-Role-ErrorLogger> role automates it, making plugin code shorter and more readable:
614              
615             with 'Dist-Zilla-Role-ErrorLogger';
616             ...
617             $self->log_error( 'error' );
618             ...
619             $self->abort_if_errors();
620              
621             =for test_synopsis my ( $cond, $file );
622             sub Dist::Zilla::Plugin::YourPlugin::do_something;
623             sub Dist::Zilla::Plugin::YourPlugin::do_something_else;
624              
625             package Dist::Zilla::Plugin::YourPlugin;
626             use Moose;
627             use namespace::autoclean;
628             with 'Dist::Zilla::Role::Plugin';
629             with 'Dist::Zilla::Role::ErrorLogger';
630              
631             sub method {
632             my $self = shift( @_ );
633              
634             if ( $cond ) { $self->log_error( 'error message' ); };
635              
636             do_something or $self->log_error( 'another error message' );
637              
638             while ( $cond ) {
639             do_something_else or $self->log_error( 'error message' ) and next;
640             ...;
641             };
642              
643             $self->log_errors_in_file(
644             $file,
645             1 => 'error message', # Error at file line 1.
646             5 => 'another error message', # Error at file line 5.
647             );
648              
649             $self->abort_if_errors( 'errors found' );
650             };
651              
652             __PACKAGE__->meta->make_immutable;
653             1;
654              
655             =head1 NOTES
656              
657             All the methods defined in the role log items through the C<log> method. C<Dist::Zilla> takes this
658             method from C<Log::Dispatchouli>, the latter uses C<String::Flogger> to process the messages. It
659             means you can use C<String::Flogger> tricks, e. g.:
660              
661             $self->log_error( [ 'oops at %s line %d', $file, $line ] );
662             # [] are shorter than sprintf.
663              
664             Also note how C<Log::Dispatchouli> describes the C<log> method:
665              
666             $logger->log( @messages );
667              
668             and says:
669              
670             Each message is flogged individually, then joined with spaces.
671              
672             So beware. A call
673              
674             $self->log_error( 'error 1', 'error 2' );
675              
676             logs I<one> message "error 1 error 2", I<not> I<two> messages "error 1" and "error 2", and bumps
677             C<error_count> by 1, not 2.
678              
679             =head1 SEE ALSO
680              
681             =over 4
682              
683             =item L<Dist::Zilla>
684              
685             =item L<Dist::Zilla::Role>
686              
687             =item L<Dist::Zilla::Plugin>
688              
689             =item L<Log::Dispatchouli>
690              
691             =item L<String::Flogger>
692              
693             =back
694              
695             =head1 AUTHOR
696              
697             Van de Bugger <van.de.bugger@gmail.com>
698              
699             =head1 COPYRIGHT AND LICENSE
700              
701             Copyright (C) 2015 Van de Bugger
702              
703             License GPLv3+: The GNU General Public License version 3 or later
704             <http://www.gnu.org/licenses/gpl-3.0.txt>.
705              
706             This is free software: you are free to change and redistribute it. There is
707             NO WARRANTY, to the extent permitted by law.
708              
709             =cut