15

I've searched the Internet and have found some good solutions for teeing STDOUT to 2 different places. Like to a log file and also to the screen at the same time. Here's one example:

use IO::Tee;
my $log_filename = "log.txt";
my $log_filehandle;
open( $log_filehandle, '>>', $log_filename )
  or die("Can't open $log_filename for append: $!");
my $tee = IO::Tee->new( $log_filehandle, \*STDOUT );
select $tee;

But this solution leaves STDERR going only to the screen and I want STDERR go to both the screen and also to the same log file that STDOUT is being logged to. Is that even possible?

My task is to get my build process logged, but I also want to see it on my IDE's screen as usual. And logging the error messages is just as important as logging the happy messages. And getting the errors logged to a separate log file is not a good solution.

brian d foy
  • 129,424
  • 31
  • 207
  • 592
Kurt W. Leucht
  • 4,725
  • 8
  • 33
  • 45

8 Answers8

13

I use Log::Log4perl for things like this. It handles sending output to multiple places for you, including the screen, files, databases, or whatever else you like. Once you get even a little bit complex, you shouldn't be doing this stuff on your own.

Instead of printing to filehandles, you just give Log4perl a message and it figures out the rest. I have a short introduction to it in Mastering Perl. It's based on Log4j, and most of the stuff you can do in Log4j you can do in Log4perl, which also means that once you know it, it becomes a transferrable skill.

brian d foy
  • 129,424
  • 31
  • 207
  • 592
  • Doesn't Log4Perl require me to print to a filehandle? I need all output redirected because I don't control most of the print statements that are output by the Module::Build process. – Kurt W. Leucht Oct 07 '09 at 19:18
  • I did look at Log4Perl, but it didn't seem to answer the mail for me because it required me to print to a filehandle. – Kurt W. Leucht Oct 07 '09 at 19:20
  • 1
    Well, if it's Module::Build you're talking about, let's fix that problem. Is your question really "how do I capture the output from Module::Build?" That's about all I do lately. You can make a subclass and override the log_* methods to do whatever you like. – brian d foy Oct 08 '09 at 02:45
  • 1
    With Log4perl, you don't deal with filehandles at the user level. I'm not sure what you were looking at. – brian d foy Oct 08 '09 at 02:46
  • Well, I need to capture all the output from my build process, whether it came from Module::Build or whether it came from Test::Perl::Critic or whether it came from some print/die statements that I've sprinkled throughout my various build scripts or whatever. I'm looking for a generic solution that captures all output from both STDOUT and from STDERR and gets it into my build log file, but I also want to see it all on the screen like normal. – Kurt W. Leucht Oct 08 '09 at 13:51
  • Well, work as hard as you like then. I'll keep doing it the easier way. – brian d foy Oct 08 '09 at 23:39
  • In all examples I can find, they log a message with something like this: $logger->info('this is an info message'); So how do you set up Log4Perl to automatically capture all STDOUT and STDERR output generated from any module where I don't and cannot control the print statements? I don't see anything like that in any of the Log4Perl documentation. – Kurt W. Leucht Nov 23 '09 at 17:13
  • Maybe it's not clear that it's not my own code that is generating output that I want to redirect/tee. It's other people's print statements and such that I want to redirect/tee. I've looked at Log4Perl examples and I don't see it answering the mail for this task ... unless I'm missing something? It appears to me to be able to redirect/tee any outputs that I myself want to generate. I'm trying to keep an open mind, but I haven't seen any example Log4Perl code that does what I'm asking. If it can do it, then please show me an example. – Kurt W. Leucht Mar 25 '10 at 04:53
  • 3
    I don't think it can be any clearer than the answer to "Some module prints messages to STDERR. How can I funnel them to Log::Log4perl?" in the Log4perl FAQ. Read it in the Log4perl distribution. – brian d foy Mar 25 '10 at 05:06
8
use PerlIO::Util;
*STDOUT->push_layer(tee => ">>/dir/dir/file");
*STDERR->push_layer(tee => ">>/dir/dir/file");

Though I use Log::Dispatch extensively, I've used the above to log what actually got displayed to the screen to a file.

brian d foy
  • 129,424
  • 31
  • 207
  • 592
Oesor
  • 6,632
  • 2
  • 29
  • 56
  • Wow. That sort of worked. It looks like it logged both the STDOUT and STDERR to both the screen and also to the log file, but it always gives me a "perl.exe Application Error" dialog saying that an unknown software exception occurred. – Kurt W. Leucht Oct 08 '09 at 14:14
  • Don't have much practical experience with Windows perl compatibility; I've only used it on linux. Have you tried Strawberry perl vs activestate perl? Do you have cygwin installed? – Oesor Oct 08 '09 at 15:24
  • I'm using ActiveState Perl and Eclipse EPIC IDE. No cygwin. – Kurt W. Leucht Oct 08 '09 at 21:22
  • Seems that there is no `deb` package of `PerlIO::Util` for Ubuntu :-( – Boris Däppen Mar 18 '16 at 13:00
5

You can redirect stderr to stdout at the windows shell level by doing something like:

perl stuff.pl 2>&1

See support article here for the official word.

Then you could use this stackoverflow answer to do a tee from the shell.

perl stuff.pl 2>&1 | tee stuff.txt
Community
  • 1
  • 1
clstrfsck
  • 14,715
  • 4
  • 44
  • 59
3

Simply reassign the STDERR filehandle ...

use IO::Tee;
my $log_filename = "log.txt";
my $log_filehandle;
open( $log_filehandle, '>>', $log_filename )
  or die("Can't open $log_filename for append: $!");
my $tee = IO::Tee->new( $log_filehandle, \*STDOUT );
*STDERR = *$tee{IO};
select $tee;

Should mention that I tested this on Windows, it works, however I use StrawberryPerl.

glob
  • 2,960
  • 17
  • 21
  • hmmmm. This didn't get the STDERR lines to my log file. Might be that I'm using ActivePerl. – Kurt W. Leucht Mar 30 '10 at 22:15
  • works like a charm, also with `STDERR`. This solution should be on top imho. Also positive: `IO::Tee` is packed for Ubuntu, helps a lot in deployment on those systems. – Boris Däppen Mar 18 '16 at 14:50
2

I don't have a windows box to test this on, but perhaps you could do something like making a tied handle which will print to both STDOUT and a log, then redirecting STDOUT and STDERR to it?

EDIT: The only fear I have is the method of storing STDOUT for later use, I have added a second possibility for storing STDOUT for later use should the first not work on Windows. They both work for me on Linux.

#!/usr/bin/perl

use strict;
use warnings;

tie *NEWOUT, 'MyHandle', 'test.log';
*STDOUT = *NEWOUT;
*STDERR = *NEWOUT;

print "Print\n";
warn "Warn\n";

package MyHandle;

sub TIEHANDLE {
  my $class = shift;
  my $filename = shift;

  open my $fh, '>', $filename or die "Could not open file $filename";

  ## Use one of these next two lines to store STDOUT for later use.
  ## Both work for me on Linux, if one does not work on Windows try the other.
  open(OLDSTDOUT, '>&STDOUT') or die "Could not store STDOUT";
  #*OLDSTDOUT = *STDOUT;

  my $self = {
    loghandle => $fh,
    logfilename => $filename,
    stdout => \*OLDSTDOUT,
  };

  bless $self, $class;

  return $self;
}

sub PRINT {
  my $self = shift;
  my $log = $self->{loghandle};
  my $stdout = $self->{stdout};
  print $log @_;
  print $stdout @_;
}
Joel Berger
  • 20,180
  • 5
  • 49
  • 104
1

try :

my logfh;
my $logfn = "some/path/to/file.log";
open ($logfh, '>',$logfn ) or die "Error opening logfile $logfn\n";
my $tee = IO::Tee->new( $logfh);
my $tee2 = IO::Tee->new( $logfh, \*STDOUT );
# all naked print statements will send output to log file
select($tee);
# all STDERR print statements will send output to console
*STDERR = *$tee2{IO};

All print statements that do not specify a file handle (that is, any regular messages) will send output to the log file. All print statements that use the STDERR file handle (that is, all errors) will send output to both the console and the log file.

Alan
  • 1,889
  • 2
  • 18
  • 30
Divyanand
  • 11
  • 1
0

So you want STDERR to behave like STDOUT, going to both the screen and the same log file? Can you just dup STDERR with

open(STDERR, ">&STDOUT") or warn "failed to dup STDOUT:$!";

(I don't know offhand whether you would do this before or after the call to IO::tee->new).

mob
  • 117,087
  • 18
  • 149
  • 283
  • 1
    Yeah, I thought of that too. But no matter where in the code I put this line, it does not get the error messages into the log file. – Kurt W. Leucht Oct 08 '09 at 14:02
0

I wrote a minimalistic perl logger with configurable dynamic logging giving you the following API:

        use strict ; use warnings ; use Exporter;
        use Configurator ; 
        use Logger ; 


        #   anonymous hash !!!
        our $confHolder = () ; 

        sub main {

                # strip the remote path and keep the bare name
                $0=~m/^(.*)(\\|\/)(.*)\.([a-z]*)/; 
                my $MyBareName = $3; 
                my $RunDir= $1 ; 

                # create the configurator object 
                my $objConfigurator = new Configurator($RunDir , $MyBareName ); 
                # get the hash having the vars 
                $confHolder = $objConfigurator ->getConfHolder () ; 
                # pring the hash vars 
                print $objConfigurator->dumpIni();  

                my $objLogger = new Logger (\$confHolder) ; 
                $objLogger->LogMsg  (   " START MAIN " ) ;  

                $objLogger->LogMsg  (   "my \$RunDir is $RunDir" ) ; 
                $objLogger->LogMsg  (   "this is a simple message" ) ; 
                $objLogger->LogErrorMsg (   "This is an error message " ) ; 
                $objLogger->LogWarningMsg   (   "This is a warning message " ) ; 
                $objLogger->LogInfoMsg  (   "This is a info message " ) ; 
                $objLogger->LogDebugMsg (   "This is a debug message " ) ; 
                $objLogger->LogTraceMsg (   "This is a trace message " ) ; 
                $objLogger->LogMsg  (   "using the following log file " .  "$confHolder->{'LogFile'}" ) ; 
                $objLogger->LogMsg  (   " STOP MAIN \n\n" ) ; 

        } #eof main 



        #Action !!!
        main(); 

        1 ; 

        __END__
Yordan Georgiev
  • 5,114
  • 1
  • 56
  • 53