=encoding utf8
=head1 NAME
Dancer2::Plugin::LogReport - logging and exceptions via Log::Report
=head1 SYNOPSIS
$name
or error
"Please enter a name"
;
trace
"We're here"
;
if
(process(
sub
{MyApp::Model->create_user} )) {
}
else
{
}
hook
before_template
=>
sub
{
my
$tokens
=
shift
;
$tokens
->{messages} = session
'messages'
;
session
'messages'
=> [];
}
=head1 DESCRIPTION
[The Dancer2 plugin was contributed by Andrew Beverley]
This module provides easy access to the extensive logging facilities
provided by L<Log::Report|Log::Report>. Along
with
L<Dancer2::Logger::LogReport|Dancer2::Logger::LogReport>,
this brings together all the internal Dancer2 logging, handling
for
expected and unexpected exceptions, translations and application logging.
Logging is extremely flexible using many of the available
L<dispatchers|Log::Report::Dispatcher/DETAILS>. Multiple dispatchers can be
used,
each
configured separately to display different messages in different
formats. By
default
, messages are logged to a session variable
for
display on
a webpage, and to STDERR.
Messages within this plugin
use
the extended
L<Dancer2::Logger::LogReport::Message> class rather than the standard
L<Log::Report::Message> class.
Note that it is currently recommended to
use
the plugin in all apps within
a Dancer2 program, not only some. Therefore, wherever you C<
use
Dancer2>
using the same app name (C<
use
Dancer2 appname,
'Already::Exists'
>). In
Read the L</DETAILS> in below in this manual-page.
=head1 METHODS
=over 4
=item
$obj
-E<gt>B<fatal_handler>()
C<fatal_handler()> allows alternative handlers to be
defined
in place of (or in
addition to) the
default
redirect handler that is called on a fatal error.
Calls should be made
with
1 parameter: the subroutine to call in the case of a
fatal error. The subroutine is passed 3 parameters: the DSL, the message in
question, and the reason. The subroutine should
return
true or false depending
on whether it handled the error. If it returns false, the
next
fatal handler is
called, and
if
there are
no
others then the
default
redirect fatal handler is
called.
example: Error handler based on URL (e.g. API)
fatal_handler
sub
{
my
(
$dsl
,
$msg
,
$reason
) =
@_
;
return
if
$dsl
->app->request->uri !~ m!^/api/!;
status
$reason
eq
'PANIC'
?
'Internal Server Error'
:
'Bad Request'
;
$dsl
->send_as(
JSON
=> {
error
=> 1,
error_description
=>
$msg
->toString,
}, {
content_type
=>
'application/json; charset=UTF-8'
,
});
};
example: Return JSON responses
for
requests
with
content-type of application/json
fatal_handler
sub
{
my
(
$dsl
,
$msg
,
$reason
,
$default
) =
@_
;
(
my
$ctype
=
$dsl
->request->header(
'content-type'
)) =~ s/;.*//;
return
if
$ctype
ne
'application/json'
;
status
$reason
eq
'PANIC'
?
'Internal Server Error'
:
'Bad Request'
;
$dsl
->send_as(
JSON
=> {
error
=> 1,
description
=>
$msg
->toString,
}, {
content_type
=>
'application/json; charset=UTF-8'
,
});
};
=item
$obj
-E<gt>B<process>()
C<process()> is an
eval
, but one which expects and understands exceptions
generated by L<Log::Report|Log::Report>. Any messages will be logged as normal in
accordance
with
the dispatchers, but any fatal exceptions will be caught
and handled gracefully. This allows much simpler error handling, rather
than needing to test
for
lots of different scenarios.
In a module, it is enough to simply
use
the C<error> keyword in the event
of a fatal error.
The
return
value will be 1
for
success or 0
if
a fatal exception occurred.
See the L</DETAILS>
for
an example of how this is expected to be used.
This module is configured only once in your application. The other modules
which make your website
do
not need to
require
this plugin, instead they
can C<
use
Log::Report> to get useful functions like error and fault.
=back
=head2 Handlers
All the standard L<Log::Report|Log::Report> functions are available to
use
. Please see the
L<Log::Report/
"The Reason for the report"
>
for
details
of
when
each
one should be used.
L<Log::Report class functionality|Log::Report::Message.pod
to class messages (which can then be tested later):
notice __x
"Class me up"
,
_class
=>
'label'
;
...
if
(
$msg
->inClass(
'label'
)) ...
L<Dancer2::Plugin::LogReport|Dancer2::Plugin::LogReport>
has
a special message class, C<no_session>,
which prevents the message from being saved to the messages session
variable. This is useful,
for
example,
if
you are writing messages within
the session hooks, in which case recursive loops can be experienced.
=over 4
=item
$obj
-E<gt>B<alert>()
=item
$obj
-E<gt>B<assert>()
=item
$obj
-E<gt>B<error>()
=item
$obj
-E<gt>B<failure>()
=item
$obj
-E<gt>B<fault>()
=item
$obj
-E<gt>B<info>()
=item
$obj
-E<gt>B<mistake>()
=item
$obj
-E<gt>B<notice>()
=item
$obj
-E<gt>B<panic>()
=item
$obj
-E<gt>B<success>()
This is a special additional type, equivalent to C<notice>. The difference is
that messages using this keyword will have the class C<success> added, which
can be used to color the messages differently to the end user. For example,
L<Dancer2::Plugin::LogReport::Message
message in green.
=item
$obj
-E<gt>B<trace>()
=item
$obj
-E<gt>B<warning>()
=back
=head1 DETAILS
This chapter will guide you through the myriad of ways that you can
use
L<Log::Report|Log::Report> in your Dancer2 application.
We will set up
our
application to
do
the following:
=over 4
=item Messages to the user
We
'll look at an easy way to output messages to the user'
s web page, whether
they be informational messages, warnings or errors.
=item Debug information
We'll look at an easy way to
log
debug information, at different levels.
=item Manage unexpected exceptions
We'll handle unexpected exceptions cleanly, in the unfortunate event that
they happen in your production application.
=item Email alerts of significant errors
If we
do
get unexpected errors then we want to be notified them.
=item Log DBIC information and errors
We'll specifically look at nice ways to
log
SQL queries and errors
when
using DBIx::Class.
=back
=head2 Larger example
In its simplest form, this module can be used
for
more flexible logging
get
'/route'
=>
sub
{
$name
or error
"Please enter a name"
;
$name
or error __
"Please enter a name"
;
$name
or error __x
"{name} is not valid"
,
name
=>
$name
;
mistake
"Not sure that's what you wanted"
;
trace
"Hello world"
;
};
=head2 Setup and Configuration
To make full
use
of L<Log::Report>, you'll need to
use
both
L<Dancer2::Logger::LogReport> and L<Dancer2::Plugin::LogReport>.
=head3 Dancer2::Logger::LogReport
Set up L<Dancer2::Logger::LogReport> by adding it to your Dancer2
application configuration (see L<Dancer2::Config>). By
default
,
all messages will go to STDERR.
To get all message out
"the Perl way"
(using
print
,
warn
and
die
) just
use
logger:
"LogReport"
At start, these are handled by a L<Log::Report::Dispatcher::Perl|Log::Report::Dispatcher::Perl> object,
named
'default'
. If you
open
a new dispatcher
with
the name
'default'
,
the output via the perl mechanisms will be stopped.
To also
send
messages to your syslog:
logger:
"LogReport"
engines:
logger:
LogReport:
log_format:
%a
%i
%m
app_name: MyApp
dispatchers:
default
:
type: SYSLOG
identity: myapp
facility: local0
flags:
"pid ndelay nowait"
mode: DEBUG
To
send
messages to a file:
logger:
"LogReport"
engines:
logger:
LogReport:
log_format:
%a
%i
%m
app_name: MyApp
dispatchers:
logfile:
type: FILE
to: /var/
log
/myapp.
log
charset: utf-8
mode: DEBUG
See L<Log::Report::Dispatcher>
for
full details of options.
Finally: a Dancer2 script may run many applications. Each application
can have its own logger configuration. However, Log::Report dispatchers
are global, so will be shared between Dancer2 applications. Any attempt
to create a new Log::Report dispatcher by the same name (as will happen
when
a new Dancer2 application is started
with
the same configuration)
will be ignored.
=head3 Dancer2::Plugin::LogReport
To
use
the plugin, you simply
use
it in your application:
Dancer2::Plugin::LogReport takes the same C<
%config
> options as
L<Log::Report> itself (see L<Log::Report::
import
()|Log::Report/
"Configuration"
>).
If you want to
send
messages from your modules/models, there is
no
need to
use
this specific plugin. Instead, you should simply
C<
use
Log::Report> to negate the need of loading all the Dancer2
specific code.
=head2 In
use
=head3 Logging debug information
In its simplest form, you can now
use
all the
L<Log::Report logging functions|Log::Report
to
send
messages to your dispatchers (as configured in the Logger
configuration):
trace
"I'm here"
;
warning
"Something dodgy happened"
;
panic
"I'm bailing out"
;
success
"Settings saved successfully"
;
=head3 Exceptions
Log::Report is a combination of a logger and an exception
system
. Messages
to be logged are I<thrown> to all listening dispatchers to be handled.
This module will also
catch
any unexpected exceptions:
get
'route'
=>
sub
{
my
$foo
= 1;
my
$bar
=
$foo
->{x};
}
For a production application (C<show_errors: 1>), the message saved in the
session will be the generic text
"An unexpected error has occurred"
. This
can be customised in the configuration file, and will be translated.
=head3 Sending messages to the user
To make it easier to
send
messages to your users, messages at the following
levels are also stored in the user's session: C<notice>, C<warning>, C<mistake>,
C<error>, C<fault>, C<alert>, C<failure> and C<panic>.
You can pass these to your template and display them at
each
page render:
hook
before_template
=>
sub
{
my
$tokens
=
shift
;
$tokens
->{messages} = session
'messages'
;
session
'messages'
=> [];
}
Then in your template (
for
example the main layout):
[% FOR message IN messages %]
<div class=
"alert alert-[% message.bootstrap_color %]"
>
[% message.toString | html_entity %]
</div>
[% END %]
The C<bootstrap_color> of the message is compatible
with
Bootstrap contextual
colors: C<success>, C<info>, C<warning> or C<danger>.
Now, anywhere in your application that you have used Log::Report, you can
warning
"Hey user, you should now about this"
;
and the message will be sent to the
next
page the user sees.
=head3 Handling user errors
Sometimes we
write
a function in a model, and it would be nice to have a
nice easy way to
return
from the function
with
an error message. One
way of doing this is
with
a separate error message variable, but that
can be messy code. An alternative is to
use
exceptions, but these
can be a pain to deal
with
in terms of catching them.
Here's how to
do
it
with
Log::Report.
First, your module/model:
sub
update {
my
(
$self
,
%values
) =
@_
;
$values
{title} or error
"Please enter a title"
;
$values
{description} or warning
"No description entered"
;
}
Then, in your controller:
post
'/cd'
=>
sub
{
my
%values
= (
title
=> param(
'title'
);
description
=> param(
'description'
);
);
if
(process
sub
{ MyApp::CD->update(
%values
) } ) {
success
"CD updated successfully"
;
redirect
'/cd'
;
}
template
'cd'
=> {
values
=> \
%values
};
}
Now,
when
update() is called, any exceptions are caught. However, there is
no
need to worry about any error messages. Both the error and warning
messages in the above code will have been stored in the messages session
variable, where they can be displayed using the code in the previous section.
The C<error> will have caused the code to stop running, and process()
will have returned false. C<warning> will have simply logged the warning
and not caused the function to stop running.
=head3 Logging DBIC database queries and errors
If you
use
L<DBIx::Class> in your application, you can easily integrate
its logging and exceptions. To
log
SQL queries:
$schema
->storage->debugobj(new Log::Report::DBIC::Profiler);
$schema
->storage->debug(1);
By
default
, exceptions from DBIC are classified at the level
"error"
. This
is normally a user level error, and thus may be filtered as normal program
operation. If you
do
not expect to receive any DBIC exceptions, then it
is better to class them at the level
"panic"
:
$schema
->exception_action(
sub
{ panic
@_
});
$schema
->stacktrace(1);
If you are occasionally running queries where you expect to naturally
get exceptions (such as not inserting multiple
values
on a unique constraint),
then you can
catch
these separately:
try
{
$self
->schema->resultset(
'Unique'
)->create() };
$@->reportAll(
reason
=>
'TRACE'
);
=head3 Email alerts of exceptions
If you have an unexpected exception in your production application,
then you probably want to be notified about it. One way to
do
so is
configure rsyslog to
send
emails of messages at the panic level. Use
the following configuration to
do
so:
local0.* -/var/
log
/myapp.
log
$ModLoad
ommail
$ActionMailSMTPServer
localhost
$ActionMailFrom
root
$ActionMailTo
root
$template
mailSubject,
"Critical error on %hostname%"
$template
mailBody,
"RSYSLOG Alert\r\nmsg='%msg%'\r\nseverity='%syslogseverity-text%'"
$ActionMailSubject
mailSubject
$ActionExecOnlyOnceEveryInterval
60
if
$syslogfacility
-text ==
'local0'
and
$syslogseverity
< 3 then :ommail:;mailBody
$ActionExecOnlyOnceEveryInterval
0
With the above configuration, you will only be emailed of severe errors, but can
view the full
log
information in /var/
log
/myapp.
log
=head1 CONFIGURATION
All configuration is optional. The example configuration file below shows the
configuration options and defaults.
plugins:
LogReport:
handle_http_errors: 1
forward_url: /
forward_template: error_template_file
fatal_error_message: An unexpected error
has
occurred
session_messages: [ NOTICE, WARNING, MISTAKE, ERROR, FAULT, ALERT, FAILURE, PANIC ]
=head1 SEE ALSO
This module is part of Log-Report distribution version 1.39,
=head1 LICENSE
Copyrights 2007-2024 by [Mark Overmeer <markov
@cpan
.org>]. For other contributors see ChangeLog.
This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.