File Coverage

blib/lib/Debug/Easy.pm
Criterion Covered Total %
statement 177 239 74.0
branch 63 106 59.4
condition 9 24 37.5
subroutine 14 25 56.0
pod 12 12 100.0
total 275 406 67.7


line stmt bran cond sub pod time code
1             package Debug::Easy 2.26;
2              
3 1     1   105220 use strict;
  1         2  
  1         47  
4             # use warnings;
5             use constant {
6 1         68 TRUE => 1,
7             FALSE => 0,
8 1     1   4 };
  1         1  
9              
10 1     1   4 use Config;
  1         1  
  1         35  
11 1     1   3 use Term::ANSIColor;
  1         1  
  1         53  
12 1     1   4 use Time::HiRes qw(time);
  1         1  
  1         6  
13 1     1   52 use File::Basename qw(fileparse);
  1         1  
  1         41  
14              
15 1     1   487 use Data::Dumper; # Included in Perl
  1         6473  
  1         82  
16             eval { # Data::Dumper::Simple is preferred. Try to load it without dying.
17             require Data::Dumper::Simple;
18             Data::Dumper::Simple->import();
19             1;
20             };
21              
22 1     1   6 use if ($Config{'useithreads'}), 'threads';
  1         1  
  1         68  
23              
24             BEGIN {
25 1     1   3 require Exporter;
26              
27             # Inherit from Exporter to export functions and variables
28 1         15 our @ISA = qw(Exporter);
29              
30             # Functions and variables which are exported by default
31 1         1 our @EXPORT = qw();
32              
33             # Functions and variables which can be optionally exported
34 1         2663 our @EXPORT_OK = qw(fileparse @Levels);
35             } ## end BEGIN
36              
37             # This can be optionally exported for whatever
38             our @Levels = qw( ERR WARN NOTICE INFO VERBOSE DEBUG DEBUGMAX );
39              
40             # For quick level checks to speed up execution
41             our %LevelLogic;
42             for (my $count = 0; $count < scalar(@Levels); $count++) {
43             $LevelLogic{ $Levels[$count] } = $count;
44             }
45              
46             our $PARENT = $$; # This needs to be defined at the very beginning before new
47             our ($SCRIPTNAME, $SCRIPTPATH, $suffix) = fileparse($0);
48             # our @months = qw( Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec );
49             # our @days = qw(Sun Mon Tue Wed Thu Fri Sat Sun);
50              
51             =encoding utf8
52              
53             =head1 NAME
54              
55             Debug::Easy - A Handy Debugging Module With Colorized Output and Formatting
56              
57             =head1 SYNOPSIS
58              
59             use Debug::Easy;
60              
61             my $debug = Debug::Easy->new( 'LogLevel' => 'DEBUG', 'Color' => 1 );
62              
63             'LogLevel' is the maximum level to report, and ignore the rest. The method names correspond to their loglevels, when outputting a specific message. This identifies to the module what type of message [...]
64              
65             The following is a list, in order of level, of the logging methods:
66              
67             ERR or ERROR = Error
68             WARN or WARNING = Warning
69             NOTICE or ATTENTION = Notice
70             INFO or INFORMATION = Information
71             VERBOSE = Special version of INFO that does not output any
72             Logging headings. Very useful for verbose modes in your
73             scripts.
74             DEBUG = Level 1 Debugging messages
75             DEBUGMAX = Level 2 Debugging messages (typically more terse like dumping
76             variables)
77              
78             The parameter is either a string or a reference to an array of strings to output as multiple lines.
79              
80             Each string can contain newlines, which will also be split into a separate line and formatted accordingly.
81              
82             $debug->ERR( ['Error message']);
83             $debug->ERROR( ['Error message']);
84              
85             $debug->WARN( ['Warning message']);
86             $debug->WARNING( ['Warning message']);
87              
88             $debug->NOTICE( ['Notice message']);
89             $debug->ATTENTION( ['Notice message']);
90              
91             $debug->INFO( ['Information and VERBOSE mode message']);
92             $debug->INFORMATION(['Information and VERBOSE mode message']);
93              
94             $debug->DEBUG( ['Level 1 Debug message']);
95             $debug->DEBUGMAX( ['Level 2 (terse) Debug message']);
96              
97             my @messages = (
98             'First Message',
99             'Second Message',
100             "Third Message First Line\nThird Message Second Line",
101             \%hash_reference
102             );
103              
104             $debug->INFO([\@messages]);
105              
106             =head1 DESCRIPTION
107              
108             This module makes it easy to add debugging features to your code, Without having to re-invent the wheel. It uses STDERR and ANSI color formatted text output, as well as indented and multiline text fo [...]
109              
110             Benchmarking is automatic, to make it easy to spot bottlenecks in code. It automatically stamps from where it was called, and makes debug coding so much easier, without having to include the location [...]
111              
112             It also allows multiple output levels from errors only, to warnings, to notices, to verbose information, to full on debug output. All of this fully controllable by the coder.
113              
114             Generally all you need are the defaults and you are ready to go.
115              
116             =head1 B
117              
118             =head2 B<@Levels>
119              
120             A simple list of all the acceptable debug levels to pass as "LogLevel" in the {new} method. Not normally needed for coding, more for reference. Only exported if requested.
121              
122             =cut
123              
124             sub DESTROY { # We spit out one last message before we die, the total execute time.
125 6     6   3510 my $self = shift;
126             my $bench = (! $self->{'COLOR'})
127             ? sprintf('%06.2f', (time - $self->{'MASTERSTART'}))
128 6 50       113 : colored(['bright_cyan'], sprintf('%06.2f', (time - $self->{'MASTERSTART'})));
129 6         355 my $name = $SCRIPTNAME;
130 6 50       50 $name .= ' [child]' if ($PARENT ne $$);
131 6 50       20 unless ($self->{'COLOR'}) {
132 0         0 $self->DEBUG(["$bench ---- $name complete ----"]);
133             } else {
134 6         66 $self->DEBUG([$bench . ' ' . colored(['black on_white'], "---- $name complete ----")]);
135             }
136             } ## end sub DESTROY
137              
138             =head1 B
139              
140             =head2 B
141              
142             * The parameter names are case insensitive as of Version 0.04.
143              
144             =over 4
145              
146             =item B [level]
147              
148             This adjusts the global log level of the Debug object. It requires a string.
149              
150             =back
151              
152             =over 8
153              
154             B (default)
155              
156             This level shows only error messages and all other messages are not shown.
157              
158             B
159              
160             This level shows error and warning messages. All other messages are not shown.
161              
162             B
163              
164             This level shows error, warning, and notice messages. All other messages are not shown.
165              
166             B
167              
168             This level shows error, warning, notice, and information messages. Only debug level messages are not shown.
169              
170             B
171              
172             This level can be used as a way to do "Verbose" output for your scripts. It ouputs INFO level messages without logging headers and on STDOUT instead of STDERR.
173              
174             B
175              
176             This level shows error, warning, notice, information, and level 1 debugging messages. Level 2 Debug messages are not shown.
177              
178             B
179              
180             This level shows all messages up to level 2 debugging messages.
181              
182             NOTE: It has been asked "Why have two debugging levels?" Well, I have had many times where I would like to see what a script is doing without it showing what I consider garbage overhead it may gene [...]
183              
184             =back
185              
186             =over 4
187              
188             =item B [boolean] (Not case sensitive)
189              
190             B<0>, B, or B (Off)
191              
192             This turns off colored output. Everything is plain text only.
193              
194             B<1>, B, or B (On - Default)
195              
196             This turns on colored output. This makes it easier to spot all of the different types of messages throughout a sea of debug output. You can read the output with "less", and see color, by using it' [...]
197              
198             =back
199              
200             =over 4
201              
202             =item B [pattern]
203              
204             This is global
205              
206             A string that is parsed into the output prefix.
207              
208             DEFAULT: '%Date% %Time% %Benchmark% %Loglevel%[%Subroutine%][%Lastline%] '
209              
210             %Date% = Date (Uses format of "DateStamp" below)
211             %Time% = Time (Uses format of "TimeStamp" below)
212             %Epoch% = Epoch (Unix epoch)
213             %Benchmark% = Benchmark - The time it took between the last benchmark display
214             of this loglevel. If in an INFO level message, it benchmarks
215             the time until the next INFO level message. The same rule is
216             true for all loglevels.
217             %Loglevel% = Log Level
218             %Lines% = Line Numbers of all nested calls
219             %Module% = Module and subroutine of call (can be a lot of stuff!)
220             %Subroutine% = Just the last subroutine
221             %Lastline% = Just the last line number
222             %PID% = Process ID
223             %date% = Just Date (typically used internally only, use %Date%)
224             %time% = Just time (typically used internally only, use %Time%)
225             %epoch% = Unix epoch (typically used internally only, use %Epoch%)
226             %Filename% = Script Filename (parsed $0)
227             %Fork% = Running in parent or child?
228             P = Parent
229             C = Child
230             %Thread% = Running in Parent or Thread
231             P = Parent
232             T## = Thread # = Thread ID
233              
234             =item B<[loglevel]-Prefix> [pattern]
235              
236             You can define a prefix for a specific log level.
237              
238             ERR-Prefix
239             WARN-Prefix
240             NOTICE-Prefix
241             INFO-Prefix
242             DEBUG-Prefix
243             DEBUGMAX-Prefix
244              
245             If one of these are not defined, then the global value is used.
246              
247             =item B [pattern]
248              
249             (See Log::Fast for specifics on these)
250              
251             I suggest you just use Prefix above, but here it is anyway.
252              
253             Make this an empty string to turn it off, otherwise:
254              
255             =back
256              
257             =over 8
258              
259             B<%T>
260              
261             Formats the timestamp as HH:MM:SS. This is the default for the timestamp.
262              
263             B<%S>
264              
265             Formats the timestamp as seconds.milliseconds. Normally not needed, as the benchmark is more helpful.
266              
267             B<%T %S>
268              
269             Combines both of the above. Normally this is just too much, but here if you really want it.
270              
271             =back
272              
273             =over 4
274              
275             =item B [pattern]
276              
277             I suggest you just use Prefix above, but here it is anyway.
278              
279             Make this an empty string to turn it off, otherwise:
280              
281             =back
282              
283             =over 8
284              
285             B<%D>
286              
287             Formats the datestamp as YYYY-MM-DD. It is the default, and the only option.
288              
289             =back
290              
291             =over 4
292              
293             =item B
294              
295             File handle to write log messages.
296              
297             =item B
298              
299             Contains a hash reference describing the various colored debug level labels
300              
301             The default definition (using Term::ANSIColor) is as follows:
302              
303             =back
304              
305             =over 8
306              
307             'ANSILevel' => {
308             'ERR' => colored(['white on_red'], '[ ERROR ]'),
309             'WARN' => colored(['black on_yellow'], '[WARNING ]'),
310             'NOTICE' => colored(['yellow'], '[ NOTICE ]'),
311             'INFO' => colored(['black on_white'], '[ INFO ]'),
312             'DEBUG' => colored(['bold green'], '[ DEBUG ]'),
313             'DEBUGMAX' => colored(['bold black on_green'], '[DEBUGMAX]'),
314             }
315              
316             =back
317              
318             =cut
319              
320             sub new {
321 6     6 1 148264 my $class = shift;
322 6         229 my ($filename, $dir, $suffix) = fileparse($0);
323 6         26 my $tm = time;
324 6         30 my $self = {
325             'LogLevel' => 'ERR', # Default is errors only
326             'Type' => 'fh', # Default is a filehandle
327             'Path' => '/var/log', # Default path should type be unix
328             'FileHandle' => \*STDERR, # Default filehandle is STDERR
329             'MasterStart' => $tm,
330             'ANY_LastStamp' => $tm, # Initialize main benchmark
331             'ERR_LastStamp' => $tm, # Initialize the ERR benchmark
332             'WARN_LastStamp' => $tm, # Initialize the WARN benchmark
333             'INFO_LastStamp' => $tm, # Initialize the INFO benchmark
334             'NOTICE_LastStamp' => $tm, # Initialize the NOTICE benchmark
335             'DEBUG_LastStamp' => $tm, # Initialize the DEBUG benchmark
336             'DEBUGMAX_LastStamp' => $tm, # Initialize the DEBUGMAX benchmark
337             'Color' => TRUE, # Default to colorized output
338             'DateStamp' => colored(['yellow'], '%date%'),
339             'TimeStamp' => colored(['yellow'], '%time%'),
340             'Epoch' => colored(['cyan'], '%epoch%'),
341             'Padding' => -20, # Default padding is 20 spaces
342             'Lines-Padding' => -2,
343             'Subroutine-Padding' => 0,
344             'Line-Padding' => 0,
345             'PARENT' => $$,
346             'Prefix' => '%Date% %Time% %Benchmark% %Loglevel%[%Subroutine%][%Lastline%] ',
347             'DEBUGMAX-Prefix' => '%Date% %Time% %Benchmark% %Loglevel%[%Module%][%Lines%] ',
348             'Filename' => '[' . colored(['magenta'], $filename) . ']',
349             # 'TIMEZONE' => DateTime::TimeZone->new(name => 'local'),
350             # 'DATETIME' => DateTime->now('time_zone' => DateTime::TimeZone->new(name => 'local')),
351             'ANSILevel' => {
352             'ERR' => colored(['white on_red'], '[ ERROR ]'),
353             'WARN' => colored(['black on_yellow'], '[WARNING ]'),
354             'NOTICE' => colored(['yellow'], '[ NOTICE ]'),
355             'INFO' => colored(['black on_white'], '[ INFO ]'),
356             'DEBUG' => colored(['bold green'], '[ DEBUG ]'),
357             'DEBUGMAX' => colored(['bold black on_green'], '[DEBUGMAX]'),
358             },
359             };
360              
361             # This pretty much makes all hash keys uppercase
362 6         1824 my @Keys = (keys %{$self}); # Hash is redefined on the fly, so get the list before
  6         32  
363 6         13 foreach my $Key (@Keys) {
364 150         136 my $upper = uc($Key);
365 150 100       188 if ($Key ne $upper) {
    50          
366 144         201 $self->{$upper} = $self->{$Key};
367              
368             # This fixes a documentation error for past versions
369 144 100       196 if ($upper eq 'LOGLEVEL') {
370 6 50       27 $self->{$upper} = 'ERR' if ($self->{$upper} =~ /^ERROR$/i);
371 6         11 $self->{$upper} = uc($self->{$upper}); # Make loglevels case insensitive
372             }
373 144         190 delete($self->{$Key}); # Get rid of the bad key
374             } elsif ($Key eq 'LOGLEVEL') { # Make loglevels case insensitive
375 0         0 $self->{$upper} = uc($self->{$upper});
376             }
377             } ## end foreach my $Key (@Keys)
378             { # This makes sure the user overrides actually override
379 6         11 my %params = (@_);
  6         21  
380 6         16 foreach my $Key (keys %params) {
381 18         39 $self->{ uc($Key) } = $params{$Key};
382             }
383             }
384              
385             # Cache numeric log level value for quick comparisons
386 6         17 $self->{'LOGLEVEL_VALUE'} = $LevelLogic{ $self->{'LOGLEVEL'} };
387              
388             # Cache thread support check for hot path
389 6 50 33     90 my $use_threads = ($Config{'useithreads'} && eval { require threads; 1 }) ? 1 : 0;
390 6         15 $self->{'USE_THREADS'} = $use_threads;
391              
392             # This instructs the ANSIColor library to turn off coloring,
393             # if the Color attribute is set to zero.
394 6 50       14 unless ($self->{'COLOR'}) {
395             # local $ENV{'ANSI_COLORS_DISABLED'} = TRUE; # Only this module should be set
396              
397             # If COLOR is FALSE, then clear color data from ANSILEVEL, as these were
398             # defined before color was turned off.
399 0         0 $self->{'ANSILEVEL'} = {
400             'ERR' => '[ ERROR ]',
401             'WARN' => '[WARNING ]',
402             'NOTICE' => '[ NOTICE ]',
403             'INFO' => '[ INFO ]',
404             'DEBUG' => '[ DEBUG ]',
405             'DEBUGMAX' => '[DEBUGMAX]',
406             };
407 0         0 $self->{'DATESTAMP'} = '%date%';
408 0         0 $self->{'TIMESTAMP'} = '%time%';
409 0         0 $self->{'EPOCH'} = '%epoch%';
410 0         0 $self->{'FILENAME'} = '[' . $filename . ']'; # Ensure filename without color
411             }
412              
413 6         13 foreach my $lvl (@Levels) {
414 42 100 66     133 $self->{"$lvl-PREFIX"} = $self->{'PREFIX'} unless (exists($self->{"$lvl-PREFIX"}) && defined($self->{"$lvl-PREFIX"}));
415             }
416              
417             # Precompute static prefix templates per level to minimize per-line substitutions.
418             # We will leave dynamic tokens (%date%, %time%, %epoch%, %Benchmark%) for runtime.
419 6         13 $self->{'_PREFIX_TEMPLATES'} = {};
420 6         11 foreach my $lvl (@Levels) {
421 42         55 my $tmpl = $self->{"$lvl-PREFIX"} . ''; # copy
422 42 50       112 my $forked = ($PARENT ne $$) ? 'C' : 'P';
423 42         42 my $threaded = 'PT-';
424 42 50       57 if ($self->{'USE_THREADS'}) {
425 0 0       0 my $tid = threads->can('tid') ? threads->tid() : 0;
426 0 0 0     0 $threaded = ($tid && $tid > 0) ? sprintf('T%02d', $tid) : 'PT-';
427             }
428              
429             # Static substitutions
430 42         101 $tmpl =~ s/\%PID\%/$$/gi;
431 42         166 $tmpl =~ s/\%Loglevel\%/$self->{'ANSILEVEL'}->{$lvl}/gi;
432 42         88 $tmpl =~ s/\%Filename\%/$self->{'FILENAME'}/gi;
433 42         78 $tmpl =~ s/\%Fork\%/$forked/gi;
434 42         93 $tmpl =~ s/\%Thread\%/$threaded/gi;
435              
436             # Leave dynamic tokens for runtime:
437             # %Lines%, %Lastline%, %Subroutine%, %Module% (caller-dependent)
438             # %Date%, %Time%, %Epoch% (colorized stamp placeholders)
439             # %date%, %time%, %epoch% (raw values)
440             # %Benchmark%
441              
442 42         94 $self->{'_PREFIX_TEMPLATES'}->{$lvl} = $tmpl;
443             }
444              
445 6         7 my $fh = $self->{'FILEHANDLE'};
446              
447             # Signal the script has started (and logger initialized)
448 6         13 my $name = $SCRIPTNAME;
449 6 50       19 $name .= ' [child]' if ($PARENT ne $$);
450 6 50       37 my $string = (! $self->{'COLOR'}) ? "----- $name begin -----" : colored(['black on_white'], "----- $name begin -----");
451 6 100       306 print $fh sprintf(' %.02f%s %s%s', 0, $self->{'ANSILEVEL'}->{'DEBUG'}, $string, " (To View in 'less', use it's '-r' switch)"), "\n" if ($self->{'LOGLEVEL'} !~ /ERR/);
452              
453 6         15 bless($self, $class);
454 6         39 return ($self);
455             } ## end sub new
456              
457             =head2 debug
458              
459             NOTE: This is a legacy method for backwards compatibility. Please use the direct methods instead.
460              
461             The parameters must be passed in the order given
462              
463             =over 4
464              
465             =item B
466              
467             The log level with which this message is to be triggered
468              
469             =item B
470              
471             A string or a reference to a list of strings to output line by line.
472              
473             =back
474              
475             =cut
476              
477             sub debug {
478 114     114 1 68284 my $self = shift;
479 114         248 my $level = uc(shift);
480 114         188 my $msgs = shift;
481              
482 114 50       903 if ($level !~ /ERR.*|WARN.*|NOTICE|INFO.*|DEBUG/i) { # Compatibility with older versions.
483 0         0 $level = uc($msgs); # It tosses the legacy __LINE__ argument
484 0         0 $msgs = shift;
485             }
486 114         458 $level =~ s/(OR|ING|RMATION)$//; # Strip off the excess
487              
488             # A much quicker bypass when the log level is below what is needed
489             # This minimizes the execution overhead for log levels not active.
490 114 100       531 return if ($self->{'LOGLEVEL_VALUE'} < $LevelLogic{$level});
491              
492 65         101 my @messages;
493 65 100 66     349 if (ref($msgs) eq 'SCALAR' || ref($msgs) eq '') {
    50          
494 42         93 push(@messages, $msgs);
495             } elsif (ref($msgs) eq 'ARRAY') {
496 23         36 @messages = @{$msgs};
  23         93  
497             } else {
498 0         0 push(@messages, _send_to_dumper($msgs));
499             }
500 65         191 my ($sname, $cline, $nested, $subroutine, $thisBench, $thisBench2, $sline, $short) = ('', '', '', '', '', '', '', '');
501              
502             # Figure out the proper caller tree and line number ladder
503             # But only if it's part of the effective level prefix, else don't waste time.
504 65   33     188 my $effective_prefix = $self->{ $level . '-PREFIX' } || $self->{'PREFIX'};
505 65 50       410 if ($effective_prefix =~ /\%(Subroutine|Module|Lines|Lastline)\%/i) { # %P = Subroutine, %l = Line number(s)
506 65         90 my $package = '';
507 65         108 my $count = 1;
508 65         93 my $nest = 0;
509 65         200 while (my @array = caller($count)) {
510 6 100       17 if ($array[3] !~ /Debug::Easy/) {
511 2         4 $package = $array[0];
512 2         3 my $subroutine = $array[3];
513 2         35 $subroutine =~ s/^$package\:\://;
514 2         14 $sname =~ s/$subroutine//;
515 2 50       6 if ($sname eq '') {
516 2 50       8 $sname = ($subroutine ne '') ? $subroutine : $package;
517 2         3 $cline = $array[2];
518             } else {
519 0         0 $sname = $subroutine . '::' . $sname;
520 0         0 $cline = $array[2] . '/' . $cline;
521             }
522 2 50       6 if ($count == 2) {
523 0         0 $short = $array[3];
524 0         0 $sline = $array[2];
525             }
526 2         4 $nest++;
527             } ## end if ($array[3] !~ /Debug::Easy/)
528 6         28 $count++;
529             } ## end while (my @array = caller...)
530 65 100       129 if ($package ne '') {
531 2         5 $sname = $package . '::' . $sname;
532 2 50       6 $nested = ' ' x $nest if ($nest);
533             } else {
534 63         107 my @array = caller(1);
535 63         96 $cline = $array[2];
536 63 50 33     144 if (!defined($cline) || $cline eq '') {
537 63         327 @array = caller(0);
538 63         148 $cline = $array[2];
539             }
540 63         85 $sname = 'main';
541 63         94 $sline = $cline;
542 63         147 $short = $sname;
543             } ## end else [ if ($package ne '') ]
544 65 50       132 $subroutine = ($sname ne '') ? $sname : 'main';
545 65 50       195 $self->{'PADDING'} = 0 - length($subroutine) if (length($subroutine) > abs($self->{'PADDING'}));
546 65 50       162 $self->{'LINES-PADDING'} = 0 - length($cline) if (length($cline) > abs($self->{'LINES-PADDING'}));
547 65 100       163 $self->{'SUBROUTINE-PADDING'} = 0 - length($short) if (length($short) > abs($self->{'SUBROUTINE-PADDING'}));
548 65 100       181 $self->{'LINE-PADDING'} = 0 - length($sline) if (length($sline) > abs($self->{'LINE-PADDING'}));
549 65         219 $cline = sprintf('%' . $self->{'LINES-PADDING'} . 's', $cline);
550 65 50       374 $subroutine = (! $self->{'COLOR'}) ? sprintf('%' . $self->{'PADDING'} . 's', $subroutine) : colored(['bold cyan'], sprintf('%' . $self->{'PADDING'} . 's', $subroutine));
551 65         3546 $sline = sprintf('%' . $self->{'LINE-PADDING'} . 's', $sline);
552 65 50       252 $short = (! $self->{'COLOR'}) ? sprintf('%' . $self->{'SUBROUTINE-PADDING'} . 's', $short) : colored(['bold cyan'], sprintf('%' . $self->{'SUBROUTINE-PADDING'} . 's', $short));
553             } ## end if ($effective_prefix ...)
554              
555             # Figure out the benchmarks, but only if it is in the prefix
556 65 50       3215 if ($effective_prefix =~ /\%Benchmark\%/i) {
557              
558             # For multiline output, only output the bench data on the first line. Use padded spaces for the rest.
559 65         536 $thisBench = sprintf('%7s', sprintf(' %.02f', time - $self->{'ANY_LASTSTAMP'}));
560 65         137 $thisBench2 = ' ' x length($thisBench);
561             } ## end if ($effective_prefix ...)
562 65         85 my $first = TRUE; # Set the first line flag.
563              
564             # Buffer lines to reduce syscalls for multi-line messages
565 65         101 my $buffer = '';
566              
567 65         117 foreach my $msg (@messages) { # Loop through each line of output and format accordingly.
568 107 50       254 if (ref($msg) ne '') {
569 0         0 $msg = _send_to_dumper($msg);
570             }
571 107 100       240 if ($msg =~ /\n/s) { # If the line contains newlines, then it too must be split into multiple lines.
572 21         59 my @message = split(/\n/, $msg);
573 21         37 foreach my $line (@message) { # Loop through the split lines and format accordingly.
574 42         102 $buffer .= $self->_format_line($level, $nested, $line, $first, $thisBench, $thisBench2, $subroutine, $cline, $sline, $short);
575 42         59 $buffer .= "\n";
576 42         93 $first = FALSE; # Clear the first line flag.
577             }
578             } else { # This line does not contain newlines. Treat it as a single line.
579 86         213 $buffer .= $self->_format_line($level, $nested, $msg, $first, $thisBench, $thisBench2, $subroutine, $cline, $sline, $short);
580 86         147 $buffer .= "\n";
581             }
582 107         190 $first = FALSE; # Clear the first line flag.
583             } ## end foreach my $msg (@messages)
584              
585 65         98 my $fh = $self->{'FILEHANDLE'};
586 65 50 66     189 if ($level eq 'INFO' && $self->{'LOGLEVEL'} eq 'VERBOSE') { # Trap verbose flag and temporarily drop the prefix.
    100          
587             # For verbose, we need to print messages without prefixes.
588             # Extract lines and print only message contents.
589 0         0 foreach my $msg (@messages) {
590 0 0       0 if (ref($msg) ne '') {
591 0         0 $msg = _send_to_dumper($msg);
592             }
593 0 0       0 if ($msg =~ /\n/s) {
594 0         0 my @message = split(/\n/, $msg);
595 0         0 foreach my $line (@message) {
596 0         0 print $fh "$line\n";
597             }
598             } else {
599 0         0 print $fh "$msg\n";
600             }
601             }
602             } elsif ($level eq 'DEBUGMAX') { # Special version of DEBUG. Extremely verbose debugging and quite noisy
603 3 50       7 if ($self->{'LOGLEVEL'} eq 'DEBUGMAX') {
604 3         11 print $fh $buffer;
605             }
606             } else {
607 62         293 print $fh $buffer;
608             }
609              
610 65         132 $self->{'ANY_LASTSTAMP'} = time;
611 65         344 $self->{ $level . '_LASTSTAMP' } = time;
612             } ## end sub debug
613              
614             sub _send_to_dumper {
615 0     0   0 my $msg = shift;
616             # Set up dumper variables for friendly output that doesn't interfere with the script Dumpor variables.
617 0         0 local $Data::Dumper::Terse = TRUE;
618 0         0 local $Data::Dumper::Indent = TRUE;
619 0         0 local $Data::Dumper::Useqq = TRUE;
620 0         0 local $Data::Dumper::Deparse = TRUE;
621 0         0 local $Data::Dumper::Quotekeys = TRUE;
622 0         0 local $Data::Dumper::Trailingcomma = TRUE;
623 0         0 local $Data::Dumper::Sortkeys = TRUE;
624 0         0 local $Data::Dumper::Purity = TRUE;
625 0         0 return(Dumper($msg));
626             }
627              
628             # Internal: format a single line for logging (without printing)
629             sub _format_line {
630 128     128   353 my ($self, $level, $padding, $msg, $first, $thisBench, $thisBench2, $subroutine, $cline, $sline, $shortsub) = @_;
631              
632             # Build prefix based on precomputed template and runtime substitutions
633 128         243 my $tmpl = $self->{'_PREFIX_TEMPLATES'}->{$level};
634 128 50       244 $tmpl = $self->{"$level-PREFIX"} . '' unless defined $tmpl; # Fallback safety
635              
636             # Clone template since we mutate
637 128         169 my $prefix = $tmpl . '';
638              
639             # Apply caller-derived fields only if present in the effective level prefix
640 128 100       450 if ($prefix =~ /\%Lines\%/i) { $prefix =~ s/\%Lines\%/$cline/gi; }
  6         28  
641 128 100       435 if ($prefix =~ /\%Lastline\%/i) { $prefix =~ s/\%Lastline\%/$sline/gi; }
  122         461  
642 128 100       389 if ($prefix =~ /\%Subroutine\%/i){ $prefix =~ s/\%Subroutine\%/$shortsub/gi; }
  122         334  
643 128 100       338 if ($prefix =~ /\%Module\%/i) { $prefix =~ s/\%Module\%/$subroutine/gi; }
  6         19  
644              
645 128         1959 my ($sec,$min,$hour,$mday,$mon,$year) = localtime();
646 128         512 my $Date = sprintf('%02d/%02d/%04d', $mday, ($mon + 1), (1900 + $year));
647 128         263 my $Time = sprintf('%02d:%02d:%02d', $hour, $min, $sec);
648 128         261 my $epoch = time;
649              
650             # Apply dynamic tokens
651 128 100       209 if ($first) {
652 65         281 $prefix =~ s/\%Benchmark\%/$thisBench/gi;
653             } else {
654 63         205 $prefix =~ s/\%Benchmark\%/$thisBench2/gi;
655             }
656 128         650 $prefix =~ s/\%Date\%/$self->{'DATESTAMP'}/gi;
657 128         394 $prefix =~ s/\%Time\%/$self->{'TIMESTAMP'}/gi;
658 128         258 $prefix =~ s/\%Epoch\%/$self->{'EPOCH'}/gi;
659 128         346 $prefix =~ s/\%date\%/$Date/gi;
660 128         285 $prefix =~ s/\%time\%/$Time/gi;
661 128         201 $prefix =~ s/\%epoch\%/$epoch/gi;
662              
663 128         468 return "$prefix$padding$msg";
664             }
665              
666             sub _send_to_logger { # Legacy path: retained for backward compatibility but routed via _format_line
667 0     0   0 my ($self, $level, $padding, $msg, $first, $thisBench, $thisBench2, $subroutine, $cline, $sline, $shortsub) = @_;
668              
669 0         0 my $fh = $self->{'FILEHANDLE'};
670 0 0 0     0 if ($level eq 'INFO' && $self->{'LOGLEVEL'} eq 'VERBOSE') { # Trap verbose flag and temporarily drop the prefix.
    0          
671 0         0 print $fh "$msg\n";
672             } elsif ($level eq 'DEBUGMAX') { # Special version of DEBUG. Extremely verbose debugging and quite noisy
673 0 0       0 if ($self->{'LOGLEVEL'} eq 'DEBUGMAX') {
674 0         0 my $line = $self->_format_line($level, $padding, $msg, $first, $thisBench, $thisBench2, $subroutine, $cline, $sline, $shortsub);
675 0         0 print $fh "$line\n";
676             }
677             } else {
678 0         0 my $line = $self->_format_line($level, $padding, $msg, $first, $thisBench, $thisBench2, $subroutine, $cline, $sline, $shortsub);
679 0         0 print $fh "$line\n";
680             }
681             } ## end sub _send_to_logger
682              
683             =head2 B or B
684              
685             Sends ERROR level debugging output to the log. Errors are always shown.
686              
687             =over 4
688              
689             =item B
690              
691             Either a single string or a reference to a list of strings
692              
693             =back
694             =cut
695              
696             sub ERR {
697 0     0 1 0 my $self = shift;
698 0         0 $self->debug('ERR', @_);
699             }
700              
701             sub ERROR {
702 0     0 1 0 my $self = shift;
703 0         0 $self->debug('ERR', @_);
704             }
705              
706             =head2 B or B
707              
708             If the log level is WARN or above, then these warnings are logged.
709              
710             =over 4
711              
712             =item B
713              
714             Either a single string or a reference to a list of strings
715              
716             =back
717             =cut
718              
719             sub WARN {
720 0     0 1 0 my $self = shift;
721 0         0 $self->debug('WARN', @_);
722             }
723              
724             sub WARNING {
725 0     0 1 0 my $self = shift;
726 0         0 $self->debug('WARN', @_);
727             }
728              
729             =head2 B or B
730              
731             If the loglevel is NOTICE or above, then these notices are logged.
732              
733             =over 4
734              
735             =item B
736              
737             Either a single string or a reference to a list of strings
738              
739             =back
740             =cut
741              
742             sub NOTICE {
743 0     0 1 0 my $self = shift;
744 0         0 $self->debug('NOTICE', @_);
745             }
746              
747             sub ATTENTION {
748 0     0 1 0 my $self = shift;
749 0         0 $self->debug('NOTICE', @_);
750             }
751              
752             =head2 B or B
753              
754             If the loglevel is INFO (or VERBOSE) or above, then these information messages are displayed.
755              
756             =over 4
757              
758             =item B
759              
760             Either a single string or a reference to a list of strings
761              
762             =back
763             =cut
764              
765             sub INFO {
766 0     0 1 0 my $self = shift;
767 0         0 $self->debug('INFO', @_);
768             }
769              
770             sub INFORMATION {
771 0     0 1 0 my $self = shift;
772 0         0 $self->debug('INFO', @_);
773             }
774              
775             =head2 B
776              
777             If the Loglevel is DEBUG or above, then basic debugging messages are logged. DEBUG is intended for basic program flow messages for easy tracing. Best not to place variable contents in these messages [...]
778              
779             =over 4
780              
781             =item B
782              
783             Either a single string or a reference to a list of strings
784              
785             =back
786             =cut
787              
788             sub DEBUG {
789 6     6 1 352 my $self = shift;
790 6         20 $self->debug('DEBUG', @_);
791             }
792              
793             =head2 B
794              
795             If the loglevel is DEBUGMAX, then all messages are shown, and terse debugging messages as well. Typically DEBUGMAX is used for variable dumps and detailed data output for heavy tracing. This is a ve [...]
796              
797             =over 4
798              
799             =item B
800              
801             Either a single string or a reference to a list of strings
802              
803             =back
804             =cut
805              
806             sub DEBUGMAX {
807 0     0 1   my $self = shift;
808 0           $self->debug('DEBUGMAX', @_);
809             }
810              
811             1;
812              
813             =head1 B
814              
815             Since it is possible to duplicate the object in a fork or thread, the output formatting may be mismatched between forks and threads due to the automatic padding adjustment of the subroutine name field [...]
816              
817             Ways around this are to separately create a Debug::Easy object in each fork or thread, and have them log to separate files.
818              
819             The "less" pager is the best for viewing log files generated by this module. It's switch "-r" allows you to see them in all their colorful glory.
820              
821             =head1 B
822              
823             To install this module, run the following commands:
824              
825             perl Makefile.PL
826             make
827             make test
828             [sudo] make install
829              
830             =head1 AUTHOR
831              
832             Richard Kelsch
833              
834             This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
835              
836             =head1 B
837              
838             Version 2.26
839              
840             =head1 B
841              
842             You can find documentation for this module with the perldoc command.
843              
844             C
845              
846             or if you have "man" installed, then
847              
848             C
849              
850             You can also look for information at: L
851              
852             =head1 B
853              
854             I coded this module because it filled a gap when I was working for a major chip manufacturing company (which I coded at home on my own time). It gave the necessary output the other coders asked for, [...]
855              
856             If you have any features you wish added, or functionality improved or changed, then I welcome them, and will very likely incorporate them sooner than you think.
857              
858             =head1 B
859              
860             Copyright 2013-2026 Richard Kelsch.
861              
862             This program is free software; you can redistribute it and/or modify it under the terms of the the Artistic License (2.0). You may obtain a copy of the full license at:
863              
864             L
865              
866             Any use, modification, and distribution of the Standard or Modified Versions is governed by this Artistic License. By using, modifying or distributing the Package, you accept this license. Do not use, [...]
867              
868             If your Modified Version has been derived from a Modified Version made by someone other than you, you are nevertheless required to ensure that your Modified Version complies with the requirements of t [...]
869              
870             This license does not grant you the right to use any trademark, service mark, tradename, or logo of the Copyright Holder.
871              
872             This license includes the non-exclusive, worldwide, free-of-charge patent license to make, have made, use, offer to sell, sell, import and otherwise transfer the Package with respect to any patent cla [...]
873              
874             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A [...]
875              
876             =head1 B
877              
878             Perl modules available on github - L
879              
880             And available on CPAN:
881              
882             * Debug::Easy - L
883             * Graphics::Framebuffer - L
884             * Term::ANSIEncode - L
885              
886             =cut