package IPC::Run::Debug; =head1 NAME IPC::Run::Debug - debugging routines for IPC::Run =head1 SYNOPSIS ## ## Environment variable usage ## ## To force debugging off and shave a bit of CPU and memory ## by compile-time optimizing away all debugging code in IPC::Run ## (debug => ...) options to IPC::Run will be ignored. export IPCRUNDEBUG=none ## To force debugging on (levels are from 0..10) export IPCRUNDEBUG=basic ## Leave unset or set to "" to compile in debugging support and ## allow runtime control of it using the debug option. =head1 DESCRIPTION Controls IPC::Run debugging. Debugging levels are now set by using words, but the numbers shown are still supported for backwards compatability: 0 none disabled (special, see below) 1 basic what's running 2 data what's being sent/recieved 3 details what's going on in more detail 4 gory way too much detail for most uses 10 all use this when submitting bug reports noopts optimizations forbidden due to inherited STDIN The C level is special when the environment variable IPCRUNDEBUG is set to this the first time IPC::Run::Debug is loaded: it prevents the debugging code from being compiled in to the remaining IPC::Run modules, saving a bit of cpu. To do this in a script, here's a way that allows it to be overridden: BEGIN { unless ( defined $ENV{IPCRUNDEBUG} ) { eval 'local $ENV{IPCRUNDEBUG} = "none"; require IPC::Run::Debug"' or die $@; } } This should force IPC::Run to not be debuggable unless somebody sets the IPCRUNDEBUG flag; modify this formula to grep @ARGV if need be: BEGIN { unless ( grep /^--debug/, @ARGV ) { eval 'local $ENV{IPCRUNDEBUG} = "none"; require IPC::Run::Debug"' or die $@; } Both of those are untested. =cut @ISA = qw( Exporter ) ; ## We use @EXPORT for the end user's convenience: there's only one function ## exported, it's homonymous with the module, it's an unusual name, and ## it can be suppressed by "use IPC::Run () ;". @EXPORT = qw( _debug _debug_desc_fd _debugging _debugging_data _debugging_details _debugging_gory_details _debugging_not_optimized _set_child_debug_name ); @EXPORT_OK = qw( _debug_init _debugging_level _map_fds ); %EXPORT_TAGS = ( default => \@EXPORT, all => [ @EXPORT, @EXPORT_OK ], ); use strict ; use Exporter ; my $disable_debugging = defined $ENV{IPCRUNDEBUG} && ( ! $ENV{IPCRUNDEBUG} || lc $ENV{IPCRUNDEBUG} eq "none" ); eval( $disable_debugging ? <<'STUBS' : <<'SUBS' ) or die $@; sub _map_fds() { "" } sub _debug {} sub _debug_desc_fd {} sub _debug_init {} sub _set_child_debug_name {} sub _debugging() { 0 } sub _debugging_level() { 0 } sub _debugging_data() { 0 } sub _debugging_details() { 0 } sub _debugging_gory_details() { 0 } sub _debugging_not_optimized() { 0 } 1; STUBS use POSIX; use UNIVERSAL qw( isa ); sub _map_fds { my $map = '' ; my $digit = 0 ; my $in_use ; my $dummy ; for my $fd (0..63) { ## I'd like a quicker way (less user, cpu & expecially sys and kernal ## calls) to detect open file descriptors. Let me know... ## Hmmm, could do a 0 length read and check for bad file descriptor... ## but that segfaults on Win32 my $test_fd = POSIX::dup( $fd ) ; $in_use = defined $test_fd ; POSIX::close $test_fd if $in_use ; $map .= $in_use ? $digit : '-'; $digit = 0 if ++$digit > 9 ; } warn "No fds open???" unless $map =~ /\d/ ; $map =~ s/(.{1,12})-*$/$1/ ; return $map ; } use vars qw( $parent_pid ) ; $parent_pid = $$ ; ## TODO: move debugging to it's own module and make it compile-time ## optimizable. ## Give kid process debugging nice names my $debug_name ; sub _set_child_debug_name { $debug_name = shift; } ## There's a bit of hackery going on here. ## ## We want to have any code anywhere be able to emit ## debugging statements without knowing what harness the code is ## being called in/from, since we'd need to pass a harness around to ## everything. ## ## Thus, $cur_self was born. # my %debug_levels = ( none => 0, basic => 1, data => 2, details => 3, gore => 4, gory_details => 4, "gory details" => 4, gory => 4, gorydetails => 4, all => 10, notopt => 0, ); my $warned; sub _debugging_level() { my $level = 0 ; $level = $IPC::Run::cur_self->{debug} || 0 if $IPC::Run::cur_self && ( $IPC::Run::cur_self->{debug} || 0 ) >= $level ; if ( defined $ENV{IPCRUNDEBUG} ) { my $v = $ENV{IPCRUNDEBUG}; $v = $debug_levels{lc $v} if $v =~ /[a-zA-Z]/; unless ( defined $v ) { $warned ||= warn "Unknown debug level $ENV{IPCRUNDEBUG}, assuming 'basic' (1)\n"; $v = 1; } $level = $v if $v > $level ; } return $level ; } sub _debugging_atleast($) { my $min_level = shift || 1 ; my $level = _debugging_level ; return $level >= $min_level ? $level : 0 ; } sub _debugging() { _debugging_atleast 1 } sub _debugging_data() { _debugging_atleast 2 } sub _debugging_details() { _debugging_atleast 3 } sub _debugging_gory_details() { _debugging_atleast 4 } sub _debugging_not_optimized() { ( $ENV{IPCRUNDEBUG} || "" ) eq "notopt" } sub _debug_init { ## This routine is called only in spawned children to fake out the ## debug routines so they'll emit debugging info. $IPC::Run::cur_self = {} ; ( $parent_pid, $^T, $IPC::Run::cur_self->{debug}, $IPC::Run::cur_self->{DEBUG_FD}, $debug_name ) = @_ ; } sub _debug { # return unless _debugging || _debugging_not_optimized ; my $fd = defined &IPC::Run::_debug_fd ? IPC::Run::_debug_fd() : fileno STDERR; my $s ; my $debug_id ; $debug_id = join( " ", join( "", defined $IPC::Run::cur_self ? "#$IPC::Run::cur_self->{ID}" : (), "($$)", ), defined $debug_name && length $debug_name ? $debug_name : (), ) ; my $prefix = join( "", "IPC::Run", sprintf( " %04d", time - $^T ), ( _debugging_details ? ( " ", _map_fds ) : () ), length $debug_id ? ( " [", $debug_id, "]" ) : (), ": ", ) ; my $msg = join( '', map defined $_ ? $_ : "", @_ ) ; chomp $msg ; $msg =~ s{^}{$prefix}gm ; $msg .= "\n" ; POSIX::write( $fd, $msg, length $msg ) ; } my @fd_descs = ( 'stdin', 'stdout', 'stderr' ) ; sub _debug_desc_fd { return unless _debugging ; my $text = shift ; my $op = pop ; my $kid = $_[0] ; Carp::carp join " ", caller(0), $text, $op if defined $op && isa( $op, "IO::Pty" ) ; _debug( $text, ' ', ( defined $op->{FD} ? $op->{FD} < 3 ? ( $fd_descs[$op->{FD}] ) : ( 'fd ', $op->{FD} ) : $op->{FD} ), ( defined $op->{KFD} ? ( ' (kid', ( defined $kid ? ( ' ', $kid->{NUM}, ) : () ), "'s ", ( $op->{KFD} < 3 ? $fd_descs[$op->{KFD}] : defined $kid && defined $kid->{DEBUG_FD} && $op->{KFD} == $kid->{DEBUG_FD} ? ( 'debug (', $op->{KFD}, ')' ) : ( 'fd ', $op->{KFD} ) ), ')', ) : () ), ) ; } 1; SUBS =head1 AUTHOR Barrie Slaymaker , with numerous suggestions by p5p. =cut 1 ;