File Coverage

blib/lib/RSH/Logging.pm
Criterion Covered Total %
statement 117 120 97.5
branch 44 52 84.6
condition 15 20 75.0
subroutine 19 21 90.4
pod 8 8 100.0
total 203 221 91.8


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             RSH::Logging - Utility for instrumenting code using Log::Log4perl.
4              
5             =head1 SYNOPSIS
6              
7             # In frameworks and other units of code
8             use RSH::Logging qw(start_event stop_event);
9             ...
10             start_event('My piece of work');
11             ...
12             stop_event();
13            
14             # At the entry point or "top" of your unit of work
15             use RSH::Logging qw(start_event_tracking stop_event_tracking print_event_tracking_results);
16             use Log::Log4perl qw(:easy);
17             our $logger = get_logger(__PACKAGE__);
18             ...
19             start_event_tracking($logger, 'My Business Function');
20             ...
21             stop_event_tracking();
22             # print the results to logging
23             print_event_tracking_results($logger);
24             # print the results to a file (using ">filename")
25             print_event_tracking_results($filename);
26             # print the results to a file handle
27             print_event_tracking_results($fh);
28             # print the results to STDERR
29             print_event_tracking_results();
30            
31             # all of which will give you something like:
32             TOTAL TIME 0.000766s (1305.483/s)
33             .------------------------------+-----------.
34             | Event | Time |
35             +------------------------------+-----------+
36             | + My Business Function | 0.000766s |
37             | + Reusable foo module | 0.000615s |
38             | + Database code | 0.000141s |
39             | + My piece of work | 0.000191s |
40             '------------------------------+-----------'
41              
42             =head1 DESCRIPTION
43              
44             RSH::Logging is kind of like poor-man's profiling or dtrace. It is designed on the same
45             concepts behind logging packages like Log::Log4perl, that when event tracking is off, there
46             be little to no overhead incurred by leaving the code in place. This allows you to
47             instrument all your code and frameworks and then dynamically turn on this kind of
48             profiling information when you need it.
49              
50             =head2 Best Practices
51              
52             Below are some best practices to using RSH::Logging.
53              
54             =head3 1. Only use start_event/stop_event.
55              
56             The start_event_tracking and stop_event_tracking will mark the "top" or beginning of
57             a transaction. If you are truly writing modular code, you will never know when the
58             beginning of a transaction is until you assemble all your modular code and execute it
59             as a part of a business function or script. As a result, you should always defer
60             marking the transaction boundaries and printing out the results to the final client
61             of your code.
62              
63             For example, if you write a data access object that loads data into objects and a
64             generic database framework, both of these should only use start_event and stop_event.
65             It would be your CGI script that would do the start_event_tracking and stop_event_tracking,
66             thus marking the total transaction--which your events in the data access object
67             and the generic database framework would be contained within. For example,
68             the final event tree would look something like this:
69              
70             TOTAL TIME 0.000766s (1305.483/s)
71             .------------------------------+-----------.
72             | Event | Time |
73             +------------------------------+-----------+
74             | + foobar.cgi | 0.000766s |
75             | + foobar business function | 0.000615s |
76             | + get database handle | 0.000141s |
77             | + load foobar by id | 0.000191s |
78             '------------------------------+-----------'
79              
80             Where "get database handle" is your generic database framework and "load foobar by id"
81             is your data access object. You can see that the "foobar.cgi" is the script marking
82             the transaction via start_event_tracking and stop_event_tracking.
83              
84             NOTE: if you call start_event_tracking within a block of code that has already
85             called start_event_tracking, it will be treated as just another call to start_event.
86              
87             =head3 2. Try to specify the event string when possible.
88              
89             While RSH::Logging will not do anything if tracking isn't enabled, unnecessary overhead can
90             be incurred if you don't specify an event string. If no event string lable is sent to
91             start_event, caller() is used to find out who called--and that string is used as the
92             event name. The call to caller() can be avoided by specifying an event name. While
93             caller() is not horribly expensive, it will add to overhead for the total time if
94             you make enough calls to it.
95              
96             =head3 3. Always print the results to a logger
97              
98             Traversing the events and printing them to a table will always have some cost. You should
99             allow the logging system to help you mitigate this. RSH::Logging will output the
100             event table only if the logger has DEBUG enabled. If the logger is only printing INFO
101             levels, the tree will not be processed or printed.
102              
103             =head3 4. If you don't print the results to a logger, wrap it in a logger check.
104              
105             If for some reason you don't print the results to the logger, at least wrap
106             the call to print_event_tracking_results with a check on the logger:
107              
108             print_event_tracking_results($event_trace_file)
109             if ($logger->is_debug());
110              
111             This will prevent you from incurring overhead unnecessarily in production.
112              
113             =head3 5. Try not to be overly fine-grained in your tracking.
114              
115             This takes a little trial and error to get the feel for, but can best be illustrated
116             with an example.
117              
118             If you have a method "convert_value" that may be called 100 times, putting a
119             start_event in the convert_value body will then generate 100 events in your event tree.
120             This will make it hard to pin-point problem areas in your code, as the event tree will be
121             100+ events in length (many of them perhaps very small time values).
122              
123             If you have a situation like this, move the start_event call up one level. So
124             for some situation like the following:
125              
126             sub convert_value {
127             start_event('convert_value');
128             ...
129             stop_event();
130             }
131              
132             foreach $element (@elements) {
133             convert_value($element);
134             }
135              
136             Do the following:
137              
138             sub convert_value {
139             ...
140             }
141              
142             start_event('convert_value for elements');
143             foreach $element (@elements) {
144             convert_value($element);
145             }
146             stop_event();
147              
148             This will make the final event tree easier to parse.
149              
150             =cut
151              
152             package RSH::Logging;
153              
154 4     4   97370 use 5.008;
  4         16  
  4         155  
155 4     4   23 use strict;
  4         7  
  4         157  
156 4     4   20 use warnings;
  4         15  
  4         165  
157              
158 4     4   18 use base qw(Exporter);
  4         9  
  4         697  
159              
160             # Items to export into callers namespace by default. Note: do not export
161             # names by default without a very good reason. Use EXPORT_OK instead.
162             # Do not simply export all your public functions/methods/constants.
163              
164             =head2 EXPORT
165              
166             None by default.
167              
168             You may choose to conditionally export the following:
169              
170             =over
171              
172             =item * start_event_tracking
173              
174             =item * stop_event_tracking
175              
176             =item * start_event
177              
178             =item * stop_event
179              
180             =item * get_event_tracking_results
181              
182             =item * print_event_tracking_results
183              
184             =item * event_tracking_in_progress
185              
186             =back
187              
188             =cut
189              
190             our @EXPORT_OK = qw(
191             &start_event_tracking
192             &stop_event_tracking
193             &start_event
194             &stop_event
195             &get_event_tracking_results
196             &print_event_tracking_results
197             &event_tracking_in_progress
198             );
199              
200             our @EXPORT = qw(
201             );
202              
203             our $VERSION = '0.4.3';
204              
205             # use/imports go here
206 4     4   5615 use Log::Log4perl qw(:easy);
  4         274122  
  4         33  
207             our $logger = get_logger(__PACKAGE__);
208 4     4   2690 use Scalar::Util qw(blessed);
  4         15  
  4         544  
209 4     4   27 use Time::HiRes qw/gettimeofday tv_interval/;
  4         11  
  4         39  
210             #use Text::SimpleTable;
211 4     4   4088 use RSH::Logging::TextTable;
  4         13  
  4         215  
212 4     4   5364 use IO::Handle;
  4         39906  
  4         246  
213 4     4   5015 use IO::File;
  4         24492  
  4         7308  
214              
215             # ******************** Class Methods ********************
216              
217             # The number of rows we automatically start using chunking at.
218             # A value of <= 0 means no auto-chunking.
219             our $AUTO_CHUNK_LIMIT = -1;
220              
221             our $tracking = 0;
222             our $event_tree;
223             our $event_count = 0;
224             our $curr;
225             our @parents;
226             our $results;
227             our $nested_starts = 0;
228              
229             =head2 FUNCTIONS
230              
231             =over
232              
233             =cut
234              
235             =item event_tracking_in_progress()
236              
237             Returns 1 if events are currently being tracked, 0 otherwise.
238              
239             =cut
240              
241             sub event_tracking_in_progress {
242 0     0 1 0 return $tracking;
243             }
244              
245             =item start_event_tracking($logger [, $event_name, $descriptive_note])
246              
247             Called to signal the beginning of a transaction or the "top" of an event
248             tree. This call should be deferred to client code, such as top-level scripts
249             or business functions.
250              
251             This call starts event tracking if the supplied logger is processing DEBUG messages.
252             If the logger is undefined or not processing DEBUG messages, the call returns
253             immediately and event tracking is not started (all calls to start_event will
254             exit immediately, preventing any unnecessary overhead in the intrumented
255             code).
256              
257             If the logger is processing DEBUG messages, event tracking is started
258             and a call is made to start_event with the given event name and optional
259             descriptive note. If no event name is specified, the results of caller()
260             are used for the event name.
261              
262             =cut
263              
264             sub start_event_tracking {
265 19     19 1 13908 my $the_logger = shift;
266 19         61 my $event = shift;
267 19         37 my $note = shift;
268            
269 19 100       83 $event = caller() if not defined($event);
270            
271 19 100       66 return if (not defined($the_logger));
272 18 100       60 if ($tracking) {
273             # if we are already tracking, just suck it in like a normal event ...
274 1         6 start_event($event, $note);
275 1         3 $nested_starts++;
276 1         3 return;
277             }
278             else {
279 17 50       85 return unless ($the_logger->is_debug());
280            
281             # otherwise ...
282 17         217 $tracking = 1;
283 17         43 $event_tree = undef;
284 17         26 $event_count = 0;
285 17         35 $results = undef;
286 17         224 $curr = undef;
287 17         37 @parents = ();
288 17         45 start_event($event, $note);
289 17         43 return;
290             }
291             }
292              
293             =item stop_event_tracking()
294              
295             Called to signal the end of a transaction or event tree. This call
296             should be deferred to client code, such as top-level scripts or business
297             functions.
298              
299             Stops event tracking and places the current event tree in a variable
300             to be used by get_event_tracking_results and print_event_tracking_results.
301             Neither get_event_tracking_results or print_event_tracking_results will
302             process anything until stop_event_tracking has been called.
303              
304             =cut
305              
306             sub stop_event_tracking {
307 19 100   19 1 1165 return unless $tracking;
308 18 100       80 if ($nested_starts > 0) {
309 1         2 $nested_starts--;
310 1         4 stop_event();
311             }
312             else {
313 17         53 while (defined($curr)) {
314 19         104 stop_event();
315             }
316 17         38 $results = $event_tree;
317 17         21 $event_tree = undef;
318 17         39 $tracking = 0;
319             }
320             }
321              
322             =item print_event_tracking_results($to_target, [$chunk_it])
323              
324             Prints the event tree to a specified target if event tracking was started
325             and stopped successfully. The target may be either a Logger from
326             Log::Log4perl (recommended and preferred), a filename (in which case
327             it will be opened for writing via ">filename"), or a file handle (subclass
328             of IO::Handle). If event tracking was never started (either because start_event_tracking
329             was not called or the supplied logger was not processing DEBUG messages) or
330             is stop_event_tracking was not called, this method will exit
331             quickly and do nothing.
332              
333             If there are event tracking results to process, the events will be composed
334             into a table, with each row's indentation representing whether it is a parent,
335             peer, or child of the surrounding rows. The following table is an example:
336              
337             .------------------------------+-----------.
338             | Event | Time |
339             +------------------------------+-----------+
340             | + My Business Function | 0.000766s |
341             | + Reusable foo module | 0.000615s |
342             | + Database code | 0.000141s |
343             | + My piece of work | 0.000191s |
344             '------------------------------+-----------'
345              
346             "My Business Function" is the top-most event, the top of the call stack.
347             At this point, start_event_tracking was called with an event name of "My Business
348             Function". At some point between start_event_tracking and stop_event_tracking,
349             "Reusable foo module" was called--thus it is a child of "My Business Function".
350             The module then made two calls, one to "Database code" and another to "My piece
351             of work"--these two events are peers and children of the module event.
352             Other calls may have been made, but they were not instrumented using start/stop_event.
353              
354             If the second parameter is "true", then chunking will be used if supported for
355             the C<$to_target> value. Only filehandles and Log4Perl are supported currently.
356              
357             =cut
358              
359             sub print_event_tracking_results {
360 17 100   17 1 449 return unless defined($results);
361            
362 16         30 my $to = shift;
363 16         24 my $chunk_it = shift;
364 16         29 my $fh = undef;
365 16         20 my $logger = undef;
366 16 100 100     2331 if (blessed($to) and $to->isa('Log::Log4perl::Logger')) {
    100 66        
    50          
367 4         14 $logger = $to;
368             }
369             elsif (blessed($to) and $to->isa('IO::Handle')) {
370 2         5 $fh = $to;
371             }
372             elsif (defined($to)) {
373 0         0 $fh = new IO::File ">$to";
374             }
375             else {
376 10         364 $fh = new IO::File ">&STDERR";
377             }
378            
379 16 50 66     882 return unless defined($fh) or defined($logger);
380 16 50 66     157 return if (defined($logger) and (not $logger->is_debug));
381            
382            
383             # my $t = Text::SimpleTable->new( [ 62, 'Event' ], [ 9, 'Time' ] );
384 16         254 my $t = RSH::Logging::TextTable->new( [ 62, 'Event' ], [ 9, 'Time' ] );
385             # while (defined($ptr)) {
386             # $elapsed = tv_interval($ptr->{start}, $ptr->{stop});
387             # $ptr->{elapsed} = sprintf( '%fs', $elapsed );
388             # $t->row(( q{ } x $depth ) . $ptr->{event}, $ptr->{elapsed} || '??');
389             # $ptr = undef;
390             # }
391 16         144 _event_tree_table($t, $results, 0);
392 16         70 my $elapsed = tv_interval($results->{start}, $results->{stop});
393            
394 16 50       295 my $av = sprintf '%.3f', ( $elapsed == 0 ? -1 : ( 1 / $elapsed ) );
395 16 50       80 $av = '??' if ($av < 0);
396            
397             # my $msg = "TOTAL TIME ${elapsed}s ($av/s)\n" . $t->draw . "\n";
398             # if ($logger) {
399             # $logger->debug($msg);
400             # }
401             # else {
402             # print $fh $msg;
403             # }
404 16         33 my $output;
405 16 100       35 if ($logger) {
406             $output = sub {
407 47     47   761 $logger->debug(@_);
408 4         30 };
409             }
410             else {
411             $output = sub {
412 30     30   6004 print $fh @_;
413             }
414 12         63 }
415            
416 16         22 my $table_row_count = @{$t->{columns}->[0]->[1]} - 1; # hack lifted form Text::SimpleTable
  16         45  
417 16 100 100     112 if ($chunk_it or (($AUTO_CHUNK_LIMIT > 0) and ($table_row_count >= $AUTO_CHUNK_LIMIT)) ) {
      66        
418 4         42 $output->("TOTAL TIME ${elapsed}s ($av/s)\n");
419 4         968 $t->draw($output);
420 4         11 $output->("\n");
421             }
422             else {
423 12         133 $output->("TOTAL TIME ${elapsed}s ($av/s)\n" . $t->draw . "\n");
424             }
425             }
426              
427             =item get_event_tracking_results()
428              
429             Get the event tree. The format of the event tree isn't really for
430             public consumption, but if for some reason you wanted to perform your
431             own processing, this is how you would go about doing it.
432              
433             The event tree is a hash of hashes. The general structure is:
434              
435             {
436             event => 'event name',
437             note => 'descriptive note (optional)',
438             start => Time::HiRes::gettimeofday() value,
439             stop => Time::HiRes::gettimeofday() value,
440             children => [array of child event hashes]
441             }
442              
443             This method will return undef if stop_event_tracking has not been called.
444              
445             =cut
446              
447             sub get_event_tracking_results {
448 40     40 1 347 return $results;
449             }
450              
451             =item get_event_count()
452              
453             Returns a count of the number of events. Everytime a start_event is called will
454             increase the count until another event tracking result is created (i.e.
455             stop_event_tracking + start_event_tracking).
456              
457             =cut
458              
459             sub get_event_count {
460 0     0 1 0 return $event_count;
461             }
462              
463             =begin private
464              
465             =cut
466              
467             =item _event_tree_table()
468              
469             TODO _event_tree_table description
470              
471             =cut
472              
473             sub _event_tree_table {
474 95     95   131 my $table = shift;
475 95         105 my $ptr = shift;
476 95         388 my $depth = shift;
477              
478 95         428 my $elapsed = tv_interval($ptr->{start}, $ptr->{stop});
479 95         1801 $ptr->{elapsed} = sprintf( '%fs', $elapsed );
480 95         277 my $event_str = ( q{ } x $depth ) . "+ ". $ptr->{event};
481 95 100       355 $event_str .= " (". $ptr->{note} .")" if defined($ptr->{note});
482 95   50     413 $table->row($event_str, $ptr->{elapsed} || '??');
483 95 100       9410 if (defined($ptr->{children})) {
484 33         47 foreach my $child (@{$ptr->{children}}) {
  33         85  
485 79         301 _event_tree_table($table, $child, $depth + 1);
486             }
487             }
488 95         554 return;
489             }
490              
491             =end private
492              
493             =cut
494              
495             =item start_event([$event_name, $descriptive_note])
496              
497             Starts an event, using the optional event name and descriptive note. If tracking has not
498             been started via start_event_tracking, this methd will return immediately,
499             incurring no more overhead. If event tracking has been started, a new
500             event will be logged in the event tree with its start time (via
501             Time::HiRes::gettimeofday()). If there is already a current event, the new
502             event is added as a child to the current event, the current event is stored
503             on the stack, and the new event becomes the current event.
504              
505             If the event name is not specified, caller() is used to populate the value.
506              
507             =cut
508              
509             sub start_event {
510 97 100   97 1 369 return unless $tracking;
511 96         6043 my $event = shift;
512 96         122 my $note = shift;
513            
514 96 100       249 $event = caller() if not defined($event);
515              
516 96         672 my $new = { event => $event, start => [gettimeofday()], note => $note };
517 96 100       248 if (defined($curr)) {
518 79 50       209 push @parents, $curr if defined($curr);
519 79 100       260 $curr->{children} = [] if not defined($curr->{children});
520 79         148 push @{$curr->{children}}, $new;
  79         191  
521             }
522 96         352 $curr = $new;
523 96 100       205 $event_tree = $curr if not defined($event_tree);
524 96         233 $event_count++;
525             }
526              
527             =item stop_event()
528              
529             Stops the current event (if there is one). If event tracking has not been started,
530             this method returns immediately, incurring no additional overhead. If the
531             current event was a child, the parent is popped from the stack and made the new
532             current event.
533              
534             =cut
535              
536             sub stop_event {
537 97 100   97 1 259 return unless $tracking;
538 96 50       261 return unless $curr; # stop a possible error if stuff is messed up
539            
540 96         498 $curr->{stop} = [gettimeofday()];
541 96         345 $curr = pop @parents;
542             }
543              
544             =back
545              
546             =cut
547              
548             # #################### RSH::Logging.pm ENDS ####################
549             1;
550              
551             =head1 SEE ALSO
552              
553             L<Log::Log4perl>
554              
555             L<http://www.rshtech.com/software/>
556              
557             =head1 AUTHOR
558              
559             Matt Luker C<< <mluker@cpan.org> >>
560              
561             =head1 COPYRIGHT AND LICENSE
562              
563             Copyright 2007 by Matt Luker C<< <mluker@rshtech.com> >>.
564              
565             This library is free software; you can redistribute it and/or modify
566             it under the same terms as Perl itself.
567              
568             =head1 DISCLAIMER OF WARRANTY
569              
570             BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
571             FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
572             OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
573             PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
574             EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
575             WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
576             ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH
577             YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
578             NECESSARY SERVICING, REPAIR, OR CORRECTION.
579              
580             IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
581             WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
582             REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE
583             LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL,
584             OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE
585             THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
586             RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
587             FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
588             SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
589             SUCH DAMAGES.
590              
591             =cut
592              
593             __END__
594             # TTGOG
595              
596             # ---------------------------------------------------------------------
597             # $Log$
598             # ---------------------------------------------------------------------