527 lines
17 KiB
Plaintext
527 lines
17 KiB
Plaintext
=encoding utf8
|
|
|
|
=head1 NAME
|
|
|
|
Dancer2::Plugin::LogReport - logging and exceptions via Log::Report
|
|
|
|
=head1 INHERITANCE
|
|
|
|
Dancer2::Plugin::LogReport
|
|
is a Dancer2::Plugin
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
# Load the plugin into Dancer2
|
|
# see Log::Report::import() for %options
|
|
use Dancer2::Plugin::LogReport %options;
|
|
|
|
# Stop execution, redirect, and display an error to the user
|
|
$name or error "Please enter a name";
|
|
|
|
# Add debug information to logger
|
|
trace "We're here";
|
|
|
|
# Handling user errors cleanly
|
|
if (process( sub {MyApp::Model->create_user} )) {
|
|
# Success, redirect user elsewhere
|
|
} else {
|
|
# Failed, continue as if submit hadn't been made.
|
|
# Error message will be in session for display later.
|
|
}
|
|
|
|
# Send errors to template for display
|
|
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>
|
|
you should also C<use Dancer2::Plugin::LogReport>. This does not apply if
|
|
using the same app name (C<use Dancer2 appname, 'Already::Exists'>). In
|
|
all other modules, you can just C<use Log::Report>.
|
|
|
|
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#class-STRING-ARRAY>
|
|
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#bootstrap_color> uses this to display the
|
|
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 {
|
|
# Stop execution, redirect, and display an error to the user
|
|
$name or error "Please enter a name";
|
|
|
|
# The same but translated
|
|
$name or error __"Please enter a name";
|
|
|
|
# The same but translated and with variables
|
|
$name or error __x"{name} is not valid", name => $name;
|
|
|
|
# Show the user a warning, but continue execution
|
|
mistake "Not sure that's what you wanted";
|
|
|
|
# Add debug information, can be caught in syslog by adding
|
|
# the (for instance) syslog dispatcher
|
|
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 # See Dancer2::Logger::LogReport
|
|
app_name: MyApp
|
|
dispatchers:
|
|
default: # Name
|
|
type: SYSLOG # Log::Reporter::dispatcher() options
|
|
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 # See Dancer2::Logger::LogReport
|
|
app_name: MyApp
|
|
dispatchers:
|
|
logfile: # "default" dispatcher stays open as well
|
|
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:
|
|
|
|
package MyApp;
|
|
use Log::Report (); # use early and minimal once
|
|
use Dancer2;
|
|
use Dancer2::Plugin::LogReport %config;
|
|
|
|
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#The-Reason-for-the-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";
|
|
|
|
# Additional, special Dancer2 keyword
|
|
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:
|
|
|
|
# This will be caught, the error will be logged (full stacktrace to STDOUT,
|
|
# short message to the session messages), and the user will be forwarded
|
|
# (default to /). This would also be sent to syslog with the appropriate
|
|
# dispatcher.
|
|
get 'route' => sub {
|
|
my $foo = 1;
|
|
my $bar = $foo->{x}; # whoops
|
|
}
|
|
|
|
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' => []; # Clear the message queue
|
|
}
|
|
|
|
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.
|
|
|
|
In this example, we do use exceptions, but in a neat, easier to use manner.
|
|
|
|
First, your module/model:
|
|
|
|
package MyApp::CD;
|
|
|
|
sub update {
|
|
my ($self, %values) = @_;
|
|
$values{title} or error "Please enter a title";
|
|
$values{description} or warning "No description entered";
|
|
}
|
|
|
|
Then, in your controller:
|
|
|
|
package MyApp;
|
|
use Dancer2;
|
|
|
|
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:
|
|
|
|
# Log all queries and execution time
|
|
$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":
|
|
|
|
# panic() DBIC errors
|
|
$schema->exception_action(sub { panic @_ });
|
|
# Optionally get a stracktrace too
|
|
$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() };
|
|
# Log any messages from try block, but only as trace
|
|
$@->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:
|
|
|
|
# Normal logging from LOCAL0
|
|
local0.* -/var/log/myapp.log
|
|
|
|
# Load the mail module
|
|
$ModLoad ommail
|
|
# Configure sender, receiver and mail server
|
|
$ActionMailSMTPServer localhost
|
|
$ActionMailFrom root
|
|
$ActionMailTo root
|
|
# Set up an email template
|
|
$template mailSubject,"Critical error on %hostname%"
|
|
$template mailBody,"RSYSLOG Alert\r\nmsg='%msg%'\r\nseverity='%syslogseverity-text%'"
|
|
$ActionMailSubject mailSubject
|
|
# Send an email no more frequently than every minute
|
|
$ActionExecOnlyOnceEveryInterval 60
|
|
# Configure the level of message to notify via email
|
|
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:
|
|
# Whether to handle Dancer HTTP errors such as 404s. Currently has
|
|
# no effect due to unresolved issues saving messages to the session
|
|
# and accessing the DSL at that time.
|
|
handle_http_errors: 1
|
|
# Where to forward users in the event of an uncaught fatal
|
|
# error within a GET request
|
|
forward_url: /
|
|
# Or you can specify a template instead [1.13]
|
|
forward_template: error_template_file # Defaults to empty
|
|
# For a production server (show_errors: 0), this is the text that
|
|
# will be displayed instead of unexpected exception errors
|
|
fatal_error_message: An unexpected error has occurred
|
|
# The levels of messages that will be saved to the session, and
|
|
# thus displayed to the end user
|
|
session_messages: [ NOTICE, WARNING, MISTAKE, ERROR, FAULT, ALERT, FAILURE, PANIC ]
|
|
|
|
=head1 SEE ALSO
|
|
|
|
This module is part of Log-Report distribution version 1.31,
|
|
built on January 15, 2021. Website: F<http://perl.overmeer.net/CPAN/>
|
|
|
|
=head1 LICENSE
|
|
|
|
Copyrights 2007-2021 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.
|
|
See F<http://dev.perl.org/licenses/>
|
|
|