NAME
Fred::Fish::DBUG - Fred Fish library for Perl
SYNOPSIS (Default)
use Fred::Fish::DBUG qw / on /;
or
require Fred::Fish::DBUG;
Fred::Fish::DBUG::import ( qw / on / );
DESCRIPTION
Fred::Fish::DBUG is a pure Perl implementation of the C/C++ Fred Fish macro libraries. While in C/C++ this library is implemented mostly via macros, in Perl this library is implemented using true function calls. It has also been slightly modified to address Perlish features over C/C++ ones. This can make using some features a bit strange compared to C/C++. But the basic concepts are the same. The most powerful feature being able to dynamically turn fish logging on and off.
But due to this module being implemented as functions, there can be significant overhead when using this module. So see the next section on how to mitigate this overhead.
ELIMINATING THE OVERHEAD
This can be as simple as changing qw /on/ to qw /off/. This turns most DBUG calls into calls to stubs that do very little. Dropping the current file from any fish logging.
But having to modify your code right before moving it into production, or modifying it to troubleshoot, can make anyone nervous. So I provided ways to dynamically do this for you.
# Called from package my::special::module ... (off by default)
use Fred::Fish::DBUG qw / on_if_set my_special_module_flag /;
Is equivalant to:
BEGIN { require Fred::Fish::DBUG;
my @opt = $ENV{my_special_module_flag} ? qw / ON / : qw / OFF /;
Fred::Fish::DBUG->import ( @opt );
}
Where if $ENV{my_special_module_flag} evaluates to true you have fish logging available. Otherwise it isn't. Chose a name for the environment variable as appropriate to your situation.
Or you can do the reverse where it's on by default:
use Fred::Fish::DBUG qw / off_if_set my_special_module_flag /;
In summary all the options are:
use Fred::Fish::DBUG qw / on /;
use Fred::Fish::DBUG qw / off /;
use Fred::Fish::DBUG qw / on_if_set EnvVar /;
use Fred::Fish::DBUG qw / off_if_set EnvVar /;
use Fred::Fish::DBUG; # Same as if qw / on / was used.
# While enforcing a minimum version ...
use Fred::Fish::DBUG 2.04 qw / on /;
TRAPPING SIGNALS IN FISH
As an extension to the Fred Fish library, this module allows the trapping and logging to fish of all trappable signals for your OS. This list of signals varies per OS. But the most common two being __DIE__ and __WARN__.
But in order to trace these signals you must first ask fish to do so by by first sourcing in Fred::Fish::DBUG::Signal, and then calling DBUG_TRAP_SIGNAL. See that module for more details. You don't have to use that module, but it can make thigs easier if you do.
Just be aware that both __DIE__ and __WARN__ signals can be thrown during Perl's parsing phase of your code. So care must be taken if you try to trap these signals in a BEGIN block. Since if set in BEGIN these traps may end up interfering with your attempts to debug your code.
TRAPPING STDOUT AND STDERR IN FISH
Another extension to the Fred Fish libary allowing you to trap all prints to STDOUT and/or STDERR to also appear in your fish logs. Implemented as a wrapper to Perl's "tie" feature against the SDTOUT and STDERR file handles.
Very useful for putting prints from your program or other modules into context in your fish logs. Just be aware that you may have only one tie per file handle. But if your code does require ties to work, this module provides a way to coexist.
See Fred::Fish::DBUG::TIE for more details on how to enable this feature.
FISH FOR MULTI-THREADED PERL PROGRAMS
This module should be thread-safe as long as Perl's print command is thread-safe. If threads are used, there are two ways to use this module.
The first way is call DBUG_PUSH($file, multi=>1) in the main process and then spawn your threads. This will cause all threads to write to the same fish file as your main program. But you'll have to use a tool such as grep in order to be able to trace the logic of individual threads. Thread 0 is the main process. If you don't use the multi option, your fish log will be unusable since you'll be unable to tell which thread wrote each entry in your log.
The second way is to not call DBUG_PUSH() in the main thread until after you spawn all your threads. In this case you can't share the same fish file name. Each thread should call DBUG_PUSH($file) using a unique file name for each thread's fish log. Using option multi is optional in this case, but still recommended.
But what happens with the second option if you reuse the same filename between threads? In that case this module is not thread-safe! Each thread can step on each other. You can limit the impact with a creative combination of options to DBUG_PUSH(), but you can't reliably remove all the corruption and dropped lines in the shared fish logs. And your work around may break in future releases of this module.
As a reminder, when the main process (thread # 0) terminates, this causes all the child threads to terminate as well. Even if they are still busy. Also child threads do not normally call BEGIN and/or END blocks of code! And all threads share the same PID.
FISH FOR MULTI-PROCESS PERL PROGRAMS
This is when you spawn a child process using fork. In this case all processes have a unique PID and each child process will call their own END blocks. But otherwise it follows the same fish rules as multi-threading.
When the parent process terminates, it allows any running child process to finish it's work and they can still write to fish.
To turn on fish for multi-process use DBUG_PUSH($file, multi=>1) as well.
FURTHER INFORMATION
Not all Perl implementations support mutli-threading and/or multi-processing. So if you are not using multi-threading or multi-processing, I recommend not using the multi option.
USING GOTO STATEMENTS
Using a goto can cause fish issues where the return statements get out of sync with the proper function entry points in your fish logs. This is because calls like goto &MyPackage::MyFunction; jump to MyFunction's entry point, and removes the function using goto from Perl's stack as if it was never there.
Currently the only fix for this is to not use DBUG_ENTER_(FUNC|BLOCK) and the corresponding DBUG_RETURN methods in functions that use goto. Limit yourself to calls to DBUG_PRINT in those methods instead.
Your other choice is to reorganize your code to avoid using the goto statement in the first place.
A common place you'd see a goto is if you used the AUTOLOAD function. But even there, there are alternatives to using the goto if you want clean fish logging.
USING THIS MODULE IN A CPAN MODULE
When you upload a module using fish to CPAN, you probably don't want your code trace being dumped to an end user's fish logs by default. So I recommend doing the following in your code so that "make test" will still have fish turned on, while normal usage won't trace in fish.
use Fred::Fish::DBUG qw / on_if_set my_special_module_flag /;
For an explination on how this works, reread the POD above.
FUNCTIONS
- DBUG_PUSH ( [$file [, %opts]] )
-
Calling this function turns logging on so that all future DBUG fish calls are written to the requested file. Failure to call this function results in nothing being written to the fish logs. Currently there is no way to turn fish back off again except by aborting the program. But there are ways to turn some of the logging off.
You are expected to provide a filename to write the fish logs to. If that file already exists, this function will recreate the fish file and write as its first log message that this happened. By default, the fish log's file permissions allow anyone to read the log file no matter the current umask settings.
But if you fail to provide a filename, fish will instead be written to STDERR. You may also use an open file handle or GLOB reference instead of a filename and fish would be written there instead.
The options hash may be passed by either reference or value. Either way works. Most options are ignored unless you also gave it a filename to open. Most option's value is a flag telling if it's turned on (1) or off (0), and most options default to off unless otherwise specified. The valid options are:
append - Open an old fish log in append mode instead of creating a new one.
autoflush - Turn autoflush on/off. By default it's turned on!
autoopen - Turn auto-open on/off. Causes each call to a fish function to auto-reopen the fish log, write out its message, and then close the fish file again.
off - If set, treat as if DBUG_PUSH was never called! (IE: Fish is off.) It overrides all other options.
filter - See DBUG_FILTER for more details.
kill_end_trace - Suppress the fish logging for the Perl END blocks.
who_called - Adds function/file/line # to the end of the enter function block. So you can locate the code making the call. Also added to the end of DBUG_PRINT messages.
multi - Turns on/off writing process ownership info to the start of each line of the fish log. For multi-thread programs this is PID-thread-id. Ex: 252345-0 is the main process && 252345-4 is the 4th thread spawned by the process. But if it's a forked process it would be PID/2-digits. Ex: 252345/00 is the main process. And 536435/35 is one of its forked child processes. There are no sequential ids for forked processes, nor is the 2-digit code guaranteed to be unique.
limit - If your program is multi-threaded or muli-process, use this option to limit what gets written to fish. 1 - Limit fish to the parent process. 0 - Write everything (default). -1 - Limit fish to the child processes.
chmod - Override the default fish file permissions. Default is 0644. It ignores the current umask settings!
before - Normally the 1st call to DBUG_ENTER_FUNC is after the call to DBUG_PUSH, but set to on if you've already called it. But you will lose printing the function arguments if you do it this way.
strip - Strip off the module name for DBUG_ENTER_FUNC and the various return methods. So main::abc becomes abc in fish.
delay - Number of seconds to sleep after calling DBUG_PRINT in your code. The delay only happens if the write to fish actually happens. If Time::HiRes is installed you can sleep for fractions of a second. But if it isn't installed your time will be truncated. IE: 0.5 becomes 0.
elapsed - Prints the elapsed time inside the function once any DBUG return function is called. If Time::HiRes is installed it tracks to fractions of a second. Otherwise it's whole seconds only.
keep - (1/0/code ref) - (1) Keep your fish log only if your program exits with a non-zero exit status. (0) Always keep your fish log (default). Otherwise it calls your function with the exit status as it's single argument. It's expected to return 1 to keep the fish log or 0 to toss it. This code ref is only called if there is a fish log to potentially remove.
no_addresses - (1/0) - (0) Default, print variable reference addresses like HASH(0x202f4028) which change between runs. (1) Suppress addresses so shows up like HASN(001) so it's easier to compare fish files between runs. Only works for arguments and return values.
allow_utf8 - Writes to fish in UTF-8 mode. Use if you get warnings about writing 'Wide character in print' to fish.
- DBUG_POP ( )
-
Not yet implemented.
- DBUG_ENTER_FUNC ( [@arguments] )
-
Its expected to be called whenever you enter a function. You pass all the arguments from the calling function to this one (@_). It automatically knows the calling function without having to be told what it is.
To keep things in the fish logs balanced, it expects you to call one of the DBUG_RETURN variant methods when exiting your function!
This function also works when called inside named blocks such as eval blocks or even try/catch blocks.
It returns the name of the calling function. In rare cases this name can be useful.
See DBUG_MASK_NEXT_FUNC_CALL should you need to mask any arguments!
- DBUG_ENTER_BLOCK ( $name[, @arguments] )
-
Similar to DBUG_ENTER_FUNC except that it deals with unnamed blocks of code. Or if you wish to call a particular function a different name in the fish logs.
It usually expects you to call DBUG_VOID_RETURN when the block goes out of scope to keep the fish logs balanced. But nothing prevents you from using one of the other return variants instead.
It returns the name of the code block you used. In rare cases this name can be useful.
- DBUG_PRINT ( $tag, $fmt [, $val1 [, $val2 [, ...]]] )
-
This function writes the requested message to the active fish log.
The $tag argument is a text identifier that will be used to 'tag' the line being printed out and enforce any requested filtering and/or coloring.
The remaining arguments are the same as what's passed to printf(1) if given a $fmt and one or more values. But if no values are given then it's treated as a regular call to print.
If the formatted message should be terminated by multiple \n, then it will be truncated to a single \n. All trailing whitespace on each line will be removed as well.
It returns the formatted message written to fish and it will always end in \n. This message doesn't include the $tag or the optional caller info if the who_called option was used by DBUG_PUSH.
This message is returned even if fish is currently turned off!
NOTE: If this request resulted in a write to fish, and you asked for a delay in DBUG_PUSH, this function will sleep the requested number of seconds before returning control to you. If no write, then no delay!
- DBUG_RETURN ( ... )
-
It takes the parameter(s) passed as arguments and uses them as the return values to the calling function similar to how perl's return command works. Except that it also writes what is being returned to fish. Since this is a function, care should be taken if called from the middle of your function's code. In that case use the syntax: "return DBUG_RETURN( value1 [, value2 [, ...]] );".
It uses Perl's wantarray feature to determine what to print to fish and return to the calling function. IE scalar mode (only the 1st value) or list mode (all the values in the list). Which is not quite what many perl developers might expect.
EX: return (wantarray ? (value1, value2, ...) : value1);
If DBUG_MASK was called, it will mask the appropriate return value(s) as: <******>.
- DBUG_ARRAY_RETURN ( @args )
-
A variant of "DBUG_RETURN()" that behaves the same as perl does natively when returning a list to a scalar. IE it returns the # of elements in the @args array.
It always assumes @args is a list, even when provided a single scalar value.
- DBUG_VOID_RETURN ( )
-
Terminates the current block of fish code. It doesn't return any value back to the calling function.
- DBUG_RETURN_SPECIAL ( $scalar, @array )
-
This DBUG_RETURN variant allows you to differentiate between what to return when your function is called in a scalar context vs an array context vs void context.
If called in an array context, the return value is equivalent to DBUG_RETURN (@array).
If called in a scalar context, the return value is equivalent to DBUG_RETURN ($scalar). With a few special case exceptions.
Special case # 1: If $scalar is set to the predefined constant value DBUG_SPECIAL_ARRAYREF, it returns the equivalent to DBUG_RETURN (\@array). Feel free to modify the contents of the referenced array, it can't hurt anything. It's a copy.
Special case # 2: If $scalar is set to the predefined constant value DBUG_SPECIAL_COUNT, it returns the equivalent to DBUG_RETURN (scalar (@array)), the number of elements in the array.
Special case # 3: If $scalar is set to the predefined constant value DBUG_SPECIAL_LAST, it returns the equivalent to DBUG_RETURN ($array[-1]), the last element in the array.
Special case # 4: If $scalar is a CODE ref, it returns the equivalent to DBUG_RETURN (scalar($scalar->(@array))).
If called in a void context, the return value is equivalent to DBUG_VOID_RETURN (). But in some cases it will print additional information to fish. But it will never call the CODE reference when called in void context.
- DBUG_LEAVE ( [$status] )
-
This function terminates your program with a call to exit(). It expects a numeric argument to use as the program's $status code, but will default to zero if it's missing. It is considered the final return of your program.
Only module END and DESTROY blocks can be logged after this function is called as Perl cleans up after itself, unless you turned this feature off with option kill_end_trace when fish was first enabled.
- DBUG_CATCH ( )
-
This function rebalances the fish function trace after trapping die from an eval or try code block.
If using eval, place this function call inside the if ($@) { } section after each eval block of code.
If using try/catch, place this function inside the catch block instead.
But if you don't call this function, the fish logs will still try to auto rebalance itself. But you loose why this happens and it may mischaracterize why it did so in the fish logs. It implies you trapped an eval or try event.
So calling this function is in most cases optional. One of the few times it could be considered required is if you used the elapsed option to DBUG_PUSH. In that case failure to immediately call it could affect your timings when the rebalancing gets deferred until the next DBUG call.
- DBUG_PAUSE ( )
-
Temporarily turns fish off until the pause request goes out of scope. This allows you to conditionally disable fish for particularly verbose blocks of code or any other reason you choose.
The scope of the pause is defined as the previous call to a DBUG_ENTER function variant and it's coresponding call to a DBUG_RETURN variant.
While the pause is active, calling it again does nothing.
- DBUG_MASK ( @offsets )
-
Sometimes the return value(s) returned by DBUG_RETURN and/or it's variants contain sensitive data that you wouldn't want to see recorded in a fish file. Such as user names and passwords. So we need a way to mask these values without the programmer having to jump through too many hoops to do so.
So this function tells the DBUG_RETURN call that goes with the most recent DBUG_ENTER variant which of its return values to mask. So if you have multiple exit points to the current function, this one call handles the masking for them all.
The @offsets array consists of 1 or more integers representing the offset to expected return values. Or the special case of -1 to say mask all return values.
So DBUG_MASK(0,2) would cause DBUG_RETURN to mask the 1st and 3rd elements being returned.
If you pass a non-numeric value, it will assume that the return value is a hash and that you are providing a hash key who's value needs to be masked.
So if you say DBUG_MASK("TWO", "THREE"), it might return [TWO], [<*****>], [ONE], [1]. And since there is no key "THREE" in your hash, nothing was masked for it. And as you can see, we only mask the value, not the key itself! The key is case sensitive, so "two" wouldn't have matched anything. Also remember that the order of the keys returned is random, so pure numeric offsets wouldn't give you the desired results.
We could have combined both examples with DBUG_MASK(0,2,"TWO","THREE").
- DBUG_MASK_NEXT_FUNC_CALL ( @offsets )
-
Sometimes some arguments passed to DBUG_ENTER_FUNC contain sensitive data that you wouldn't want to see recorded in a fish file. Such as user names and passwords. So we need a way to mask these values without the programmer having to jump through too many hoops to do so.
So this function tells the next DBUG_ENTER_FUNC or DBUG_ENTER_BLOCK call which arguments are sensitive. If you call it multiple times before the next time the enter function is called it will only remember the last time called!
The @offsets array consists of 1 or more integers representing the offset to expected arguments. Or the special case of -1 to say mask all arguments passed. Any other negative value will be ignored.
But should any offset be non-numeric, it assumes one of the arguments was a hash passed by value with that string as it's key. And so it will mask the next value after it if the key exists. Needed since the order of hash keys is random. Also in this case the hash key is case insensitive. So "abc" and "ABC" represent the same hash key.
So DBUG_MASK_NEXT_FUNCT_CALL(0,2,"password") would cause DBUG_ENTER_FUNC to mask the 1st and 3rd elements passed to it as well as the next argument after the "password" key.
Any invalid offset value will be silently ignored.
- DBUG_FILTER ( [$level] )
-
This function allows you to filter out unwanted messages being written to fish. This is controlled by the value of $level being passed to this method. If you never call this method, by default you'll get everything.
If you call it with no $level provided, the current level will remain unchanged!
It returns up to two values: (old_level, new_level)
The old_level may be -1 if it was previously using custom filtering.
The valid levels are defined by the following exposed constants:
DBUG_FILTER_LEVEL_FUNC - Just the function entry and exit points.
DBUG_FILTER_LEVEL_ARGS - Add on the function arguments & return values.
DBUG_FILTER_LEVEL_ERROR - Add on DBUG_PRINT calls with ERROR as their tag.
DBUG_FILTER_LEVEL_STD - Add on trapped writes to STDOUT & STDERR.
DBUG_FILTER_LEVEL_WARN - Add on DBUG_PRINT calls with WARN or WARNING as their tag.
DBUG_FILTER_LEVEL_DEBUG - Add on DBUG_PRINT calls with DEBUG or DBUG as their tag.
DBUG_FILTER_LEVEL_INFO - Add on DBUG_PRINT calls with INFO as their tag.
DBUG_FILTER_LEVEL_OTHER - Include everything! (default)
DBUG_FILTER_LEVEL_INTERNAL - Include Fred::Fish::DBUG diagnostics.
- DBUG_CUSTOM_FILTER ( @levels )
-
This function allows you to customize which filter level(s) should appear in your fish logs. You can pick and choose from any of the levels defined by DBUG_FILTER(). If you provide an invalid level, it will be silently ignored. Any level not listed will no longer appear in fish.
- DBUG_CUSTOM_FILTER_OFF ( @levels )
-
This function is the reverse of DBUG_CUSTOM_FILTER. Instead of specifying the filter levels you wish to see, you specify the list of levels you don't want to see. Sometimes it's just easier to list what you don't want to see in fish.
- DBUG_SET_FILTER_COLOR ( $level [, @color_attr] )
-
This method allows you to control what color to use when printing to the fish logs for each filter level. Each level may use different colors or repeat the same color between levels.
See DBUG_FILTER() above to see what the valid levels are.
See Term::ANSIColor for what color strings are available. But undef or the empty string means to use no color information. (default) You may use strings like ("red on_yellow") or ("red", "on_yellow") or even use the color constants (RED, ON_YELLOW).
If Term::ANSIColor is not installed, this method does nothing. If you set $ENV{ANSI_COLORS_DISABLED} to a non-zero value it will disable your color choice as well.
Returns 1 if the color request was accepted, else 0.
- DBUG_ACTIVE ( )
-
This function tells you if fish is currently turned on or not.
It will return 0 if DBUG_PUSH() was never called, called with off => 1, or if DBUG_PAUSE() is currently in effect. It ignores any filter request.
It will return 1 if fish is currently writing to a file.
It will return -1 if fish is currently writing to your screen via STDERR or STDOUT.
- DBUG_EXECUTE ( $tag )
-
This boolean function helps determine if a call to DBUG_PRINT using this $tag would actually result in the print request being written to fish or not.
It returns 1 if the DBUG_PRINT would write it to fish and 0 if for any reason it wouldn't write to fish. It returns -1 if fish is currently writing to your screena via STDERR or STDOUT.
Reasons for returning 0 would be: Fish was turned off, pause was turned on, or you set your fish filtering level too low.
This way you can write conditional code based on what's being written to fish!
- DBUG_FILE_NAME ( )
-
Returns the full absolute file name to the fish log created by DBUG_PUSH. If DBUG_PUSH was passed an open file handle, then the file name is unknown and the empty string is returned!
- DBUG_FILE_HANDLE ( )
-
Returns the file handle to the open fish file created by DBUG_PUSH. If DBUG_PUSH wasn't called, or called using autoopen, then it returns undef instead.
- DBUG_ASSERT ( $expression [, $always_on [, $msg]] )
-
This function works similar to the C/C++ assert function except that it can't tell you what the boolean expression was.
This assert is usually turned off when fish isn't currently active. But you may enable it even when fish is turned off by setting the $always_on flag to true.
If the $expression is true, no action is taken and nothing is written to fish.
But if the $expression is false, it will log the event to fish and then exit your program with a status code of 14. Meaning this exit can't be trapped by eval or try/catch blocks.
If you provide the optional $msg, it will print out that message as well after the assert statement.
These messages will be written to both STDERR and fish.
- DBUG_MODULE_LIST ( )
-
This optional method writes to fish all modules used by your program. It provides the module version as well as where the module was installed. Very useful when you are trying to see what's different between different installs of perl or when you need to open a CPAN ticket.
CREDITS
To Fred Fish for developing the basic algorithm and putting it into the public domain! Any bugs in its implementation are purely my fault.
SEE ALSO
Fred::Fish::DBUG::ON - Is what does the actual work when fish is enabled.
Fred::Fish::DBUG::OFF - Is the stub version of the ON module.
Fred::Fish::DBUG::TIE - Allows you to trap and log STDOUT/STDERR to fish.
Fred::Fish::DBUG::Signal - Allows you to trap and log signals to fish.
Fred::Fish::DBUG::SignalKiller - Allows you to implement action DBUG_SIG_ACTION_LOG for die. Really dangerous to use. Will break most code bases.
Fred::Fish::DBUG::Tutorial - Sample code demonstrating using the DBUG module.
COPYRIGHT
Copyright (c) 2007 - 2024 Curtis Leach. All rights reserved.
This program is free software. You can redistribute it and/or modify it under the same terms as Perl itself.