File Coverage

lib/App/Trace.pm
Criterion Covered Total %
statement 6 120 5.0
branch 0 88 0.0
condition 0 36 0.0
subroutine 2 6 33.3
pod n/a
total 8 250 3.2


line stmt bran cond sub pod time code
1              
2             #############################################################################
3             ## $Id: Trace.pm 6702 2006-07-25 01:43:27Z spadkins $
4             #############################################################################
5              
6             package App::Trace;
7              
8 1     1   747 use vars qw($VERSION);
  1         3  
  1         53  
9 1     1   6 use strict;
  1         2  
  1         2460  
10              
11             $VERSION = "0.50";
12              
13             =head1 NAME
14              
15             App::Trace - Embedded debug statements, including call/return tracing
16              
17             =head1 SYNOPSIS
18              
19             In a program (such as the following one named "foo"), you can do the following...
20              
21             #!/usr/bin/perl
22              
23             use App::Option; # App::Trace was written to be used with App::Options
24             use App::Trace; # enable tracing support
25              
26             use Foo;
27             &run();
28             sub run {
29             &App::sub_entry if ($App::trace); # trace subroutine entry
30             my $foo = Foo->new();
31             $foo->run();
32             &App::sub_exit() if ($App::trace); # trace subroutine exit
33             }
34              
35             in a module (i.e. Foo.pm), you can do the following...
36              
37             package Foo;
38             # new(): a generic object constructor
39             sub new {
40             &App::sub_entry if ($App::trace); # trace method entry
41             my ($this, @args) = @_;
42             my $class = ref($this) || $this;
43             my $self = { @args };
44             bless $self, $class;
45             &App::sub_exit($self) if ($App::trace); # trace method exit
46             return($self);
47             }
48             sub run {
49             &App::sub_entry if ($App::trace); # trace method entry
50             print "Expression: (1 + 2) * (7 - (2*2))\n";
51             my $value = $self->multiply(
52             $self->add(1, 2),
53             $self->subtract(7, $self->multiply(2, 2))
54             );
55             print "Value: $value\n";
56             &App::sub_exit() if ($App::trace); # trace method exit
57             }
58             sub add {
59             &App::sub_entry if ($App::trace); # trace method entry
60             my ($self, $operand1, $operand2) = @_;
61             my $value = $operand1 + $operand2;
62             &App::sub_exit($value) if ($App::trace); # trace method exit
63             return($value);
64             }
65             sub subtract {
66             &App::sub_entry if ($App::trace); # trace method entry
67             my ($self, $operand1, $operand2) = @_;
68             my $value = $operand1 - $operand2;
69             &App::sub_exit($value) if ($App::trace); # trace method exit
70             return($value);
71             }
72             sub multiply {
73             &App::sub_entry if ($App::trace); # trace method entry
74             my ($self, $operand1, $operand2) = @_;
75             my $value = $operand1 * $operand2;
76             &App::sub_exit($value) if ($App::trace); # trace method exit
77             return($value);
78             }
79              
80             Then when you invoke the program normally, you get no debug output.
81             You only get the expected program output.
82              
83             foo
84              
85             However, when you invoke the program with something like the following...
86              
87             foo --trace
88              
89             you get trace output like the following.
90              
91             ...
92              
93             Try the following options...
94              
95             foo --trace --trace_width=0 # unlimited width (long lines wrap on screen)
96             foo --trace --trace_width=78 # set max width of output
97             foo --trace --trace_width=78 --trace_justify # right-justify package
98             foo --trace=main # only trace subs in "main" package
99             foo --trace=Foo # only trace subs in "Foo" package
100             foo --trace=Foo.multiply # only trace the multiply() method in the Foo package
101             foo --trace=main,Foo.run # trace a combo of full packages and specific methods
102              
103             =head1 DESCRIPTION
104              
105             App::Trace provides debug/tracing support for perl programs and modules.
106              
107             The basic concept is that you put a special call at the beginning and
108             end of each subroutine/method, and when tracing is enabled, you can see
109             the flow of your program.
110              
111             This module reflects my dislike of the perl debugger.
112             I also dislike putting in print statements to debug, then commenting
113             them out when I'm done. I would rather put debug statements in my
114             code and leave them there. That way, when programs work their way
115             into production, they can still be debugged by using appropriate
116             command line options.
117              
118             Perl modules which are written to be used with App::Trace can be debugged
119             easily without entering the perl debugger.
120             The output of tracing is a "program trace" which shows the entry and
121             exit of every subroutine/method (and the arguments).
122             This trace is printed in a format which allows you to follow the
123             flow of the program.
124              
125             Someday I might figure out how to do this at a language level so it will
126             work on any module, not just ones which have been specially instrumented
127             with &App::sub_entry() and &App::sub_exit() calls. In fact, I started
128             work on this with the Aspect.pm module, but that was specific to perl
129             version 5.6.x and didn't work with 5.8.x. That's when I decided I would
130             write App::Trace which would work on any Perl (even back to 5.5.3, which
131             I consider to be the first Perl 5 to support for deep backward
132             compatibility).
133              
134             The App-Trace distribution began life as a collection of routines pulled
135             out of the App-Context distribution. I created App-Trace because these
136             routines were very useful independent of the rest of the framework
137             provided by App-Context.
138              
139             App::Trace is dependent on App::Options. It is possible to use App::Trace
140             without App::Options, but they share a common convention with regard to
141             certain global variables in the "App" package/namespace.
142              
143             It is expected that when App::Trace is mature, the routines included will
144             be removed from App.pm module in the App-Context distribution. The
145             App-Context distribution will then be dependent on App::Trace for these
146             features.
147              
148             =cut
149              
150             #############################################################################
151             # ATTRIBUTES/CONSTANTS/CLASS VARIABLES/GLOBAL VARIABLES
152             #############################################################################
153              
154             =head1 Attributes, Constants, Global Variables, Class Variables
155              
156             =head2 Global Variables
157              
158             * Global Variable: %App::scope scope for debug or tracing output
159             * Global Variable: $App::scope_exclusive flag saying that the scope is exclusive (a list of things *not* to debug/trace)
160             * Global Variable: %App::trace trace level
161             * Global Variable: $App::DEBUG debug level
162             * Global Variable: $App::DEBUG_FILE file for debug output
163              
164             =cut
165              
166             if (!defined $App::DEBUG) {
167             %App::scope = ();
168             $App::scope_exclusive = 0;
169             $App::trace = 0;
170             $App::DEBUG = 0;
171             $App::DEBUG_FILE = "";
172             }
173              
174             #################################################################
175             # DEBUGGING
176             #################################################################
177              
178             # Supports the following command-line usage:
179             # --debug=1 (global debug)
180             # --debug=9 (detail debug)
181             # --scope=App::Context (debug class only)
182             # --scope=!App::Context (debug all but this class)
183             # --scope=App::Context,App::Session (multiple classes)
184             # --scope=App::Repository::DBI.select_rows (indiv. methods)
185             # --trace=App::Context (trace class only)
186             # --trace=!App::Context (trace all but this class)
187             # --trace=App::Context,App::Session (multiple classes)
188             # --trace=App::Repository::DBI.select_rows (indiv. methods)
189             {
190             my $scope = $App::options{scope} || "";
191              
192             my $trace = $App::options{trace};
193             if ($trace) {
194             if ($trace =~ s/^([0-9]+),?//) {
195             $App::trace = $1;
196             }
197             else {
198             $App::trace = 9;
199             }
200             }
201             if ($trace) {
202             $scope .= "," if ($scope);
203             $scope .= $trace;
204             }
205             $App::trace_width = (defined $App::options{trace_width}) ? $App::options{trace_width} : 1024;
206             $App::trace_justify = (defined $App::options{trace_justify}) ? $App::options{trace_justify} : 0;
207              
208             my $debug = $App::options{debug};
209             if ($debug) {
210             if ($debug =~ s/^([0-9]+),?//) {
211             $App::DEBUG = $1;
212             }
213             else {
214             $App::DEBUG = 9;
215             }
216             }
217             if ($debug) {
218             $scope .= "," if ($scope);
219             $scope .= $debug;
220             }
221              
222             if ($scope =~ s/^!//) {
223             $App::scope_exclusive = 1;
224             }
225              
226             if (defined $scope && $scope ne "") {
227             foreach my $pkg (split(/,/,$scope)) {
228             $App::scope{$pkg} = 1;
229             }
230             }
231              
232             my $debug_file = $App::options{debug_file};
233             if ($debug_file) {
234             if ($debug_file !~ /^[>|]/) {
235             $debug_file = ">> $debug_file";
236             }
237             open(App::DEBUG_FILE, $debug_file);
238             }
239             }
240              
241             # NOTE: All the functions we define are in the App package!!! (not App::Trace)
242              
243             package App;
244              
245             #############################################################################
246             # Aspect-oriented programming support
247             #############################################################################
248             # NOTE: This can be done much more elegantly at the Perl language level,
249             # but it requires version-specific code. I created these subroutines so that
250             # any method that is instrumented with them will enable aspect-oriented
251             # programming in Perl versions from 5.5.3 to the present.
252             #############################################################################
253              
254             my $calldepth = 0;
255              
256             #############################################################################
257             # sub_entry()
258             #############################################################################
259              
260             =head2 sub_entry()
261              
262             * Signature: &App::sub_entry;
263             * Signature: &App::sub_entry(@args);
264             * Param: @args any
265             * Return: void
266             * Throws: none
267             * Since: 0.01
268              
269             This is called at the beginning of a subroutine or method (even before $self
270             may be shifted off).
271              
272             =cut
273              
274             sub sub_entry {
275 0 0   0     if ($App::trace) {
276 0           my ($stacklevel, $calling_package, $file, $line, $subroutine, $hasargs, $wantarray, $text);
277 0           $stacklevel = 1;
278 0           ($calling_package, $file, $line, $subroutine, $hasargs, $wantarray) = caller($stacklevel);
279 0   0       while (defined $subroutine && $subroutine eq "(eval)") {
280 0           $stacklevel++;
281 0           ($calling_package, $file, $line, $subroutine, $hasargs, $wantarray) = caller($stacklevel);
282             }
283 0           my ($name, $obj, $class, $package, $sub, $method, $firstarg, $trailer);
284              
285             # split subroutine into its "package" and the "sub" within the package
286 0 0         if ($subroutine =~ /^(.*)::([^:]+)$/) {
287 0           $package = $1;
288 0           $sub = $2;
289             }
290              
291             # check if it might be a method call rather than a normal subroutine call
292 0 0         if ($#_ >= 0) {
293 0           $class = ref($_[0]);
294 0 0         if ($class) {
295 0           $obj = $_[0];
296 0 0 0       $method = $sub if ($class ne "ARRAY" && $class ne "HASH");
297             }
298             else {
299 0           $class = $_[0];
300 0 0 0       if ($class =~ /^[A-Z][A-Za-z0-9_:]*$/ && $class->isa($package)) {
301 0           $method = $sub; # the sub is a method call on the class
302             }
303             else {
304 0           $class = ""; # it wasn't really a class/method
305             }
306             }
307             }
308              
309 0 0         if (%App::scope) {
310 0 0         if ($App::scope_exclusive) {
311 0 0 0       return if ($App::scope{$package} || $App::scope{"$package.$sub"});
312             }
313             else {
314 0 0 0       return if (!$App::scope{$package} && !$App::scope{"$package.$sub"});
315             }
316             }
317              
318 0 0         if ($method) {
319 0 0         if (ref($obj)) { # dynamic method, called on an object
320 0 0         if ($obj->isa("App::Service")) {
321 0           $text = ("| " x $calldepth) . "+-" . $obj->{name} . "->${method}(";
322             }
323             else {
324 0           $text = ("| " x $calldepth) . "+-" . $obj . "->${method}(";
325             }
326 0           $trailer = " [$package]";
327             }
328             else { # static method, called on a class
329 0           $text = ("| " x $calldepth) . "+-" . "${class}->${method}(";
330 0 0         $trailer = ($class eq $package) ? "" : " [$package]";
331             }
332 0           $firstarg = 1;
333             }
334             else {
335 0           $text = ("| " x $calldepth) . "+-" . $subroutine . "(";
336 0           $firstarg = 0;
337 0           $trailer = "";
338             }
339 0           my ($narg);
340 0           for ($narg = $firstarg; $narg <= $#_; $narg++) {
341 0 0         $text .= "," if ($narg > $firstarg);
342 0 0         if (!defined $_[$narg]) {
    0          
    0          
    0          
343 0           $text .= "undef";
344             }
345             elsif (ref($_[$narg]) eq "") {
346 0           $text .= $_[$narg];
347             }
348             elsif (ref($_[$narg]) eq "ARRAY") {
349 0 0         $text .= ("[" . join(",", map { defined $_ ? $_ : "undef" } @{$_[$narg]}) . "]");
  0            
  0            
350             }
351             elsif (ref($_[$narg]) eq "HASH") {
352 0 0         $text .= ("{" . join(",", map { defined $_ ? $_ : "undef" } %{$_[$narg]}) . "}");
  0            
  0            
353             }
354             else {
355 0           $text .= $_[$narg];
356             }
357             }
358             #$trailer .= " [package=$package sub=$sub subroutine=$subroutine class=$class method=$method]";
359 0           $text .= ")";
360 0           my $trailer_len = length($trailer);
361 0           $text =~ s/\n/\\n/g;
362 0           my $text_len = length($text);
363 0 0         if ($App::trace_width) {
364 0 0         if ($text_len + $trailer_len > $App::trace_width) {
    0          
365 0           my $len = $App::trace_width - $trailer_len;
366 0 0         $len = 1 if ($len < 1);
367 0           print substr($text, 0, $len), $trailer, "\n";
368             }
369             elsif ($App::trace_justify) {
370 0           my $len = $App::trace_width - $trailer_len - $text_len;
371 0 0         $len = 0 if ($len < 0); # should never happen
372 0           print $text, ("." x $len), $trailer, "\n";
373             }
374             else {
375 0           print $text, $trailer, "\n";
376             }
377             }
378             else {
379 0           print $text, $trailer, "\n";
380             }
381 0           $calldepth++;
382             }
383             }
384              
385             #############################################################################
386             # sub_exit()
387             #############################################################################
388              
389             =head2 sub_exit()
390              
391             * Signature: &App::sub_exit(@return);
392             * Param: @return any
393             * Return: void
394             * Throws: none
395             * Since: 0.01
396              
397             This subroutine is called just before you return from a subroutine or method.
398             =cut
399              
400             sub sub_exit {
401 0 0   0     if ($App::trace) {
402 0           my ($stacklevel, $calling_package, $file, $line, $subroutine, $hasargs, $wantarray, $text);
403 0           $stacklevel = 1;
404 0           ($calling_package, $file, $line, $subroutine, $hasargs, $wantarray) = caller($stacklevel);
405 0   0       while (defined $subroutine && $subroutine eq "(eval)") {
406 0           $stacklevel++;
407 0           ($calling_package, $file, $line, $subroutine, $hasargs, $wantarray) = caller($stacklevel);
408             }
409              
410 0           my ($package, $sub);
411             # split subroutine into its "package" and the "sub" within the package
412 0 0         if ($subroutine =~ /^(.*)::([^:]+)$/) {
413 0           $package = $1;
414 0           $sub = $2;
415             }
416              
417 0 0 0       return if (%App::scope && !$App::scope{$package} && !$App::scope{"$package.$sub"});
      0        
418              
419 0           $calldepth--;
420 0           $text = ("| " x $calldepth) . "+-> $sub()";
421 0           my ($narg, $arg);
422 0           for ($narg = 0; $narg <= $#_; $narg++) {
423 0 0         $text .= $narg ? "," : " : ";
424 0           $arg = $_[$narg];
425 0 0         if (! defined $arg) {
    0          
    0          
    0          
426 0           $text .= "undef";
427             }
428             elsif (ref($arg) eq "") {
429 0           $text .= $arg;
430             }
431             elsif (ref($arg) eq "ARRAY") {
432 0 0         $text .= ("[" . join(",", map { defined $_ ? $_ : "undef" } @$arg) . "]");
  0            
433             }
434             elsif (ref($arg) eq "HASH") {
435 0 0         $text .= ("{" . join(",", map { defined $_ ? $_ : "undef" } %$arg) . "}");
  0            
436             }
437             else {
438 0 0         $text .= defined $arg ? $arg : "undef";
439             }
440             }
441 0           $text =~ s/\n/\\n/g;
442 0 0 0       if ($App::trace_width && length($text) > $App::trace_width) {
443 0           print substr($text, 0, $App::trace_width), "\n";
444             }
445             else {
446 0           print $text, "\n";
447             }
448             }
449 0           return(@_);
450             }
451              
452             #############################################################################
453             # in_debug_scope()
454             #############################################################################
455              
456             =head2 in_debug_scope()
457              
458             * Signature: &App::in_debug_scope
459             * Signature: App->in_debug_scope
460             * Param:
461             * Return: void
462             * Throws: none
463             * Since: 0.01
464              
465             This is called within a subroutine or method in order to see if debug output
466             should be produced.
467              
468             if ($App::debug && &App::in_debug_scope) {
469             print "This is debug output\n";
470             }
471              
472             Note: The App::in_debug_scope subroutine also checks $App::debug, but checking
473             it in your code allows you to skip the subroutine call if you are not debugging.
474              
475             if (&App::in_debug_scope) {
476             print "This is debug output\n";
477             }
478              
479             =cut
480              
481             sub in_debug_scope {
482 0 0   0     if ($App::debug) {
483 0           my ($stacklevel, $calling_package, $file, $line, $subroutine, $hasargs, $wantarray, $text);
484 0           $stacklevel = 1;
485 0           ($calling_package, $file, $line, $subroutine, $hasargs, $wantarray) = caller($stacklevel);
486 0   0       while (defined $subroutine && $subroutine eq "(eval)") {
487 0           $stacklevel++;
488 0           ($calling_package, $file, $line, $subroutine, $hasargs, $wantarray) = caller($stacklevel);
489             }
490 0           my ($package, $sub);
491              
492             # split subroutine into its "package" and the "sub" within the package
493 0 0         if ($subroutine =~ /^(.*)::([^:]+)$/) {
494 0           $package = $1;
495 0           $sub = $2;
496             }
497              
498 0 0         if (%App::scope) {
499 0 0         if ($App::scope_exclusive) {
500 0 0 0       return(undef) if ($App::scope{$package} || $App::scope{"$package.$sub"});
501             }
502             else {
503 0 0 0       return(undef) if (!$App::scope{$package} && !$App::scope{"$package.$sub"});
504             }
505             }
506 0           return(1);
507             }
508 0           return(undef);
509             }
510              
511             #############################################################################
512             # debug_indent()
513             #############################################################################
514              
515             =head2 debug_indent()
516              
517             * Signature: &App::debug_indent()
518             * Signature: App->debug_indent()
519             * Param: void
520             * Return: $indent_str string
521             * Throws: none
522             * Since: 0.01
523              
524             This subroutine returns the $indent_str string which should be printed
525             before all debug lines if you wish to line the debug output up with the
526             nested/indented trace output.
527              
528             =cut
529              
530             sub debug_indent {
531 0     0     my $text = ("| " x $calldepth) . " * ";
532 0           return($text);
533             }
534              
535              
536             =head1 ACKNOWLEDGEMENTS
537              
538             * Author: Stephen Adkins
539             * License: This is free software. It is licensed under the same terms as Perl itself.
540              
541             =head1 SEE ALSO
542              
543             =cut
544              
545             1;
546