fwashbur has asked for the wisdom of the Perl Monks concerning the following question:

Hi PerlMongers,

I have a problem for which I can't seem to find a solution. We have a huge 5.6 code base and are looking at porting to 5.8. Most everything is going well except for one area. I have searched all the Perl books, CPAN, Google, etc to find a solution but have been unsuccessful.

We took advantage of what sounds like a hole in 5.6 and did a self-tie of STDOUT to itself and a log file. STDERR was tied to the same log file and itself. This allowed us to capture all print/printf output in text logs from sessions with both STDOUT and STDERR as well as seeing this output on the screen. 5.6 was somehow able to detect that it needed to call print with the real handle one time and not recurse. It may have been a bad thing to do, but it mades sense to us and worked like a charm.

On 5.8, the upgrade notes say self tie is not allowed and in fact it does cause a recursive call to print.

I have tried saving a global value for STDOUT, doing a local STDOUT and passing the global value to the Tie class and print to that. This works, but I can only get either STDOUT or STDERR to work in this situation, not both.

I have tried using the suggestion from the Perl Cookbook 7-18:

open(STDOUT, "| tee $outfile); print "whatever\n"; close(STDOUT);

Which fails miserably. There are several problems with this. Upon close, the tee process does not die. Attempts to kill the tee process after the close result in tee not being able to run again during that process (it complains about arguments???) If I add 'or die' to the close call it fails as there seems to be an issue with closing STDOUT. Also upon close, STDOUT is now undef and cannot be used unless opened again. If I open another log file after closing the first, all print output now goes to the 1st AND 2nd file (2 tee processes now running).

I have tried overriding print and that seems like it is not allowed. Perhaps there is a way to do this that I have not found, I tried the function re-definition example from O'Reilly but it needs to work for print.

If anyone has ideas on how to:
1. cause output from print and printf
2. for both STDOUT and STDERR
3. to go to successive log files within the same perl 5.8 session I would be extremely grateful.

Thanks - Rick

Rick Washburn
Qualcomm Inc. 928-478-4663

Replies are listed 'Best First'.
Re: Porting Perl 5.6 to Perl 5.8 issue with self-tie
by TedYoung (Deacon) on Dec 09, 2006 at 00:38 UTC

    Well, a quick solution would be IO::Tee. This should be nearly a drop-in replacement. A more comprehensive solution would be Log::Log4perl.

    I have no way to test this tonight, mayhapse another monk will correct me if wrong, but to assign an IO::Tee to, say, STDOUT would be done something like this:

    local *STDOUT = new IO::Tee(\*STDOUT, '>file.txt');

    UPDATE: No, after looking at that, I don't think that will work. This is more likely too, but there is probably a shorter way:

    open STDOUT, '>&', new IO::Tee(\*STDOUT, '>file.txt');

    With this method, you can do silly things like:

    # Append open STDOUT, '>>&', new IO::Tee(\*STDOUT, '>file.txt'); # Save the extra file descriptor open STDOUT, '>&=', new IO::Tee(\*STDOUT, '>file.txt'); open STDOUT, '>>&=', new IO::Tee(\*STDOUT, '>file.txt');

    See open for more details.

    Ted Young

    ($$<<$$=>$$<=>$$<=$$>>$$) always returns 1. :-)
      Hi Ted,

      I had played around with IO:Tee but I was unsuccessful in getting both STDOUT and STDERR to go to the same file. You have done something slightly different here, so I might play around with that some more.

      Thanks! Rick

        Yea, even if you can get them out to the same file, it would probably be safer to use Log::Log4perl. Any, you will get a richer logging featureset.

        Ted Young

        ($$<<$$=>$$<=>$$<=$$>>$$) always returns 1. :-)
Re: Porting Perl 5.6 to Perl 5.8 issue with self-tie
by shmem (Chancellor) on Dec 10, 2006 at 01:05 UTC
    The second tee doesn't work because you didn't save your original STDOUT, so tee doesn't get it.
    #!/usr/bin/perl -wl $| = 1; # unbuffered output print $$; for(1,2){ open SAVEOUT,">&STDOUT"; my $pid = open STDOUT,"| tee log_$_"; open STDERR, ">&STDOUT"; print "this goes to STDOUT (log_$_)"; warn "This goes to STDERR (log_$_)"; sleep 1; print "killing $pid"; kill 15, $pid; close STDOUT; close STDERR; open STDOUT,">&SAVEOUT"; } __END__ Name "main::SAVEOUT" used only once: possible typo at tee.pl line 4. 14781 this goes to STDOUT (log_1) This goes to STDERR (log_1) at tee.pl line 8. killing 14782 this goes to STDOUT (log_2) This goes to STDERR (log_2) at tee.pl line 8.
    $ cat log_1 log_2 this goes to STDOUT (log_1) This goes to STDERR (log_1) at tee.pl line 8. this goes to STDOUT (log_2) This goes to STDERR (log_2) at tee.pl line 8.

    Of course, if you need to restore STDERR later, save that too. See open. Note that if you remove the sleep 1 line in the above code you will likely not see any output, because then the tee process gets killed too soon.

    --shmem

    update: fixed inconsistencies stemming from putting a oneliner into a file..

    _($_=" "x(1<<5)."?\n".q·/)Oo.  G°\        /
                                  /\_¯/(q    /
    ----------------------------  \__(m.====·.(_("always off the crowd"))."·
    ");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}
      Hey shmem,

      This looks like what I'm looking for! I had tried combos of saving STDOUT and killing the tee, but not in this configuration. I'll give this a try. This could explain why I was having trouble with STDERR as well.

      Thanks - Rick

        Hi shmem,

        I tried this scheme on my system. It ran into the same issue I had when trying the example out of the O'Reilly book. The first log works great, but the second log attempt results in the following error at the first print statement: tee: write error: Invalid argument

        Are you running this on 5.8? Could there be an issue with my 'tee'?

        Thanks - Rick

Re: Porting Perl 5.6 to Perl 5.8 issue with self-tie
by andreas1234567 (Vicar) on Dec 10, 2006 at 19:34 UTC
    If anyone has ideas on how to:
    1. cause output from print and printf
    2. for both STDOUT and STDERR
    3. to go to successive log files within the same perl 5.8 session I would be extremely grateful.
    I'm part of a team who successfully switched from a printf-to-stdout approach to using Log::Log4perl for logging all kinds of messages. Log4perl is easy to use, and very configurable. Destinations range from (automatically rotated) files, database(s) as well as stdout.

    One advantage with a logging framework is that it allows you to leave (disabled) debugging statements in production code that can be enabled at will. This can make save your day when things start to fail in your deployed systems.

    You may want to benchmark performance with and without logging enabled if your application is very sensitive performance-wise.

    Andreas
    --
      Thanks Andreas,

      I did not know about this module, I'll have a look to see what it has to offer. The logging we are performing is not intensive, so it could fit the bill.

      Rick

Re: Porting Perl 5.6 to Perl 5.8 issue with self-tie
by ysth (Canon) on Dec 11, 2006 at 09:15 UTC
    On 5.8, the upgrade notes say self tie is not allowed
    Only about hashes and arrays.

    Tying STDOUT/STDERR should work...can you show exactly what you were doing?

    Depending on what exactly you were doing, you may need to save *STDOUT{IO}/*STDERR{IO} instead of just \*STDOUT/\*STDERR.

      Hey ysth,

      Hmmm, it would be a dream come true if it worked, but alas I get either nasty recursion errors or reference errors when trying this on 5.8. Basically we tie both STDOUT and STDERR to themselves and a logfile.

      tie *STDOUT, 'InstallerHandleTie', $outfile, *STDOUT; tie *STDERR, 'InstallerHandleTie', $outfile, *STDERR;

      InstallerHandleTie opens $outfile and saves both it's handle and STDOUT/STDERR. We override print and printf to print to the list of handles. Code shown below:

      package InstallerHandleTie; use strict; no strict 'refs'; use FileHandle; use Tie::Handle; our @ISA = qw (Tie::StdHandle); use File::Basename; use File::Path; my $handleId = 0; # this is a class variable so multiple ties # to same file will create different handles sub TIEHANDLE { my $class = shift; my @handles = (); # see if each handle is a filename or a real handle foreach my $h (@_) { if ($h =~ /^\*/ or ref $h eq 'GLOB') { # it's a handle (ie *package::name) or a glob push @handles, $h; $h->autoflush(1); } else { # assume it is a file name and try to open # create a handle name based on my $hname = "_FH_$handleId"; $handleId++; my $dir = dirname($h); if (!-e $dir or !-d $h) { mkpath($dir); } open($hname, ">$h") or die "unable to open file '$h': $!"; $hname->autoflush(1); push @handles, $hname; } } return bless \@handles, $class; } sub PRINT { my $self = shift; my $result = 0; foreach my $handle (@$self) { $result += print $handle @_ if (fileno($handle)); } return ($result == @$self); } sub PRINTF { my $self = shift; my $result = 0; foreach my $handle (@$self) { $result += printf $handle @_ if (fileno($handle)); } return ($result == @$self); } 1; # required 1 to end the Perl module

      I have played around with various forms of *STDOUT{IO} etc. But as long as STDOUT or STDERR are self tied it doesn't work and I get recursion. I was able to save *STDOUT, then do a local *STDOUT, then do the tie by passing in the saved value instead of the local value and this worked. The problem came in when I tried to do the same with STDERR. In this case all the output from STDERR disappeared. If I swapped the STDERR and STDOUT ties done in this manner, I would then get STDERR output and not STDOUT.

      my $gfh = *STDOUT; my $geh = *STDERR; foreach my $outfile (@logs) { local *STDOUT; local *STDERR; tie *STDOUT, 'InstallerHandleTie', $outfile, $gfh; tie *STDERR, 'InstallerHandleTie', $outfile, $geh; print "Writing to $outfile\n"; warn "Writing warning to $outfile\n"; untie *STDOUT; untie *STDERR; }

      Doing this results in the print getting to the log file, but warn does not.

      Thanks! Rick

        The problem came in when I tried to do the same with STDERR. In this case all the output from STDERR disappeared. If I swapped the STDERR and STDOUT ties done in this manner, I would then get STDERR output and not STDOUT.
        That's because you are essentially doing:
        perl -we'open FOO, "> baz"; open BAR, "> baz"; print FOO "one\n"; prin +t BAR "one\n";'
        where FOO and BAR have independent file pointers into the same file. If you want to share a logfile between two handles, use >> to force all writes to append at the end, or open the file outside the tie handler and pass its glob to your logger.

        This works for me:

        use InstallerHandleTie; my $gfh = *STDOUT; my $geh = *STDERR; foreach my $outfile (qw/log1 log2/) { open my $outfh, ">", $outfile or die "nope: $!"; local *STDOUT; local *STDERR; tie *STDOUT, 'InstallerHandleTie', $outfh, $gfh; tie *STDERR, 'InstallerHandleTie', $outfh, $geh; print "Writing to $outfile\n"; warn "Writing warning to $outfile\n"; untie *STDOUT; untie *STDERR; }