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

I am using a CPAN module (Parse::RecDescent, as it happens) which prints output to stderr. My program, on the other hand, uses a logging module (based on log4perl) and I want to direct all output through my own logging module. I do not want to change the code of P::RD and it is not convenient to inherit P::RD, in order to change its behaviour. (NB When I say "prints output to stderr", I mean literally "print STDERR". If it was using warn(), I could simply install a __WARN__ handler.)

The obvious solution would appear to be to attach an output filter to my own program's STDERR. However, I find to my chagrin that I do not know how to do so :-( Attaching an output filter to my own program's STDOUT is straightforward. For example:

attach_stdout_filter; while (<>) { print; } sub attach_stdout_filter { my($pid); return if $pid = open(STDOUT, '|-'); defined $pid or die "Cannot fork: $!\n"; # child while (<STDIN>) { s/foo/bar/; print; } }

Does anybody have a similar example which attaches an output filter to STDERR?

Cheers
Kevin

Replies are listed 'Best First'.
Re: Filtering my own stderr
by RMGir (Prior) on Nov 01, 2002 at 19:51 UTC
    This seems like such a dumb idea, but maybe it'll work...
    $|=1; attach_stderr_filter(); while (<>) { print "To STDOUT: $_"; print STDERR "To STDERR: $_"; } sub attach_stderr_filter { my($pid); # This is the top secret part :) $pid = open(STDERR, '|-'); defined $pid or die "Cannot fork: $!\n"; if($pid) { return; } # child, now prints to STDERR. while (<STDIN>) { s/foo/bar/; print STDERR "$_"; } }
    Woot, what do you know, it DOES work. Cool!
    --
    Mike
      Mike

      Many thanks for your reply and sorry for taking a while to respond...

      <confusion>
      Er, yes, that does work. Ok, then. Given that open(STDERR, '|-'); was actually the first thing I tried and it did not work for me, I obviously did something stupid. It turns out that I did two stupid things. The first was that I did not try all the alternatives against a simple test case. The second was that I did not read the source of P::RD carefully enough.

      ...Time passes while Kevin reads P::RD properly this time...

      The output I am concerned about is actually written to a format, not directly to STDERR. Why does this matter? Because the format is opened by dup(2)ing STDERR, which gives a fresh file descriptor. So, I can sit there in the child waiting for output on STDERR until I am blue in the face and it will not get me anywhere.
      </confusion>

      So, what now? It does not seem as though I can open an output filter, since I do not know to which file descriptor I should listen. Perhaps I could spin around all of them, but that sounds pretty obscure.

      How about tie'ing the format to a suitable class? Well, this partially works. As it happens, P::RD both prints and writes to the format. The prints come through fine but the writes do not. It seems as though writes to a format cannot be tied. See the example code below:

      use strict; use warnings; package Log::OPG::Info; use Tie::Handle; use base qw(Tie::StdHandle); sub WRITE { print "Good\n"; } package main; format STDERR = Bad . tie *STDERR, 'Log::OPG::Info'; print STDERR ''; write STDERR;

      Whereas I hope for the output

      Good Good
      I actually get
      Good Bad

      So, either I am missing something obvious, or the follow-up question is:

      "How does one tie writes to a format?"

      Cheers
      Kevin
      

      P.S. I faintly remember that it is possible to name individual slots in hash table entries, along the lines of $::slot{SCALAR} and $::slot{FORMAT}. Perhaps that might form the basis for a solution, but the details escape me.

        Rather than capturing your own STDERR, why not try wrapping your program with a short one that uses IPC::Open3 to launch the original program with STDERR captured.

        You can also do this kind of redirecting in the command-line invocation with any decent shell.

Re: Filtering my own stderr
by BUU (Prior) on Nov 01, 2002 at 23:38 UTC
    package main; use Parse::RecDescent; package Parse::RecDescent; tie STDERR,myclass; package myclass; sub PRINT {&loggingFunctions} package main; #viola!
Re: Filtering my own stderr
by robartes (Priest) on Nov 02, 2002 at 09:18 UTC
    Another thing yo could try, is doing some pipe magic: open a pipe, reopen STDERR to the write end of the pipe and have your filter read from the read end:
    pipe (STDERR_READER, STDERR_WRITER) or die "Pipes clogged: $!\n"; close STDERR; open STDERR, ">&STDERR_WRITER" or die "Could not dup STDERR: $!\n"; sub my_output_filter { my $line=<STDERR_READER>; do_something_funny_and_possibly_even_useful; }

    CU
    Robartes-