diff options
Diffstat (limited to 'lib/Log/Dispatch.pm')
-rw-r--r-- | lib/Log/Dispatch.pm | 755 |
1 files changed, 755 insertions, 0 deletions
diff --git a/lib/Log/Dispatch.pm b/lib/Log/Dispatch.pm new file mode 100644 index 0000000..a611d6b --- /dev/null +++ b/lib/Log/Dispatch.pm @@ -0,0 +1,755 @@ +package Log::Dispatch; + +use 5.006; + +use strict; +use warnings; + +our $VERSION = '2.45'; + +use base qw( Log::Dispatch::Base ); + +use Module::Runtime qw( use_package_optimistically ); +use Params::Validate 0.15 qw(validate_with ARRAYREF CODEREF); +use Carp (); + +our %LEVELS; + +BEGIN { + my %level_map = ( + ( + map { $_ => $_ } + qw( + debug + info + notice + warning + error + critical + alert + emergency + ) + ), + warn => 'warning', + err => 'error', + crit => 'critical', + emerg => 'emergency', + ); + + foreach my $l ( keys %level_map ) { + my $sub = sub { + my $self = shift; + $self->log( + level => $level_map{$l}, + message => @_ > 1 ? "@_" : $_[0], + ); + }; + + $LEVELS{$l} = 1; + + no strict 'refs'; + *{$l} = $sub; + } +} + +sub new { + my $proto = shift; + my $class = ref $proto || $proto; + + my %p = validate_with( + params => \@_, + spec => { + outputs => { type => ARRAYREF, optional => 1 }, + callbacks => { type => ARRAYREF | CODEREF, optional => 1 } + }, + allow_extra => 1, # for backward compatibility + ); + + my $self = bless {}, $class; + + my @cb = $self->_get_callbacks(%p); + $self->{callbacks} = \@cb if @cb; + + if ( my $outputs = $p{outputs} ) { + if ( ref $outputs->[1] eq 'HASH' ) { + + # 2.23 API + # outputs => [ + # File => { min_level => 'debug', filename => 'logfile' }, + # Screen => { min_level => 'warning' } + # ] + while ( my ( $class, $params ) = splice @$outputs, 0, 2 ) { + $self->_add_output( $class, %$params ); + } + } + else { + + # 2.24+ syntax + # outputs => [ + # [ 'File', min_level => 'debug', filename => 'logfile' ], + # [ 'Screen', min_level => 'warning' ] + # ] + foreach my $arr (@$outputs) { + die "expected arrayref, not '$arr'" + unless ref $arr eq 'ARRAY'; + $self->_add_output(@$arr); + } + } + } + + return $self; +} + +sub clone { + my $self = shift; + + my %clone = ( + callbacks => [ @{ $self->{callbacks} || [] } ], + outputs => { %{ $self->{outputs} || {} } }, + ); + + return bless \%clone, ref $self; +} + +sub _add_output { + my $self = shift; + my $class = shift; + + my $full_class + = substr( $class, 0, 1 ) eq '+' + ? substr( $class, 1 ) + : "Log::Dispatch::$class"; + + use_package_optimistically($full_class); + + $self->add( $full_class->new(@_) ); +} + +sub add { + my $self = shift; + my $object = shift; + + # Once 5.6 is more established start using the warnings module. + if ( exists $self->{outputs}{ $object->name } && $^W ) { + Carp::carp( + "Log::Dispatch::* object ", $object->name, + " already exists." + ); + } + + $self->{outputs}{ $object->name } = $object; +} + +sub remove { + my $self = shift; + my $name = shift; + + return delete $self->{outputs}{$name}; +} + +sub outputs { + my $self = shift; + + return values %{ $self->{outputs} }; +} + +sub callbacks { + my $self = shift; + + return @{ $self->{callbacks} }; +} + +sub log { + my $self = shift; + my %p = @_; + + return unless $self->would_log( $p{level} ); + + $self->_log_to_outputs( $self->_prepare_message(%p) ); +} + +sub _prepare_message { + my $self = shift; + my %p = @_; + + $p{message} = $p{message}->() + if ref $p{message} eq 'CODE'; + + $p{message} = $self->_apply_callbacks(%p) + if $self->{callbacks}; + + return %p; +} + +sub _log_to_outputs { + my $self = shift; + my %p = @_; + + foreach ( keys %{ $self->{outputs} } ) { + $p{name} = $_; + $self->_log_to(%p); + } +} + +sub log_and_die { + my $self = shift; + + my %p = $self->_prepare_message(@_); + + $self->_log_to_outputs(%p) if $self->would_log( $p{level} ); + + $self->_die_with_message(%p); +} + +sub log_and_croak { + my $self = shift; + + $self->log_and_die( @_, carp_level => 3 ); +} + +sub _die_with_message { + my $self = shift; + my %p = @_; + + my $msg = $p{message}; + + local $Carp::CarpLevel = ( $Carp::CarpLevel || 0 ) + $p{carp_level} + if exists $p{carp_level}; + + Carp::croak($msg); +} + +sub log_to { + my $self = shift; + my %p = @_; + + $p{message} = $self->_apply_callbacks(%p) + if $self->{callbacks}; + + $self->_log_to(%p); +} + +sub _log_to { + my $self = shift; + my %p = @_; + my $name = $p{name}; + + if ( exists $self->{outputs}{$name} ) { + $self->{outputs}{$name}->log(@_); + } + elsif ($^W) { + Carp::carp( + "Log::Dispatch::* object named '$name' not in dispatcher\n"); + } +} + +sub output { + my $self = shift; + my $name = shift; + + return unless exists $self->{outputs}{$name}; + + return $self->{outputs}{$name}; +} + +sub level_is_valid { + shift; + my $level = shift + or Carp::croak('Logging level was not provided'); + + return $LEVELS{$level}; +} + +sub would_log { + my $self = shift; + my $level = shift; + + return 0 unless $self->level_is_valid($level); + + foreach ( values %{ $self->{outputs} } ) { + return 1 if $_->_should_log($level); + } + + return 0; +} + +sub is_debug { $_[0]->would_log('debug') } +sub is_info { $_[0]->would_log('info') } +sub is_notice { $_[0]->would_log('notice') } +sub is_warning { $_[0]->would_log('warning') } +sub is_warn { $_[0]->would_log('warn') } +sub is_error { $_[0]->would_log('error') } +sub is_err { $_[0]->would_log('err') } +sub is_critical { $_[0]->would_log('critical') } +sub is_crit { $_[0]->would_log('crit') } +sub is_alert { $_[0]->would_log('alert') } +sub is_emerg { $_[0]->would_log('emerg') } +sub is_emergency { $_[0]->would_log('emergency') } + +1; + +# ABSTRACT: Dispatches messages to one or more outputs + +__END__ + +=pod + +=head1 NAME + +Log::Dispatch - Dispatches messages to one or more outputs + +=head1 VERSION + +version 2.45 + +=head1 SYNOPSIS + + use Log::Dispatch; + + # Simple API + # + my $log = Log::Dispatch->new( + outputs => [ + [ 'File', min_level => 'debug', filename => 'logfile' ], + [ 'Screen', min_level => 'warning' ], + ], + ); + + $log->info('Blah, blah'); + + # More verbose API + # + my $log = Log::Dispatch->new(); + $log->add( + Log::Dispatch::File->new( + name => 'file1', + min_level => 'debug', + filename => 'logfile' + ) + ); + $log->add( + Log::Dispatch::Screen->new( + name => 'screen', + min_level => 'warning', + ) + ); + + $log->log( level => 'info', message => 'Blah, blah' ); + + my $sub = sub { my %p = @_; return reverse $p{message}; }; + my $reversing_dispatcher = Log::Dispatch->new( callbacks => $sub ); + +=head1 DESCRIPTION + +This module manages a set of Log::Dispatch::* output objects that can be +logged to via a unified interface. + +The idea is that you create a Log::Dispatch object and then add various +logging objects to it (such as a file logger or screen logger). Then you +call the C<log> method of the dispatch object, which passes the message to +each of the objects, which in turn decide whether or not to accept the +message and what to do with it. + +This makes it possible to call single method and send a message to a +log file, via email, to the screen, and anywhere else, all with very +little code needed on your part, once the dispatching object has been +created. + +=encoding UTF-8 + +=head1 METHODS + +This class provides the following methods: + +=head2 Log::Dispatch->new(...) + +This method takes the following parameters: + +=over 4 + +=item * outputs( [ [ class, params, ... ], [ class, params, ... ], ... ] ) + +This parameter is a reference to a list of lists. Each inner list consists of +a class name and a set of constructor params. The class is automatically +prefixed with 'Log::Dispatch::' unless it begins with '+', in which case the +string following '+' is taken to be a full classname. e.g. + + outputs => [ [ 'File', min_level => 'debug', filename => 'logfile' ], + [ '+My::Dispatch', min_level => 'info' ] ] + +For each inner list, a new output object is created and added to the +dispatcher (via the C<add()> method). + +See L<OUTPUT CLASSES> for the parameters that can be used when creating an +output object. + +=item * callbacks( \& or [ \&, \&, ... ] ) + +This parameter may be a single subroutine reference or an array +reference of subroutine references. These callbacks will be called in +the order they are given and passed a hash containing the following keys: + + ( message => $log_message, level => $log_level ) + +In addition, any key/value pairs passed to a logging method will be +passed onto your callback. + +The callbacks are expected to modify the message and then return a +single scalar containing that modified message. These callbacks will +be called when either the C<log> or C<log_to> methods are called and +will only be applied to a given message once. If they do not return +the message then you will get no output. Make sure to return the +message! + +=back + +=head2 $dispatch->clone() + +This returns a I<shallow> clone of the original object. The underlying output +objects and callbacks are shared between the two objects. However any changes +made to the outputs or callbacks that the object contains are not shared. + +=head2 $dispatch->log( level => $, message => $ or \& ) + +Sends the message (at the appropriate level) to all the +output objects that the dispatcher contains (by calling the +C<log_to> method repeatedly). + +This method also accepts a subroutine reference as the message +argument. This reference will be called only if there is an output +that will accept a message of the specified level. + +=head2 $dispatch->debug (message), info (message), ... + +You may call any valid log level (including valid abbreviations) as a method +with a single argument that is the message to be logged. This is converted +into a call to the C<log> method with the appropriate level. + +For example: + + $log->alert('Strange data in incoming request'); + +translates to: + + $log->log( level => 'alert', message => 'Strange data in incoming request' ); + +If you pass an array to these methods, it will be stringified as is: + + my @array = ('Something', 'bad', 'is', 'here'); + $log->alert(@array); + + # is equivalent to + + $log->alert("@array"); + +You can also pass a subroutine reference, just like passing one to the +C<log()> method. + +=head2 $dispatch->log_and_die( level => $, message => $ or \& ) + +Has the same behavior as calling C<log()> but calls +C<_die_with_message()> at the end. + +=head2 $dispatch->log_and_croak( level => $, message => $ or \& ) + +This method adjusts the C<$Carp::CarpLevel> scalar so that the croak +comes from the context in which it is called. + +You can throw exception objects by subclassing this method. + +If the C<carp_level> parameter is present its value will be added to +the current value of C<$Carp::CarpLevel>. + +=head2 $dispatch->log_to( name => $, level => $, message => $ ) + +Sends the message only to the named object. Note: this will not properly +handle a subroutine reference as the message. + +=head2 $dispatch->add_callback( $code ) + +Adds a callback (like those given during construction). It is added to the end +of the list of callbacks. Note that this can also be called on individual +output objects. + +=head2 $dispatch->callbacks() + +Returns a list of the callbacks in a given output. + +=head2 $dispatch->level_is_valid( $string ) + +Returns true or false to indicate whether or not the given string is a +valid log level. Can be called as either a class or object method. + +=head2 $dispatch->would_log( $string ) + +Given a log level, returns true or false to indicate whether or not +anything would be logged for that log level. + +=head2 $dispatch->is_C<$level> + +There are methods for every log level: C<is_debug()>, C<is_warning()>, etc. + +This returns true if the logger will log a message at the given level. + +=head2 $dispatch->add( Log::Dispatch::* OBJECT ) + +Adds a new L<output object|OUTPUT CLASSES> to the dispatcher. If an object +of the same name already exists, then that object is replaced, with +a warning if C<$^W> is true. + +=head2 $dispatch->remove($) + +Removes the object that matches the name given to the remove method. +The return value is the object being removed or undef if no object +matched this. + +=head2 $dispatch->outputs() + +Returns a list of output objects. + +=head2 $dispatch->output( $name ) + +Returns the output object of the given name. Returns undef or an empty +list, depending on context, if the given output does not exist. + +=head2 $dispatch->_die_with_message( message => $, carp_level => $ ) + +This method is used by C<log_and_die> and will either die() or croak() +depending on the value of C<message>: if it's a reference or it ends +with a new line then a plain die will be used, otherwise it will +croak. + +=head1 OUTPUT CLASSES + +An output class - e.g. L<Log::Dispatch::File> or +L<Log::Dispatch::Screen> - implements a particular way +of dispatching logs. Many output classes come with this distribution, +and others are available separately on CPAN. + +The following common parameters can be used when creating an output class. +All are optional. Most output classes will have additional parameters beyond +these, see their documentation for details. + +=over 4 + +=item * name ($) + +A name for the object (not the filename!). This is useful if you want to +refer to the object later, e.g. to log specifically to it or remove it. + +By default a unique name will be generated. You should not depend on the +form of generated names, as they may change. + +=item * min_level ($) + +The minimum L<logging level|LOG LEVELS> this object will accept. Required. + +=item * max_level ($) + +The maximum L<logging level|LOG LEVELS> this object will accept. By default +the maximum is the highest possible level (which means functionally that the +object has no maximum). + +=item * callbacks( \& or [ \&, \&, ... ] ) + +This parameter may be a single subroutine reference or an array +reference of subroutine references. These callbacks will be called in +the order they are given and passed a hash containing the following keys: + + ( message => $log_message, level => $log_level ) + +The callbacks are expected to modify the message and then return a +single scalar containing that modified message. These callbacks will +be called when either the C<log> or C<log_to> methods are called and +will only be applied to a given message once. If they do not return +the message then you will get no output. Make sure to return the +message! + +=item * newline (0|1) + +If true, a callback will be added to the end of the callbacks list that adds +a newline to the end of each message. Default is false, but some +output classes may decide to make the default true. + +=back + +=head1 LOG LEVELS + +The log levels that Log::Dispatch uses are taken directly from the +syslog man pages (except that I expanded them to full words). Valid +levels are: + +=over 4 + +=item debug + +=item info + +=item notice + +=item warning + +=item error + +=item critical + +=item alert + +=item emergency + +=back + +Alternately, the numbers 0 through 7 may be used (debug is 0 and emergency is +7). The syslog standard of 'err', 'crit', and 'emerg' is also acceptable. We +also allow 'warn' as a synonym for 'warning'. + +=head1 SUBCLASSING + +This module was designed to be easy to subclass. If you want to handle +messaging in a way not implemented in this package, you should be able to add +this with minimal effort. It is generally as simple as subclassing +Log::Dispatch::Output and overriding the C<new> and C<log_message> +methods. See the L<Log::Dispatch::Output> docs for more details. + +If you would like to create your own subclass for sending email then +it is even simpler. Simply subclass L<Log::Dispatch::Email> and +override the C<send_email> method. See the L<Log::Dispatch::Email> +docs for more details. + +The logging levels that Log::Dispatch uses are borrowed from the standard +UNIX syslog levels, except that where syslog uses partial words ("err") +Log::Dispatch also allows the use of the full word as well ("error"). + +=head1 RELATED MODULES + +=head2 Log::Dispatch::DBI + +Written by Tatsuhiko Miyagawa. Log output to a database table. + +=head2 Log::Dispatch::FileRotate + +Written by Mark Pfeiffer. Rotates log files periodically as part of +its usage. + +=head2 Log::Dispatch::File::Stamped + +Written by Eric Cholet. Stamps log files with date and time +information. + +=head2 Log::Dispatch::Jabber + +Written by Aaron Straup Cope. Logs messages via Jabber. + +=head2 Log::Dispatch::Tk + +Written by Dominique Dumont. Logs messages to a Tk window. + +=head2 Log::Dispatch::Win32EventLog + +Written by Arthur Bergman. Logs messages to the Windows event log. + +=head2 Log::Log4perl + +An implementation of Java's log4j API in Perl. Log messages can be limited by +fine-grained controls, and if they end up being logged, both native Log4perl +and Log::Dispatch appenders can be used to perform the actual logging +job. Created by Mike Schilli and Kevin Goess. + +=head2 Log::Dispatch::Config + +Written by Tatsuhiko Miyagawa. Allows configuration of logging via a +text file similar (or so I'm told) to how it is done with log4j. +Simpler than Log::Log4perl. + +=head2 Log::Agent + +A very different API for doing many of the same things that +Log::Dispatch does. Originally written by Raphael Manfredi. + +=head1 SUPPORT + +Please submit bugs and patches to the CPAN RT system at +http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Log%3A%3ADispatch +or via email at bug-log-dispatch@rt.cpan.org. + +Support questions can be sent to me at my email address, shown below. + +=head1 DONATIONS + +If you'd like to thank me for the work I've done on this module, +please consider making a "donation" to me via PayPal. I spend a lot of +free time creating free software, and would appreciate any support +you'd care to offer. + +Please note that B<I am not suggesting that you must do this> in order +for me to continue working on this particular software. I will +continue to do so, inasmuch as I have in the past, for as long as it +interests me. + +Similarly, a donation made in this way will probably not make me work +on this software much more, unless I get so many donations that I can +consider working on free software full time, which seems unlikely at +best. + +To donate, log into PayPal and send money to autarch@urth.org or use +the button on this page: +L<http://www.urth.org/~autarch/fs-donation.html> + +=head1 SEE ALSO + +L<Log::Dispatch::ApacheLog>, L<Log::Dispatch::Email>, +L<Log::Dispatch::Email::MailSend>, L<Log::Dispatch::Email::MailSender>, +L<Log::Dispatch::Email::MailSendmail>, L<Log::Dispatch::Email::MIMELite>, +L<Log::Dispatch::File>, L<Log::Dispatch::File::Locked>, +L<Log::Dispatch::Handle>, L<Log::Dispatch::Output>, L<Log::Dispatch::Screen>, +L<Log::Dispatch::Syslog> + +=head1 AUTHOR + +Dave Rolsky <autarch@urth.org> + +=head1 CONTRIBUTORS + +=for stopwords Karen Etheridge Olaf Alders Olivier Mengué Ross Attrill swartz@jonathan-swartzs-macbook-4.local swartz@pobox.com Whitney Jackson + +=over 4 + +=item * + +Karen Etheridge <ether@cpan.org> + +=item * + +Olaf Alders <olaf@wundersolutions.com> + +=item * + +Olivier Mengué <dolmen@cpan.org> + +=item * + +Ross Attrill <ross.attrill@gmail.com> + +=item * + +swartz@jonathan-swartzs-macbook-4.local <swartz@jonathan-swartzs-macbook-4.local> + +=item * + +swartz@pobox.com <swartz@pobox.com> + +=item * + +Whitney Jackson <whitney.jackson@baml.com> + +=back + +=head1 COPYRIGHT AND LICENSE + +This software is Copyright (c) 2015 by Dave Rolsky. + +This is free software, licensed under: + + The Artistic License 2.0 (GPL Compatible) + +=cut |