7 | 7 |
|
8 | 8 |
use Scalar::Util qw/blessed/;
|
9 | 9 |
|
10 | |
my $_dsl; # XXX How to avoid the global?
|
|
10 |
my $_dsl; # XXX How to avoid the global? Dancer2::Core::DSL
|
11 | 11 |
my $_settings;
|
12 | 12 |
|
13 | 13 |
=chapter NAME
|
|
53 | 53 |
used, each configured separately to display different messages in different
|
54 | 54 |
formats. By default, messages are logged to a session variable for display on
|
55 | 55 |
a webpage, and to STDERR.
|
|
56 |
|
|
57 |
Read the L</DETAILS> in below in this manual-page.
|
56 | 58 |
|
57 | 59 |
=chapter METHODS
|
58 | 60 |
|
|
81 | 83 |
if $error->{exception};
|
82 | 84 |
});
|
83 | 85 |
|
84 | |
if ($settings->{handle_http_errors})
|
|
86 |
if($settings->{handle_http_errors})
|
85 | 87 |
{ # Need after_error for HTTP errors (eg 404) so as to
|
86 | 88 |
# be able to change the forwarding location
|
87 | 89 |
$dsl->hook(after_error => sub {
|
|
209 | 211 |
# out. With the former, the exception will have been
|
210 | 212 |
# re-thrown as a non-fatal exception, so check that.
|
211 | 213 |
, ERROR => sub {
|
212 | |
exists $options->{is_fatal} && !$options->{is_fatal}
|
213 | |
? _message_add( danger => $_[0] )
|
214 | |
: _forward_home( $_dsl, danger => $_[0] );
|
215 | |
}
|
|
214 |
return _message_add( danger => $_[0] )
|
|
215 |
if exists $options->{is_fatal} && !$options->{is_fatal};
|
|
216 |
|
|
217 |
return _forward_home( $_dsl, danger => $_[0] )
|
|
218 |
if $_dsl->request->uri ne '/' || !$_dsl->request->is_get;
|
|
219 |
|
|
220 |
return;
|
|
221 |
}
|
216 | 222 |
|
217 | 223 |
# 'FAULT', 'ALERT', 'FAILURE', 'PANIC'
|
218 | 224 |
# All these are fatal errors. Display error to user, but
|
|
250 | 256 |
#----------
|
251 | 257 |
=chapter DETAILS
|
252 | 258 |
|
|
259 |
This chapter will guide you through the myriad of ways that you can use
|
|
260 |
M<Log::Report> in your Dancer2 application.
|
|
261 |
|
|
262 |
We will set up our application to do the following:
|
|
263 |
|
|
264 |
=over 4
|
|
265 |
|
|
266 |
=item Messages to the user
|
|
267 |
We'll look at an easy way to output messages to the user's web page, whether
|
|
268 |
they be informational messages, warnings or errors.
|
|
269 |
|
|
270 |
=item Debug information
|
|
271 |
We'll look at an easy way to log debug information, at different levels.
|
|
272 |
|
|
273 |
=item Manage unexpected exceptions
|
|
274 |
We'll handle unexpected exceptions cleanly, in the unfortunate event that
|
|
275 |
they happen in your production application.
|
|
276 |
|
|
277 |
=item Email alerts of significant errors
|
|
278 |
If we do get unexpected errors then we want to be notified them.
|
|
279 |
|
|
280 |
=item Log DBIC information and errors
|
|
281 |
We'll specifically look at nice ways to log SQL queries and errors when
|
|
282 |
using DBIx::Class.
|
|
283 |
|
|
284 |
=back
|
|
285 |
|
253 | 286 |
=section Larger example
|
254 | 287 |
|
255 | 288 |
In its simplest form, this module can be used for more flexible logging
|
|
264 | 297 |
# The same but translated and with variables
|
265 | 298 |
$name or error __x"{name} is not valid", name => $name;
|
266 | 299 |
|
267 | |
# Show the user a warning, but continue exection
|
|
300 |
# Show the user a warning, but continue execution
|
268 | 301 |
mistake "Not sure that's what you wanted";
|
269 | 302 |
|
270 | |
# Add debug information, can be caught in syslog by adding the syslog
|
271 | |
# dispatcher
|
272 | |
trace "Hello world"; };
|
273 | |
|
274 | |
The module can also be used in models to test for user input and act
|
275 | |
accordingly, without needing to set up complicated error handling:
|
276 | |
|
277 | |
# In a module
|
278 | |
package MyApp::MyModel sub create_user {
|
279 | |
...
|
280 | |
$surname or error "Please enter a surname"; # Execution stops here
|
281 | |
...
|
282 | |
$telephone or mistake "Tel not entered"; # Execution continues
|
283 | |
...
|
284 | |
}
|
285 | |
|
286 | |
# In the main app
|
287 | |
get '/user' => sub {
|
288 | |
...
|
289 | |
if (param 'submit') {
|
290 | |
if (process( sub { MyApp::Model->create_user() })) {
|
291 | |
# Success, redirect user elsewhere
|
292 | |
}
|
293 | |
}
|
294 | |
# Failed, continue as if submit hadn't been made. Error will have been
|
295 | |
# logged in session to be displayed later.
|
296 | |
};
|
|
303 |
# Add debug information, can be caught in syslog by adding
|
|
304 |
# the (for instance) syslog dispatcher
|
|
305 |
trace "Hello world";
|
|
306 |
};
|
|
307 |
|
|
308 |
=section Setup and Configuration
|
|
309 |
|
|
310 |
To make full use of L<Log::Report>, you'll need to use both
|
|
311 |
L<Dancer2::Logger::LogReport> and L<Dancer2::Plugin::LogReport>.
|
|
312 |
|
|
313 |
=subsection Dancer2::Logger::LogReport
|
|
314 |
|
|
315 |
Set up L<Dancer2::Logger::LogReport> by adding it to your Dancer2
|
|
316 |
application configuration (see L<Dancer2::Config>). By default,
|
|
317 |
all messages will go to STDERR.
|
|
318 |
|
|
319 |
To get all message out "the Perl way" (using print, warn and die) just use
|
|
320 |
|
|
321 |
logger: "LogReport"
|
|
322 |
|
|
323 |
At start, these are handled by a M<Log::Report::Dispatcher::Perl> object,
|
|
324 |
named 'default'. If you open a new dispatcher with the name 'default',
|
|
325 |
the output via the perl mechanisms will be stopped.
|
|
326 |
|
|
327 |
To also send messages to your syslog:
|
|
328 |
|
|
329 |
logger: "LogReport"
|
|
330 |
|
|
331 |
engines:
|
|
332 |
logger:
|
|
333 |
LogReport:
|
|
334 |
log_format: %a%i%m
|
|
335 |
app_name: MyApp
|
|
336 |
dispatchers:
|
|
337 |
default: # Name
|
|
338 |
type: SYSLOG # Log::Reporter::dispatcher() options
|
|
339 |
identity: gads
|
|
340 |
facility: local0
|
|
341 |
flags: "pid ndelay nowait"
|
|
342 |
mode: DEBUG
|
|
343 |
|
|
344 |
To send messages to a file:
|
|
345 |
|
|
346 |
logger: "LogReport"
|
|
347 |
|
|
348 |
engines:
|
|
349 |
logger:
|
|
350 |
LogReport:
|
|
351 |
log_format: %a%i%m
|
|
352 |
app_name: MyApp
|
|
353 |
dispatchers:
|
|
354 |
logfile: # "default" dispatcher stays open as well
|
|
355 |
type: FILE
|
|
356 |
to: /var/log/myapp.log
|
|
357 |
charset: utf-8
|
|
358 |
mode: DEBUG
|
|
359 |
|
|
360 |
See L<Log::Report::Dispatcher> for full details of options.
|
|
361 |
|
|
362 |
Finally: on Dancer2 script may run many applications. Each application
|
|
363 |
has its own logger configuration. Be aware that some loggers (for instance
|
|
364 |
syslog) can exist only once in a program. When you use the specify a
|
|
365 |
logger name for a second app (without parameters) the logger object will
|
|
366 |
be shared.
|
|
367 |
|
|
368 |
Example: reuse dispatchers 'default' and 'logfile'
|
|
369 |
|
|
370 |
engines:
|
|
371 |
logger:
|
|
372 |
LogReport:
|
|
373 |
app_name: Other App
|
|
374 |
dispatchers:
|
|
375 |
default:
|
|
376 |
logfile:
|
|
377 |
|
|
378 |
=subsection Dancer2::Plugin::LogReport
|
|
379 |
|
|
380 |
To use the plugin, you simply use it in your application:
|
|
381 |
|
|
382 |
package MyApp;
|
|
383 |
use Dancer2;
|
|
384 |
use Dancer2::Plugin::LogReport %config;
|
|
385 |
|
|
386 |
Dancer2::Plugin::LogReport takes the same C<%config> options as
|
|
387 |
L<Log::Report>.
|
|
388 |
|
|
389 |
If you want to send messages from your modules/models, there is
|
|
390 |
no need to use this specific plugin. Instead, you should simply
|
|
391 |
C<use Log::Report> to negate the need of loading all the Dancer2
|
|
392 |
specific code.
|
|
393 |
|
|
394 |
=section In use
|
|
395 |
|
|
396 |
=subsection Logging debug information
|
|
397 |
|
|
398 |
In its simplest form, you can now use all the
|
|
399 |
L<Log::Report logging functions|Log::Report#The-Reason-for-the-report>
|
|
400 |
to send messages to your dispatchers (as configured in the Logger
|
|
401 |
configuration):
|
|
402 |
|
|
403 |
trace "I'm here";
|
|
404 |
|
|
405 |
warning "Something dodgy happened";
|
|
406 |
|
|
407 |
panic "I'm bailing out";
|
|
408 |
|
|
409 |
# Additional, special Dancer2 keyword
|
|
410 |
success "Settings saved successfully";
|
|
411 |
|
|
412 |
=subsection Exceptions
|
|
413 |
|
|
414 |
Log::Report is a combination of a logger and an exception system. Messages
|
|
415 |
to be logged a I<thrown> to all listening dispatchers to be handled.
|
297 | 416 |
|
298 | 417 |
This module will also catch any unexpected exceptions:
|
299 | 418 |
|
|
306 | 425 |
my $bar = $foo->{x}; # whoops
|
307 | 426 |
}
|
308 | 427 |
|
309 | |
Errors are all logged to the session. These need to be cleared once they have
|
310 | |
been displayed.
|
|
428 |
=subsection Sending messages to the user
|
|
429 |
|
|
430 |
To make it easier to send messages to your users, messages at the following
|
|
431 |
levels are also stored in the user's session: C<notice>, C<warning>, C<mistake>
|
|
432 |
and C<error>.
|
|
433 |
|
|
434 |
You can pass these to your template and display them at each page render:
|
311 | 435 |
|
312 | 436 |
hook before_template => sub {
|
313 | |
my $tokens = shift;
|
314 | |
# Pass messages to template and clear session
|
315 | |
$tokens->{messages} = session 'messages';
|
316 | |
session 'messages' => [];
|
|
437 |
my $tokens = shift;
|
|
438 |
$tokens->{messages} = session 'messages';
|
|
439 |
session 'messages' => []; # Clear the message queue
|
317 | 440 |
}
|
318 | |
|
319 | |
In the template. This example prints them in Bootstrap colors:
|
320 | |
|
321 | |
[% FOR message IN messages %]
|
322 | |
[% IF message.type %]
|
323 | |
[% msgtype = message.type %]
|
324 | |
[% ELSE %]
|
325 | |
[% msgtype = "info" %]
|
326 | |
[% END %]
|
327 | |
<div class="alert alert-[% msgtype %]">
|
328 | |
[% message.text | html_entity %]
|
329 | |
</div>
|
330 | |
[% END %]
|
331 | |
|
332 | |
=section Configuration
|
333 | |
|
334 | |
=subsection Dancer2 configuration
|
335 | |
|
336 | |
In your application's configuration file (values shown are defaults):
|
337 | |
|
338 | |
plugins: LogReport
|
339 | |
# Set the forward URL on fatal error that isn't caught
|
340 | |
forward_url: /
|
341 | |
|
342 | |
# Set to 1 if you want the module to also catch Dancer HTTP errors
|
343 | |
# (such as 404s)
|
344 | |
handle_http_errors: 0
|
345 | |
|
346 | |
# Configure session variable for messages
|
347 | |
messages_key = messages
|
348 | |
|
349 | |
=subsection Log::Report configuration
|
350 | |
|
351 | |
Any L<Log::Report configuration options|Log::Report/Configuration> can also be
|
352 | |
used with this plugin.
|
353 | |
|
354 | |
=subsection Dancer2::Logger::LogReport
|
355 | |
|
356 | |
You probably want to also use and configure M<Dancer2::Logger::LogReport>.
|
357 | |
See its documentation for full details.
|
|
441 |
|
|
442 |
Then in your template (for example the main layout):
|
|
443 |
|
|
444 |
[% FOR message IN messages %]
|
|
445 |
[% IF message.type %]
|
|
446 |
[% msgtype = message.type %]
|
|
447 |
[% ELSE %]
|
|
448 |
[% msgtype = "info" %]
|
|
449 |
[% END %]
|
|
450 |
<div class="alert alert-[% msgtype %]">
|
|
451 |
[% message.text | html_entity %]
|
|
452 |
</div>
|
|
453 |
[% END %]
|
|
454 |
|
|
455 |
The C<type> of the message is compatible with Bootstrap contextual colors:
|
|
456 |
C<info>, C<warning> or C<danger>.
|
|
457 |
|
|
458 |
Now, anywhere in your application that you have used Log::Report, you can
|
|
459 |
|
|
460 |
warning "Hey user, you should now about this";
|
|
461 |
|
|
462 |
and the message will be sent to the next page the user sees.
|
|
463 |
|
|
464 |
=subsection Handling user errors
|
|
465 |
|
|
466 |
Sometimes we write a function in a model, and it would be nice to have a
|
|
467 |
nice easy way to return from the function with an error message. One
|
|
468 |
way of doing this is with a separate error message variable, but that
|
|
469 |
can be messy code. An alternative is to use exceptions, but these
|
|
470 |
can be a pain to deal with in terms of catching them.
|
|
471 |
Here's how to do it with Log::Report.
|
|
472 |
|
|
473 |
In this example, we do use exceptions, but in a neat, easier to use manner.
|
|
474 |
|
|
475 |
First, your module/model:
|
|
476 |
|
|
477 |
package MyApp::CD;
|
|
478 |
|
|
479 |
sub update {
|
|
480 |
my ($self, %values) = @_;
|
|
481 |
$values{title} or error "Please enter a title";
|
|
482 |
$values{description} or warning "No description entered";
|
|
483 |
}
|
|
484 |
|
|
485 |
Then, in your controller:
|
|
486 |
|
|
487 |
package MyApp;
|
|
488 |
use Dancer2;
|
|
489 |
|
|
490 |
post '/cd' => sub {
|
|
491 |
my %values = (
|
|
492 |
title => param('title');
|
|
493 |
description => param('description');
|
|
494 |
);
|
|
495 |
if (process sub { MyApp::CD->update(%values) } ) {
|
|
496 |
success "CD updated successfully";
|
|
497 |
redirect '/cd';
|
|
498 |
}
|
|
499 |
|
|
500 |
template 'cd' => { values => \%values };
|
|
501 |
}
|
|
502 |
|
|
503 |
Now, when update() is called, any exceptions are caught. However, there is
|
|
504 |
no need to worry about any error messages. Both the error and warning
|
|
505 |
messages in the above code will have been stored in the messages session
|
|
506 |
variable, where they can be displayed using the code in the previous section.
|
|
507 |
The C<error> will have caused the code to stop running, and process()
|
|
508 |
will have returned false. C<warning> will have simply logged the warning
|
|
509 |
and not caused the function to stop running.
|
|
510 |
|
|
511 |
=subsection Logging DBIC database queries and errors
|
|
512 |
|
|
513 |
If you use L<DBIx::Class> in your application, you can easily integrate
|
|
514 |
its logging and exceptions. To log SQL queries:
|
|
515 |
|
|
516 |
# Log all queries and execution time
|
|
517 |
$schema->storage->debugobj(new Log::Report::DBIC::Profiler);
|
|
518 |
$schema->storage->debug(1);
|
|
519 |
|
|
520 |
By default, exceptions from DBIC are classified at the level "error". This
|
|
521 |
is normally a user level error, and thus may be filtered as normal program
|
|
522 |
operation. If you do not expect to receive any DBIC exceptions, then it
|
|
523 |
is better to class them at the level "panic":
|
|
524 |
|
|
525 |
# panic() DBIC errors
|
|
526 |
$schema->exception_action(sub { panic @_ });
|
|
527 |
# Optionally get a stracktrace too
|
|
528 |
$schema->stacktrace(1);
|
|
529 |
|
|
530 |
If you are occasionally running queries where you expect to naturally
|
|
531 |
get exceptions (such as not inserting multiple values on a unique constraint),
|
|
532 |
then you can catch these separately:
|
|
533 |
|
|
534 |
try { $self->schema->resultset('Unique')->create() };
|
|
535 |
# Log any messages from try block, but only as trace
|
|
536 |
$@->reportAll(reason => 'TRACE');
|
|
537 |
|
|
538 |
=subsection Email alerts of exceptions
|
|
539 |
|
|
540 |
If you have an unexpected exception in your production application,
|
|
541 |
then you probably want to be notified about it. One way to do so is
|
|
542 |
configure rsyslog to send emails of messages at the panic level. Use
|
|
543 |
the following configuration to do so:
|
|
544 |
|
|
545 |
# Normal logging from LOCAL0
|
|
546 |
local0.* -/var/log/myapp.log
|
|
547 |
|
|
548 |
# Load the mail module
|
|
549 |
$ModLoad ommail
|
|
550 |
# Configure sender, receiver and mail server
|
|
551 |
$ActionMailSMTPServer localhost
|
|
552 |
$ActionMailFrom root
|
|
553 |
$ActionMailTo root
|
|
554 |
# Set up an email template
|
|
555 |
$template mailSubject,"Critical error on %hostname%"
|
|
556 |
$template mailBody,"RSYSLOG Alert\r\nmsg='%msg%'\r\nseverity='%syslogseverity-text%'"
|
|
557 |
$ActionMailSubject mailSubject
|
|
558 |
# Send an email no more frequently than every minute
|
|
559 |
$ActionExecOnlyOnceEveryInterval 60
|
|
560 |
# Configure the level of message to notify via email
|
|
561 |
if $syslogfacility-text == 'local0' and $syslogseverity < 3 then :ommail:;mailBody
|
|
562 |
$ActionExecOnlyOnceEveryInterval 0
|
|
563 |
|
|
564 |
With the above configuration, you will only be emailed of severe errors, but can
|
|
565 |
view the full log information in /var/log/myapp.log
|
|
566 |
|
358 | 567 |
|
359 | 568 |
=cut
|
360 | 569 |
|