—package
Catalyst::Log;
use
Moose;
use
Data::Dump;
our
%LEVELS
= ();
# Levels stored as bit field, ergo debug = 1, warn = 2 etc
our
%LEVEL_MATCH
= ();
# Stored as additive, thus debug = 31, warn = 30 etc
has
level
=> (
is
=>
'rw'
);
has
_body
=> (
is
=>
'rw'
);
has
abort
=> (
is
=>
'rw'
);
has
autoflush
=> (
is
=>
'rw'
,
default
=>
sub
{1});
has
_psgi_logger
=> (
is
=>
'rw'
,
predicate
=>
'_has_psgi_logger'
,
clearer
=>
'_clear_psgi_logger'
);
has
_psgi_errors
=> (
is
=>
'rw'
,
predicate
=>
'_has_psgi_errors'
,
clearer
=>
'_clear_psgi_errors'
);
sub
clear_psgi {
my
$self
=
shift
;
$self
->_clear_psgi_logger;
$self
->_clear_psgi_errors;
}
sub
psgienv {
my
(
$self
,
$env
) =
@_
;
$self
->_psgi_logger(
$env
->{
'psgix.logger'
})
if
$env
->{
'psgix.logger'
};
$self
->_psgi_errors(
$env
->{
'psgi.errors'
})
if
$env
->{
'psgi.errors'
};
}
{
my
@levels
=
qw[ debug info warn error fatal ]
;
my
$meta
= find_meta(__PACKAGE__);
my
$summed_level
= 0;
for
(
my
$i
=
$#levels
;
$i
>= 0 ;
$i
-- ) {
my
$name
=
$levels
[
$i
];
my
$level
= 1 <<
$i
;
$summed_level
|=
$level
;
$LEVELS
{
$name
} =
$level
;
$LEVEL_MATCH
{
$name
} =
$summed_level
;
$meta
->add_method(
$name
,
sub
{
my
$self
=
shift
;
if
(
$self
->level &
$level
) {
$self
->_log(
$name
,
@_
);
}
});
$meta
->add_method(
"is_$name"
,
sub
{
my
$self
=
shift
;
return
$self
->level &
$level
;
});;
}
}
around
new
=>
sub
{
my
$orig
=
shift
;
my
$class
=
shift
;
my
$self
=
$class
->
$orig
;
$self
->levels(
scalar
(
@_
) ?
@_
:
keys
%LEVELS
);
return
$self
;
};
sub
levels {
my
(
$self
,
@levels
) =
@_
;
$self
->level(0);
$self
->enable(
@levels
);
}
sub
enable {
my
(
$self
,
@levels
) =
@_
;
my
$level
=
$self
->level;
for
(
map
{
$LEVEL_MATCH
{
$_
} }
@levels
){
$level
|=
$_
;
}
$self
->level(
$level
);
}
sub
disable {
my
(
$self
,
@levels
) =
@_
;
my
$level
=
$self
->level;
for
(
map
{
$LEVELS
{
$_
} }
@levels
){
$level
&= ~
$_
;
}
$self
->level(
$level
);
}
our
$HAS_DUMPED
;
sub
_dump {
my
$self
=
shift
;
unless
(
$HAS_DUMPED
++) {
cluck(
"Catalyst::Log::_dump is deprecated and will be removed. Please change to using your own Dumper.\n"
);
}
$self
->info( Data::Dump::
dump
(
@_
) );
}
sub
_log {
my
$self
=
shift
;
my
$level
=
shift
;
my
$message
=
join
(
"\n"
,
@_
);
if
(
$self
->can(
'_has_psgi_logger'
) and
$self
->_has_psgi_logger) {
$self
->_psgi_logger->({
level
=>
$level
,
message
=>
$message
,
});
}
else
{
$message
.=
"\n"
unless
$message
=~ /\n$/;
my
$body
=
$self
->_body;
$body
.=
sprintf
(
"[%s] %s"
,
$level
,
$message
);
$self
->_body(
$body
);
}
if
(
$self
->autoflush && !
$self
->abort ) {
$self
->_flush;
}
return
1;
}
sub
_flush {
my
$self
=
shift
;
if
(
$self
->abort || !
$self
->_body ) {
$self
->abort(
undef
);
}
else
{
$self
->_send_to_log(
$self
->_body );
}
$self
->_body(
undef
);
}
sub
_send_to_log {
my
$self
=
shift
;
if
(
$self
->can(
'_has_psgi_errors'
) and
$self
->_has_psgi_errors) {
$self
->_psgi_errors->
(
@_
);
}
else
{
binmode
STDERR,
":utf8"
;
STDERR
@_
;
}
}
# 5.7 compat code.
# Alias _body to body, add a before modifier to warn..
my
$meta
= __PACKAGE__->meta;
# Calling meta method here fine as we happen at compile time.
$meta
->add_method(
'body'
,
$meta
->get_method(
'_body'
));
my
%package_hash
;
# Only warn once per method, per package.
# I haven't provided a way to disable them, patches welcome.
$meta
->add_before_method_modifier(
'body'
,
sub
{
my
$class
= blessed(
shift
);
$package_hash
{
$class
}++ ||
do
{
warn
(
"Class $class is calling the deprecated method Catalyst::Log->body method,\n"
.
"this will be removed in Catalyst 5.81"
);
};
});
# End 5.70 backwards compatibility hacks.
no
Moose;
__PACKAGE__->meta->make_immutable(
inline_constructor
=> 0);
1;
__END__
=for stopwords psgienv
=head1 NAME
Catalyst::Log - Catalyst Log Class
=head1 SYNOPSIS
$log = $c->log;
$log->debug($message);
$log->info($message);
$log->warn($message);
$log->error($message);
$log->fatal($message);
if ( $log->is_debug ) {
# expensive debugging
}
See L<Catalyst>.
=head1 DESCRIPTION
This module provides the default, simple logging functionality for Catalyst.
If you want something different set C<< $c->log >> in your application module,
e.g.:
$c->log( MyLogger->new );
Your logging object is expected to provide the interface described here.
Good alternatives to consider are Log::Log4Perl and Log::Dispatch.
If you want to be able to log arbitrary warnings, you can do something along
the lines of
$SIG{__WARN__} = sub { MyApp->log->warn(@_); };
however this is (a) global, (b) hairy and (c) may have unexpected side effects.
Don't say we didn't warn you.
=head1 LOG LEVELS
=head2 debug
$log->is_debug;
$log->debug($message);
=head2 info
$log->is_info;
$log->info($message);
=head2 warn
$log->is_warn;
$log->warn($message);
=head2 error
$log->is_error;
$log->error($message);
=head2 fatal
$log->is_fatal;
$log->fatal($message);
=head1 METHODS
=head2 new
Constructor. Defaults to enable all levels unless levels are provided in
arguments.
$log = Catalyst::Log->new;
$log = Catalyst::Log->new( 'warn', 'error' );
=head2 level
Contains a bitmask of the currently set log levels.
=head2 levels
Set log levels
$log->levels( 'warn', 'error', 'fatal' );
=head2 enable
Enable log levels
$log->enable( 'warn', 'error' );
=head2 disable
Disable log levels
$log->disable( 'warn', 'error' );
=head2 is_debug
=head2 is_error
=head2 is_fatal
=head2 is_info
=head2 is_warn
Is the log level active?
=head2 abort
Should Catalyst emit logs for this request? Will be reset at the end of
each request.
*NOTE* This method is not compatible with other log apis, so if you plan
to use Log4Perl or another logger, you should call it like this:
$c->log->abort(1) if $c->log->can('abort');
=head2 autoflush
When enabled (default), messages are written to the log immediately instead
of queued until the end of the request.
This option, as well as C<abort>, is provided for modules such as
L<Catalyst::Plugin::Static::Simple> to be able to programmatically
suppress the output of log messages. By turning off C<autoflush> (application-wide
setting) and then setting the C<abort> flag within a given request, all log
messages for the given request will be suppressed. C<abort> can still be set
independently of turning off C<autoflush>, however. It just means any messages
sent to the log up until that point in the request will obviously still be emitted,
since C<autoflush> means they are written in real-time.
If you need to turn off autoflush you should do it like this (in your main app
class):
after setup_finalize => sub {
my $c = shift;
$c->log->autoflush(0) if $c->log->can('autoflush');
};
=head2 _send_to_log
$log->_send_to_log( @messages );
This protected method is what actually sends the log information to STDERR.
You may subclass this module and override this method to get finer control
over the log output.
=head2 psgienv $env
$log->psgienv($env);
NOTE: This is not meant for public consumption.
Set the PSGI environment for this request. This ensures logs will be sent to
the right place. If the environment has a C<psgix.logger>, it will be used. If
not, we will send logs to C<psgi.errors> if that exists. As a last fallback, we
will send to STDERR as before.
=head2 clear_psgi
Clears the PSGI environment attributes set by L</psgienv>.
=head2 meta
=head1 SEE ALSO
L<Catalyst>.
=head1 AUTHORS
Catalyst Contributors, see Catalyst.pm
=head1 COPYRIGHT
This library is free software. You can redistribute it and/or modify
it under the same terms as Perl itself.
=cut
__PACKAGE__->meta->make_immutable;
1;