File Coverage

blib/lib/Log/Dispatch.pm
Criterion Covered Total %
statement 123 146 84.2
branch 29 42 69.0
condition 1 5 20.0
subroutine 31 43 72.0
pod 12 24 50.0
total 196 260 75.3


line stmt bran cond sub pod time code
1             package Log::Dispatch;
2              
3 29     29   3524987 use 5.006;
  29         386  
4              
5 29     29   279 use strict;
  29         60  
  29         540  
6 29     29   143 use warnings;
  29         161  
  29         1107  
7              
8             our $VERSION = '2.71';
9              
10 29     29   153 use Carp ();
  29         76  
  29         743  
11 29     29   11851 use Log::Dispatch::Types;
  29         115  
  29         312  
12 29     29   780040 use Log::Dispatch::Vars qw( %CanonicalLevelNames %LevelNamesToNumbers );
  29         59  
  29         4000  
13 29     29   253 use Module::Runtime qw( use_package_optimistically );
  29         97  
  29         348  
14 29     29   16530 use Params::ValidationCompiler qw( validation_for );
  29         490248  
  29         1628  
15              
16 29     29   186 use base qw( Log::Dispatch::Base );
  29         79  
  29         12601  
17              
18             BEGIN {
19 29     29   212 for my $l ( keys %CanonicalLevelNames ) {
20 348         598 my $level_num = $LevelNamesToNumbers{$l};
21             my $sub = sub {
22 172     172   20055263 my $self = shift;
23             $self->_log_with_num(
24             $level_num,
25 172 100       2134 level => $CanonicalLevelNames{$l},
26             message => @_ > 1 ? "@_" : $_[0],
27             );
28 348         1244 };
29              
30             ## no critic (TestingAndDebugging::ProhibitNoStrict)
31 29     29   207 no strict 'refs';
  29         70  
  29         1006  
32 348         496 *{$l} = $sub;
  348         48502  
33             }
34             }
35              
36             {
37             my $validator = validation_for(
38             params => {
39             outputs => {
40             type => t('ArrayRef'),
41             optional => 1,
42             },
43             callbacks => {
44             type => t('Callbacks'),
45             optional => 1,
46             },
47             },
48             );
49              
50             sub new {
51 100     100 1 407491 my $class = shift;
52 100         8363 my %p = $validator->(@_);
53              
54 99         4945 my $self = bless {}, $class;
55              
56             $self->{callbacks} = $p{callbacks}
57 99 100       681 if $p{callbacks};
58              
59 99 100       363 if ( my $outputs = $p{outputs} ) {
60 62 100       277 if ( ref $outputs->[1] eq 'HASH' ) {
61              
62             # 2.23 API
63             # outputs => [
64             # File => { min_level => 'debug', filename => 'logfile' },
65             # Screen => { min_level => 'warning' }
66             # ]
67 1         20 while ( my ( $class, $params ) = splice @$outputs, 0, 2 ) {
68 2         8 $self->_add_output( $class, %$params );
69             }
70             }
71             else {
72              
73             # 2.24+ syntax
74             # outputs => [
75             # [ 'File', min_level => 'debug', filename => 'logfile' ],
76             # [ 'Screen', min_level => 'warning' ]
77             # ]
78 61         119 for my $arr ( @{$outputs} ) {
  61         334  
79 63 100       660 die "expected arrayref, not '$arr'"
80             unless ref $arr eq 'ARRAY';
81 62         136 $self->_add_output( @{$arr} );
  62         404  
82             }
83             }
84             }
85              
86 98         407 return $self;
87             }
88             }
89              
90             sub clone {
91 1     1 1 4 my $self = shift;
92              
93             my %clone = (
94 1 50       6 callbacks => [ @{ $self->{callbacks} || [] } ],
95 1 50       2 outputs => { %{ $self->{outputs} || {} } },
  1         7  
96             );
97              
98 1         5 return bless \%clone, ref $self;
99             }
100              
101             sub _add_output {
102 64     64   127 my $self = shift;
103 64         310 my $class = shift;
104              
105 64 100       552 my $full_class
106             = substr( $class, 0, 1 ) eq '+'
107             ? substr( $class, 1 )
108             : "Log::Dispatch::$class";
109              
110 64         977 use_package_optimistically($full_class);
111              
112 64         10720 $self->add( $full_class->new(@_) );
113             }
114              
115             sub add {
116 104     104 1 1021 my $self = shift;
117 104         168 my $object = shift;
118              
119             # Once 5.6 is more established start using the warnings module.
120 104 0 33     774 if ( exists $self->{outputs}{ $object->name } && $^W ) {
121 0         0 Carp::carp(
122             'Log::Dispatch::* object ', $object->name,
123             ' already exists.'
124             );
125             }
126              
127 104         311 $self->{outputs}{ $object->name } = $object;
128             }
129              
130             sub remove {
131 0     0 1 0 my $self = shift;
132 0         0 my $name = shift;
133              
134 0         0 return delete $self->{outputs}{$name};
135             }
136              
137             sub outputs {
138 3     3 1 12 my $self = shift;
139              
140 3         5 return values %{ $self->{outputs} };
  3         11  
141             }
142              
143             sub callbacks {
144 3     3 1 770 my $self = shift;
145              
146 3         4 return @{ $self->{callbacks} };
  3         13  
147             }
148              
149             ## no critic (Subroutines::ProhibitBuiltinHomonyms)
150             sub log {
151 48     48 1 1118 my $self = shift;
152 48         166 my %p = @_;
153              
154 48         154 my $level_num = $self->_level_as_number( $p{level} );
155 47 50       116 return unless defined $level_num;
156              
157 47         158 return $self->_log_with_num( $level_num, %p );
158              
159             }
160             ## use critic
161              
162             sub _log_with_num {
163 219     219   711 my $self = shift;
164 219         552 my $level_num = shift;
165 219         1485 my %p = @_;
166              
167 219 100       1197 return unless $self->_would_log($level_num);
168              
169 128         746 $p{message} = $self->_prepare_message(%p);
170 128         311 $_->_log_with_num( $level_num, %p ) for values %{ $self->{outputs} };
  128         1566  
171              
172 128         947 return;
173             }
174              
175             sub _prepare_message {
176 130     130   320 my $self = shift;
177 130         565 my %p = @_;
178              
179             $p{message} = $p{message}->()
180 130 100       590 if ref $p{message} eq 'CODE';
181              
182             $p{message} = $self->_apply_callbacks(%p)
183 130 100       821 if $self->{callbacks};
184              
185 130         524 return $p{message};
186             }
187              
188             sub _log_to_outputs {
189 2     2   4 my $self = shift;
190 2         7 my %p = @_;
191              
192 2         7 for ( values %{ $self->{outputs} } ) {
  2         6  
193 2         14 $_->log(%p);
194             }
195             }
196              
197             sub log_and_die {
198 2     2 1 82 my $self = shift;
199 2         8 my %p = @_;
200              
201 2         8 $p{message} = $self->_prepare_message(%p);
202              
203 2 50       13 $self->_log_to_outputs(%p) if $self->would_log( $p{level} );
204              
205 2         32 $self->_die_with_message(%p);
206             }
207              
208             sub log_and_croak {
209 1     1 1 2688 my $self = shift;
210              
211 1         3 $self->log_and_die(@_);
212             }
213              
214             sub _die_with_message {
215 2     2   4 my $self = shift;
216 2         7 my %p = @_;
217              
218 2         5 my $msg = $p{message};
219              
220             local $Carp::CarpLevel = ( $Carp::CarpLevel || 0 ) + $p{carp_level}
221 2 50 0     15 if exists $p{carp_level};
222              
223 2         284 Carp::croak($msg);
224             }
225              
226             sub log_to {
227 0     0 1 0 my $self = shift;
228 0         0 my %p = @_;
229              
230             $p{message} = $self->_apply_callbacks(%p)
231 0 0       0 if $self->{callbacks};
232              
233 0         0 $self->_log_to(%p);
234             }
235              
236             sub _log_to {
237 0     0   0 my $self = shift;
238 0         0 my %p = @_;
239 0         0 my $name = $p{name};
240              
241 0 0       0 if ( exists $self->{outputs}{$name} ) {
    0          
242 0         0 $self->{outputs}{$name}->log(@_);
243             }
244             elsif ($^W) {
245 0         0 Carp::carp(
246             "Log::Dispatch::* object named '$name' not in dispatcher\n");
247             }
248             }
249              
250             sub output {
251 17     17 1 4440 my $self = shift;
252 17         46 my $name = shift;
253              
254 17 100       56 return unless exists $self->{outputs}{$name};
255              
256 16         83 return $self->{outputs}{$name};
257             }
258              
259             sub would_log {
260 10     10 1 30 my $self = shift;
261 10         16 my $level = shift;
262              
263 10         30 my $level_num = $self->_level_as_number($level);
264 10 100       27 return 0 unless defined $level_num;
265              
266 9         21 return $self->_would_log($level_num);
267             }
268              
269             sub _would_log {
270 228     228   530 my $self = shift;
271 228         687 my $level_num = shift;
272              
273 228         515 for ( values %{ $self->{outputs} } ) {
  228         983  
274 228 100       2612 return 1 if $_->_should_log($level_num);
275             }
276              
277 93         307 return 0;
278             }
279              
280 1     1 0 8 sub is_debug { $_[0]->would_log('debug') }
281 0     0 0 0 sub is_info { $_[0]->would_log('info') }
282 0     0 0 0 sub is_notice { $_[0]->would_log('notice') }
283 1     1 0 8 sub is_warning { $_[0]->would_log('warning') }
284 0     0 0 0 sub is_warn { $_[0]->would_log('warn') }
285 0     0 0 0 sub is_error { $_[0]->would_log('error') }
286 0     0 0 0 sub is_err { $_[0]->would_log('err') }
287 0     0 0 0 sub is_critical { $_[0]->would_log('critical') }
288 1     1 0 5 sub is_crit { $_[0]->would_log('crit') }
289 0     0 0   sub is_alert { $_[0]->would_log('alert') }
290 0     0 0   sub is_emerg { $_[0]->would_log('emerg') }
291 0     0 0   sub is_emergency { $_[0]->would_log('emergency') }
292              
293             1;
294              
295             # ABSTRACT: Dispatches messages to one or more outputs
296              
297             __END__
298              
299             =pod
300              
301             =encoding UTF-8
302              
303             =head1 NAME
304              
305             Log::Dispatch - Dispatches messages to one or more outputs
306              
307             =head1 VERSION
308              
309             version 2.71
310              
311             =head1 SYNOPSIS
312              
313             use Log::Dispatch;
314              
315             # Simple API
316             #
317             my $log = Log::Dispatch->new(
318             outputs => [
319             [ 'File', min_level => 'debug', filename => 'logfile' ],
320             [ 'Screen', min_level => 'warning' ],
321             ],
322             );
323              
324             $log->info('Blah, blah');
325              
326             # More verbose API
327             #
328             my $log = Log::Dispatch->new();
329             $log->add(
330             Log::Dispatch::File->new(
331             name => 'file1',
332             min_level => 'debug',
333             filename => 'logfile'
334             )
335             );
336             $log->add(
337             Log::Dispatch::Screen->new(
338             name => 'screen',
339             min_level => 'warning',
340             )
341             );
342              
343             $log->log( level => 'info', message => 'Blah, blah' );
344              
345             my $sub = sub { my %p = @_; return reverse $p{message}; };
346             my $reversing_dispatcher = Log::Dispatch->new( callbacks => $sub );
347              
348             =head1 DESCRIPTION
349              
350             This module manages a set of Log::Dispatch::* output objects that can be logged
351             to via a unified interface.
352              
353             The idea is that you create a Log::Dispatch object and then add various logging
354             objects to it (such as a file logger or screen logger). Then you call the
355             C<log> method of the dispatch object, which passes the message to each of the
356             objects, which in turn decide whether or not to accept the message and what to
357             do with it.
358              
359             This makes it possible to call single method and send a message to a log file,
360             via email, to the screen, and anywhere else, all with very little code needed
361             on your part, once the dispatching object has been created.
362              
363             =head1 METHODS
364              
365             This class provides the following methods:
366              
367             =head2 Log::Dispatch->new(...)
368              
369             This method takes the following parameters:
370              
371             =over 4
372              
373             =item * outputs( [ [ class, params, ... ], [ class, params, ... ], ... ] )
374              
375             This parameter is a reference to a list of lists. Each inner list consists of a
376             class name and a set of constructor params. The class is automatically prefixed
377             with 'Log::Dispatch::' unless it begins with '+', in which case the string
378             following '+' is taken to be a full classname. e.g.
379              
380             outputs => [ [ 'File', min_level => 'debug', filename => 'logfile' ],
381             [ '+My::Dispatch', min_level => 'info' ] ]
382              
383             For each inner list, a new output object is created and added to the dispatcher
384             (via the C<add()> method).
385              
386             See L</"OUTPUT CLASSES"> for the parameters that can be used when creating an
387             output object.
388              
389             =item * callbacks( \& or [ \&, \&, ... ] )
390              
391             This parameter may be a single subroutine reference or an array reference of
392             subroutine references. These callbacks will be called in the order they are
393             given and passed a hash containing the following keys:
394              
395             ( message => $log_message, level => $log_level )
396              
397             In addition, any key/value pairs passed to a logging method will be passed onto
398             your callback.
399              
400             The callbacks are expected to modify the message and then return a single
401             scalar containing that modified message. These callbacks will be called when
402             either the C<log> or C<log_to> methods are called and will only be applied to a
403             given message once. If they do not return the message then you will get no
404             output. Make sure to return the message!
405              
406             =back
407              
408             =head2 $dispatch->clone()
409              
410             This returns a I<shallow> clone of the original object. The underlying output
411             objects and callbacks are shared between the two objects. However any changes
412             made to the outputs or callbacks that the object contains are not shared.
413              
414             =head2 $dispatch->log( level => $, message => $ or \& )
415              
416             Sends the message (at the appropriate level) to all the output objects that the
417             dispatcher contains (by calling the C<log_to> method repeatedly).
418              
419             The level can be specified by name or by an integer from 0 (debug) to 7
420             (emergency).
421              
422             This method also accepts a subroutine reference as the message argument. This
423             reference will be called only if there is an output that will accept a message
424             of the specified level.
425              
426             =head2 $dispatch->debug (message), info (message), ...
427              
428             You may call any valid log level (including valid abbreviations) as a method
429             with a single argument that is the message to be logged. This is converted into
430             a call to the C<log> method with the appropriate level.
431              
432             For example:
433              
434             $log->alert('Strange data in incoming request');
435              
436             translates to:
437              
438             $log->log( level => 'alert', message => 'Strange data in incoming request' );
439              
440             If you pass an array to these methods, it will be stringified as is:
441              
442             my @array = ('Something', 'bad', 'is', 'here');
443             $log->alert(@array);
444              
445             # is equivalent to
446              
447             $log->alert("@array");
448              
449             You can also pass a subroutine reference, just like passing one to the C<log()>
450             method.
451              
452             =head2 $dispatch->log_and_die( level => $, message => $ or \& )
453              
454             Has the same behavior as calling C<log()> but calls C<_die_with_message()> at
455             the end.
456              
457             You can throw exception objects by subclassing this method.
458              
459             If the C<carp_level> parameter is present its value will be added to the
460             current value of C<$Carp::CarpLevel>.
461              
462             =head2 $dispatch->log_and_croak( level => $, message => $ or \& )
463              
464             A synonym for C<$dispatch->log_and_die()>.
465              
466             =head2 $dispatch->log_to( name => $, level => $, message => $ )
467              
468             Sends the message only to the named object. Note: this will not properly handle
469             a subroutine reference as the message.
470              
471             =head2 $dispatch->add_callback( $code )
472              
473             Adds a callback (like those given during construction). It is added to the end
474             of the list of callbacks. Note that this can also be called on individual
475             output objects.
476              
477             =head2 $dispatch->remove_callback( $code )
478              
479             Remove the given callback from the list of callbacks. Note that this can also
480             be called on individual output objects.
481              
482             =head2 $dispatch->callbacks()
483              
484             Returns a list of the callbacks in a given output.
485              
486             =head2 $dispatch->level_is_valid( $string )
487              
488             Returns true or false to indicate whether or not the given string is a valid
489             log level. Can be called as either a class or object method.
490              
491             =head2 $dispatch->would_log( $string )
492              
493             Given a log level, returns true or false to indicate whether or not anything
494             would be logged for that log level.
495              
496             =head2 $dispatch->is_C<$level>
497              
498             There are methods for every log level: C<is_debug()>, C<is_warning()>, etc.
499              
500             This returns true if the logger will log a message at the given level.
501              
502             =head2 $dispatch->add( Log::Dispatch::* OBJECT )
503              
504             Adds a new L<output object|/"OUTPUT CLASSES"> to the dispatcher. If an object
505             of the same name already exists, then that object is replaced, with a warning
506             if C<$^W> is true.
507              
508             =head2 $dispatch->remove($)
509              
510             Removes the output object that matches the name given to the remove method. The
511             return value is the object being removed or undef if no object matched this.
512              
513             =head2 $dispatch->outputs()
514              
515             Returns a list of output objects.
516              
517             =head2 $dispatch->output( $name )
518              
519             Returns the output object of the given name. Returns undef or an empty list,
520             depending on context, if the given output does not exist.
521              
522             =head2 $dispatch->_die_with_message( message => $, carp_level => $ )
523              
524             This method is used by C<log_and_die> and will either die() or croak()
525             depending on the value of C<message>: if it's a reference or it ends with a new
526             line then a plain die will be used, otherwise it will croak.
527              
528             =head1 OUTPUT CLASSES
529              
530             An output class - e.g. L<Log::Dispatch::File> or L<Log::Dispatch::Screen> -
531             implements a particular way of dispatching logs. Many output classes come with
532             this distribution, and others are available separately on CPAN.
533              
534             The following common parameters can be used when creating an output class. All
535             are optional. Most output classes will have additional parameters beyond these,
536             see their documentation for details.
537              
538             =over 4
539              
540             =item * name ($)
541              
542             A name for the object (not the filename!). This is useful if you want to refer
543             to the object later, e.g. to log specifically to it or remove it.
544              
545             By default a unique name will be generated. You should not depend on the form
546             of generated names, as they may change.
547              
548             =item * min_level ($)
549              
550             The minimum L<logging level|/"LOG LEVELS"> this object will accept. Required.
551              
552             =item * max_level ($)
553              
554             The maximum L<logging level|/"LOG LEVELS"> this object will accept. By default
555             the maximum is the highest possible level (which means functionally that the
556             object has no maximum).
557              
558             =item * callbacks( \& or [ \&, \&, ... ] )
559              
560             This parameter may be a single subroutine reference or an array reference of
561             subroutine references. These callbacks will be called in the order they are
562             given and passed a hash containing the following keys:
563              
564             ( message => $log_message, level => $log_level )
565              
566             The callbacks are expected to modify the message and then return a single
567             scalar containing that modified message. These callbacks will be called when
568             either the C<log> or C<log_to> methods are called and will only be applied to a
569             given message once. If they do not return the message then you will get no
570             output. Make sure to return the message!
571              
572             =item * newline (0|1)
573              
574             If true, a callback will be added to the end of the callbacks list that adds a
575             newline to the end of each message. Default is false, but some output classes
576             may decide to make the default true.
577              
578             =back
579              
580             =head1 LOG LEVELS
581              
582             The log levels that Log::Dispatch uses are taken directly from the syslog man
583             pages (except that I expanded them to full words). Valid levels are:
584              
585             =over 4
586              
587             =item debug
588              
589             =item info
590              
591             =item notice
592              
593             =item warning
594              
595             =item error
596              
597             =item critical
598              
599             =item alert
600              
601             =item emergency
602              
603             =back
604              
605             Alternately, the numbers 0 through 7 may be used (debug is 0 and emergency is
606             7). The syslog standard of 'err', 'crit', and 'emerg' is also acceptable. We
607             also allow 'warn' as a synonym for 'warning'.
608              
609             =head1 SUBCLASSING
610              
611             This module was designed to be easy to subclass. If you want to handle
612             messaging in a way not implemented in this package, you should be able to add
613             this with minimal effort. It is generally as simple as subclassing
614             Log::Dispatch::Output and overriding the C<new> and C<log_message> methods. See
615             the L<Log::Dispatch::Output> docs for more details.
616              
617             If you would like to create your own subclass for sending email then it is even
618             simpler. Simply subclass L<Log::Dispatch::Email> and override the C<send_email>
619             method. See the L<Log::Dispatch::Email> docs for more details.
620              
621             The logging levels that Log::Dispatch uses are borrowed from the standard UNIX
622             syslog levels, except that where syslog uses partial words ("err")
623             Log::Dispatch also allows the use of the full word as well ("error").
624              
625             =head1 RELATED MODULES
626              
627             =head2 Log::Dispatch::DBI
628              
629             Written by Tatsuhiko Miyagawa. Log output to a database table.
630              
631             =head2 Log::Dispatch::FileRotate
632              
633             Written by Mark Pfeiffer. Rotates log files periodically as part of its usage.
634              
635             =head2 Log::Dispatch::File::Stamped
636              
637             Written by Eric Cholet. Stamps log files with date and time information.
638              
639             =head2 Log::Dispatch::Jabber
640              
641             Written by Aaron Straup Cope. Logs messages via Jabber.
642              
643             =head2 Log::Dispatch::Tk
644              
645             Written by Dominique Dumont. Logs messages to a Tk window.
646              
647             =head2 Log::Dispatch::Win32EventLog
648              
649             Written by Arthur Bergman. Logs messages to the Windows event log.
650              
651             =head2 Log::Log4perl
652              
653             An implementation of Java's log4j API in Perl. Log messages can be limited by
654             fine-grained controls, and if they end up being logged, both native Log4perl
655             and Log::Dispatch appenders can be used to perform the actual logging job.
656             Created by Mike Schilli and Kevin Goess.
657              
658             =head2 Log::Dispatch::Config
659              
660             Written by Tatsuhiko Miyagawa. Allows configuration of logging via a text file
661             similar (or so I'm told) to how it is done with log4j. Simpler than
662             Log::Log4perl.
663              
664             =head2 Log::Agent
665              
666             A very different API for doing many of the same things that Log::Dispatch does.
667             Originally written by Raphael Manfredi.
668              
669             =head1 SEE ALSO
670              
671             L<Log::Dispatch::ApacheLog>, L<Log::Dispatch::Email>,
672             L<Log::Dispatch::Email::MailSend>, L<Log::Dispatch::Email::MailSender>,
673             L<Log::Dispatch::Email::MailSendmail>, L<Log::Dispatch::Email::MIMELite>,
674             L<Log::Dispatch::File>, L<Log::Dispatch::File::Locked>,
675             L<Log::Dispatch::Handle>, L<Log::Dispatch::Output>, L<Log::Dispatch::Screen>,
676             L<Log::Dispatch::Syslog>
677              
678             =head1 SUPPORT
679              
680             Bugs may be submitted at L<https://github.com/houseabsolute/Log-Dispatch/issues>.
681              
682             =head1 SOURCE
683              
684             The source code repository for Log-Dispatch can be found at L<https://github.com/houseabsolute/Log-Dispatch>.
685              
686             =head1 DONATIONS
687              
688             If you'd like to thank me for the work I've done on this module, please
689             consider making a "donation" to me via PayPal. I spend a lot of free time
690             creating free software, and would appreciate any support you'd care to offer.
691              
692             Please note that B<I am not suggesting that you must do this> in order for me
693             to continue working on this particular software. I will continue to do so,
694             inasmuch as I have in the past, for as long as it interests me.
695              
696             Similarly, a donation made in this way will probably not make me work on this
697             software much more, unless I get so many donations that I can consider working
698             on free software full time (let's all have a chuckle at that together).
699              
700             To donate, log into PayPal and send money to autarch@urth.org, or use the
701             button at L<https://houseabsolute.com/foss-donations/>.
702              
703             =head1 AUTHOR
704              
705             Dave Rolsky <autarch@urth.org>
706              
707             =head1 CONTRIBUTORS
708              
709             =for stopwords Anirvan Chatterjee Carsten Grohmann Doug Bell Graham Knop Ollis Gregory Oschwald hartzell Joelle Maslak Johann Rolschewski Jonathan Swartz Karen Etheridge Kerin Millar Kivanc Yazan Konrad Bucheli Michael Schout Olaf Alders Olivier Mengué Rohan Carly Ross Attrill Salvador Fandiño Sergey Leschenko Slaven Rezic Steve Bertrand Whitney Jackson
710              
711             =over 4
712              
713             =item *
714              
715             Anirvan Chatterjee <anirvan@users.noreply.github.com>
716              
717             =item *
718              
719             Carsten Grohmann <mail@carstengrohmann.de>
720              
721             =item *
722              
723             Doug Bell <doug@preaction.me>
724              
725             =item *
726              
727             Graham Knop <haarg@haarg.org>
728              
729             =item *
730              
731             Graham Ollis <plicease@cpan.org>
732              
733             =item *
734              
735             Gregory Oschwald <goschwald@maxmind.com>
736              
737             =item *
738              
739             hartzell <hartzell@alerce.com>
740              
741             =item *
742              
743             Joelle Maslak <jmaslak@antelope.net>
744              
745             =item *
746              
747             Johann Rolschewski <jorol@cpan.org>
748              
749             =item *
750              
751             Jonathan Swartz <swartz@pobox.com>
752              
753             =item *
754              
755             Karen Etheridge <ether@cpan.org>
756              
757             =item *
758              
759             Kerin Millar <kfm@plushkava.net>
760              
761             =item *
762              
763             Kivanc Yazan <kivancyazan@gmail.com>
764              
765             =item *
766              
767             Konrad Bucheli <kb@open.ch>
768              
769             =item *
770              
771             Michael Schout <mschout@gkg.net>
772              
773             =item *
774              
775             Olaf Alders <olaf@wundersolutions.com>
776              
777             =item *
778              
779             Olivier Mengué <dolmen@cpan.org>
780              
781             =item *
782              
783             Rohan Carly <se456@rohan.id.au>
784              
785             =item *
786              
787             Ross Attrill <ross.attrill@gmail.com>
788              
789             =item *
790              
791             Salvador Fandiño <sfandino@yahoo.com>
792              
793             =item *
794              
795             Sergey Leschenko <sergle.ua@gmail.com>
796              
797             =item *
798              
799             Slaven Rezic <srezic@cpan.org>
800              
801             =item *
802              
803             Steve Bertrand <steveb@cpan.org>
804              
805             =item *
806              
807             Whitney Jackson <whitney.jackson@baml.com>
808              
809             =back
810              
811             =head1 COPYRIGHT AND LICENSE
812              
813             This software is Copyright (c) 2023 by Dave Rolsky.
814              
815             This is free software, licensed under:
816              
817             The Artistic License 2.0 (GPL Compatible)
818              
819             The full text of the license can be found in the
820             F<LICENSE> file included with this distribution.
821              
822             =cut