Codebase list liblog-report-perl / v1.07
distribution Log-Report-1.07.tar.gz Mark Overmeer authored 8 years ago Mark Overmeer committed 6 years ago
10 changed file(s) with 405 addition(s) and 135 deletion(s). Raw diff Collapse all Expand all
55
66 TODO:
77 . connect to Message::Passing framework
8
9 version 1.07: Tue Jul 21 17:38:01 CEST 2015
10
11 Fixes:
12 - remove superfluous blank lines, when (translated) message
13 ends on \n. Reported by [Andrew Beverley]
14 - Dancer2::Plugin:: deep recursion in ERROR handler [Andrew Beverley]
15
16 Improvements:
17 - document HASH for ::Syslog::new(logsocket).
18 Idea of [Andrew Beverley]
19 - add Log::Report::DBIC::Profiler [Andrew Beverley]
20 - loads of documentation on using Log::Report in Dancer2,
21 written by [Andrew Beverley]
22 - protect against two instances of ::Syslog at the same time: its
23 impossible.
824
925 version 1.06: Mon Jun 15 17:30:33 CEST 2015
1026
66 lib/Dancer2/Logger/LogReport.pm
77 lib/Dancer2/Plugin/LogReport.pm
88 lib/Log/Report.pm
9 lib/Log/Report/DBIC/Profiler.pm
910 lib/Log/Report/Die.pm
1011 lib/Log/Report/Dispatcher.pm
1112 lib/Log/Report/Dispatcher/Callback.pm
11
22 use 5.008;
33
4 my $version = '1.06';
4 my $version = '1.07';
55
66 my %prereq =
77 ( Test::More => '0.86'
99 use Log::Report 'logreport', syntax => 'REPORT', mode => 'DEBUG';
1010
1111 our $AUTHORITY = 'cpan:MARKOV';
12
13 # all dispatchers shall be created exactly once (unique name)
14 my %disp_objs;
1215
1316 my %level_dancer2lr =
1417 ( core => 'TRACE'
2427 , lazy => 1
2528 );
2629
27 sub BUILD
28 { my $self = shift;
29 my $dispatchers = $self->dispatchers;
30 sub BUILD ()
31 { my $self = shift;
32 my $configs = $self->dispatchers || {default => undef};
33 $self->{use} = [keys %$config];
3034
31 foreach my $name (keys %$dispatchers)
32 { my %dispatcher = %{$dispatchers->{$name}};
33 my $type = delete $dispatcher{type};
34 dispatcher $type => $name, %dispatcher;
35 foreach my $name (keys %$configs)
36 { my $config = $configs->{$name} || {};
37 if(keys %$config)
38 { # cannot use log() output yet! recursion!
39 ! $disp_objs{$name}
40 or die "attempt to reconfigure dispatcher $name";
41
42 my $type = delete $config->{type}
43 or die "dispatcher configuration $name without type";
44
45 $disp_objs{$name} = dispatcher $type, $name, %$config;
46 }
3547 }
3648 }
3749
4456 # This module is loaded when configured. It does not provide
4557 # end-user functions or methods.
4658
59 # See L<Dancer2::Plugin::LogReport/"DETAILS">
60
4761 =chapter DESCRIPTION
4862
4963 This logger allows the use of the many logging backends available
6983 You also want to set the log level to C<debug>, because level filtering is
7084 controlled per dispatcher (as well).
7185
86 See L<Dancer2::Plugin::LogReport/"DETAILS"> for examples.
87
7288 =chapter METHODS
7389
7490 =method log $level, $params
88104 $msg =~ s/\n+$//;
89105 }
90106
91 # The levels are nearly the same.
107 # the levels are nearly the same.
92108 my $reason = $level_dancer2lr{$level} // uc $level;
93109
94 report {is_fatal => 0}, $reason => $msg;
110 report {is_fatal => 0}, $reason => $msg, to => $self->{use};
95111
96112 undef;
97113 }
98114
99 #--------------
100 =chapter DETAILS
101
102 =section Configuration
103
104 The setting B<logger> should be set to C<LogReport> in order to use
105 this logging engine in a Dancer application. See M<Dancer2::Config>
106 about ways to include these settings in your program.
107
108 There is only one optional configuration parameter: C<dispatchers>. This
109 defines the M<Log::Report> dispatchers to use. Any number of dispatchers
110 may be configured.
111
112 # instruct Dancer2 to load this module
113 logger: LogReport
114
115 # use default Log::Report dispatchers
116 engines:
117 logger:
118 LogReport:
119
120 # syslog and file dispatcher
121 engines:
122 logger:
123 LogReport:
124 logger_format: %i%m # keep it simple
125 dispatchers:
126 syslog: # Name
127 type: SYSLOG # Log::Report dispatcher type
128 identity: gads # Dispatcher options
129 facility: local0
130 flags: "pid ndelay nowait"
131 mode: DEBUG
132 default: # will replace default dispatcher
133 type: FILE
134 to: /var/log/mylog
135 charset: utf-8
136 accept: NOTICE- # Only accept NOTICE and above
137
138 =cut
139
140115 1;
77
88 use Scalar::Util qw/blessed/;
99
10 my $_dsl; # XXX How to avoid the global?
10 my $_dsl; # XXX How to avoid the global? Dancer2::Core::DSL
1111 my $_settings;
1212
1313 =chapter NAME
5353 used, each configured separately to display different messages in different
5454 formats. By default, messages are logged to a session variable for display on
5555 a webpage, and to STDERR.
56
57 Read the L</DETAILS> in below in this manual-page.
5658
5759 =chapter METHODS
5860
8183 if $error->{exception};
8284 });
8385
84 if ($settings->{handle_http_errors})
86 if($settings->{handle_http_errors})
8587 { # Need after_error for HTTP errors (eg 404) so as to
8688 # be able to change the forwarding location
8789 $dsl->hook(after_error => sub {
209211 # out. With the former, the exception will have been
210212 # re-thrown as a non-fatal exception, so check that.
211213 , 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 }
216222
217223 # 'FAULT', 'ALERT', 'FAILURE', 'PANIC'
218224 # All these are fatal errors. Display error to user, but
250256 #----------
251257 =chapter DETAILS
252258
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
253286 =section Larger example
254287
255288 In its simplest form, this module can be used for more flexible logging
264297 # The same but translated and with variables
265298 $name or error __x"{name} is not valid", name => $name;
266299
267 # Show the user a warning, but continue exection
300 # Show the user a warning, but continue execution
268301 mistake "Not sure that's what you wanted";
269302
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.
297416
298417 This module will also catch any unexpected exceptions:
299418
306425 my $bar = $foo->{x}; # whoops
307426 }
308427
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:
311435
312436 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
317440 }
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
358567
359568 =cut
360569
0 use strict;
1 use warnings;
2
3 package Log::Report::DBIC::Profiler;
4 use base 'DBIx::Class::Storage::Statistics';
5
6 use Log::Report import => 'trace';
7 use Time::HiRes qw(time);
8
9 =chapter NAME
10
11 Log::Report::DBIC::Profiler - query profiler for DBIx::Class
12
13 =chapter SYNOPSIS
14
15 use Log::Report::DBIC::Profiler;
16 $schema->storage->debugobj(Log::Report::DBIC::Profiler->new);
17 $schema->storage->debug(1);
18
19 # And maybe (if no exceptions expected from DBIC)
20 $schema->exception_action(sub { panic @_ });
21
22 # Log to syslog
23 use Log::Report;
24 dispatcher SYSLOG => 'myapp'
25 , identity => 'myapp'
26 , facility => 'local0'
27 , flags => "pid ndelay nowait"
28 , mode => 'DEBUG';
29
30 =chapter DESCRIPTION
31
32 This profile will log M<DBIx::Class> queries via M<Log::Report> to a
33 selected back-end (via a dispatcher, see M<Log::Report::Dispatcher>)
34
35 =cut
36
37 my $start;
38
39 sub print($) { trace $_[1] }
40
41 sub query_start(@)
42 { my $self = shift;
43 $self->SUPER::query_start(@_);
44 $start = time;
45 }
46
47 sub query_end(@)
48 { my $self = shift;
49 $self->SUPER::query_end(@_);
50 trace sprintf "execution took %0.4f seconds elapse", time-$start;
51 }
52
53 1;
54
9292 =default to_prio []
9393 See M<reasonToPrio()>.
9494
95 =option logsocket 'unix'|'inet'|'stream'
95 =option logsocket 'unix'|'inet'|'stream'|HASH
9696 =default logsocket C<undef>
9797 If specified, the log socket type will be initialized to this before
9898 C<openlog()> is called. If not specified, the system default is used.
107107 sysadmin may get unreadable text.
108108 =cut
109109
110 my $active;
111
110112 sub init($)
111113 { my ($self, $args) = @_;
112114 $args->{format_reason} ||= 'IGNORE';
113115
114116 $self->SUPER::init($args);
117
118 error __x"max one active syslog dispatcher, attempt for {new} have {old}"
119 , new => $self->name, old => $active
120 if $active;
121 $active = $self->name;
115122
116123 setlogsock(delete $args->{logsocket})
117124 if $args->{logsocket};
144151
145152 sub close()
146153 { my $self = shift;
154 undef $active;
147155 closelog;
156
148157 $self->SUPER::close;
149158 }
150159
303303 = ( defined $format
304304 ? __x($format, message => $msg->toString , reason => $r, error => $e)
305305 : $msg
306 )->toString . "\n";
306 )->toString;
307 $text =~ s/\n*\z/\n/;
307308
308309 if($show_loc)
309310 { if(my $loc = $opts->{location} || $self->collectLocation)
373373 sub dispatcher($@)
374374 { if($_[0] !~ m/^(?:close|find|list|disable|enable|mode|needs|filter)$/)
375375 { my ($type, $name) = (shift, shift);
376
377 # old dispatcher with same name will be closed in DESTROY
378 delete $reporter->{dispatchers}{$name};
379
376380 my $disp = Log::Report::Dispatcher->new($type, $name
377381 , mode => $default_mode, @_);
378382 defined $disp or return; # use defined, because $disp is overloaded
379383
380 # old dispatcher with same name will be closed in DESTROY
381384 $reporter->{dispatchers}{$name} = $disp;
382385 _whats_needed;
383386 return ($disp);
430433 }
431434
432435 # find does require reinventarization
433 _whats_needed unless $command eq 'find';
436 _whats_needed if $command ne 'find';
434437
435438 wantarray ? @dispatchers : $dispatchers[0];
436439 }
2626 isa_ok($log, 'Mojo::Log');
2727
2828 my $tmp;
29 dispatcher close => 'default';
2930 try { $log->error("going to die"); $tmp = 42 } mode => 3;
3031 my $err = $@;
3132 #warn Dumper $err;