如何实时和跨平台将perl代码输出到STDOUT / STDERR和文件?

问题描述

| 我需要将常规Perl代码输出同时输出到屏幕和日志文件中。但是,问题在于该工具的运行时间可能为数小时。使用Capture :: Tiny \的tee,意味着仅在脚本终止后才将日志文件写入日志,这不是很有用。 为了进一步使事情复杂化,我需要从同一进程以及用system()调用的进程中捕获纯perl的输出。 最后,由于雇主的限制,它也需要在Win32上运行。 我还有什么其他选择?     

解决方法

        使用PerlIO :: Util。 刚刚在Strawberry Perl 5.12.1 32位下进行了测试,它可以完美运行,因此可以跨平台使用。以下代码完全符合您的期望。并且由于它修改了实际的STDOUT和STDERR文件句柄,因此对它们的任何写操作都会自动进行。
use strict;
use warnings;

use IO::Handle;
use PerlIO::Util;
use 5.012;

for (*STDOUT,*STDERR) {
  $_->autoflush; $_->push_layer(tee => \">>stdout.txt\");
}

for (1..10) {
  say $_;
  warn $_ unless $_ % 2;
}
    ,        由于提出的解决方案都不令人满意,因此我独自坐下并解决了该问题: 捕获::微小::扩展     ,        如果您的程序在Linux / Unix平台上运行,则可以使用tee命令。 Tee读取stdin并将其写入stdout和指定的文件。 例:
myprogram.pl  2>&1 |tee mylog.txt
唯一的警告是stdout和stderr将合并在同一文件中。 由于您使用的是Windows平台,因此可以搜索Google por tee.exe,也可以尝试使用以下简约的perl版本的tee:
$|=1;

if ( !$ARGV[0] ) {
        print \"Usage:  some_command  \\|  tee.pl [-a]  logfile\\n\";
        exit(1);
}

# Append mode
my $mode=\'>\';
if ($ARGV[0] eq \'-a\')
{
        $mode=\'>>\';
        shift;
}
my $LOGFILE=$ARGV[0];

while (<STDIN>) {
        print;
        open( OUT,\"$mode $LOGFILE\");
        print OUT $_;
        close OUT;

        # Your logic here!
}
例:
perl myprogram.pl  2>&1 |perl tee.pl mylog.txt
如果您要进行系统调用,我真的会尽量避免修改源代码,以捕获越来越多的STDOUT和/或STDERR。     ,        您可以使用IO :: Tee。 创建一个特殊的tee文件句柄。 编辑您的程序。将所有打印更改为stdout更改为该文件句柄的打印。 需要时,重新定义tee文件句柄以仅打印到stdout,或打印到2个或更多文件。 使用``而不是os system()捕获程序输出并将它们打印到特殊的文件句柄。 如果您不想使用任何模块,请创建自己的\“ myprint \”函数。它可以打印到stdout,如果启用了全局标志,也可以打印到日志文件。
sub myPrint
{
   print @_;
   if ($LOGMODE)
   {
      open(LOGFILE,\">>$logfile\");
      print LOGFILE @_;
      close LOGFILE;
   }
}
    ,        
    package Logger ; 
    # docs at the end ... 
            # capture conditionally the output of the command
            # $objLogger->LogDebugMsg ( \"Running $cmd : \\n $cmd \" ) ; 
            # $objLogger->LogDebugMsg ( `$cmd 2>&1` ) ; 

    use lib \'.\' ; use strict ; use warnings ; use Carp qw(cluck); 

    our ( $MyBareName,$LibDir,$RunDir ) = () ; 

    BEGIN {     


        $RunDir = \'\' ; 
        $0 =~ m/^(.*)(\\\\|\\/)(.*)\\.([a-z]*)/; 
        $RunDir = $1 if defined $1 ; 
        push ( @INC,$RunDir) ;    
        #debug print join ( \' \',@INC ) ; 

    } #eof sub

    use Timer ; use FileHandler ; 

    # the hash holding the vars 
    our $confHolder = () ; 

    # ===============================================================
    # START OO


    # the constructor 
    sub new {

        my $self = shift;
        #get the has containing all the settings
        $confHolder = ${ shift @_ } ;                                           
        # Set the defaults ...
        Initialize () ;     
        return bless({},$self);
    } #eof new 


    BEGIN { 

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

            push ( @INC,$RunDir ) ; 

    } #eof BEGIN


    sub AUTOLOAD {

        my $self = shift ; 
        no strict \'refs\'; 
            my $name = our $AUTOLOAD;
            *$AUTOLOAD = sub { 
        my $msg = \"BOOM! BOOM! BOOM! \\n RunTime Error !!!\\nUndefined Function $name(@_)\\n\" ;
        print \"$self,$msg\";
            };
            goto &$AUTOLOAD;    # Restart the new routine.
    }   

    sub DESTROY {

        my $self = shift;
        #debug print \"the DESTRUCTOR is called  \\n\" ; 
        return ; 
    } 

    END { 

        close(STDOUT) || die \"can\'t close STDOUT: $! \\n\\n\"  ; 
        close(STDERR) || die \"can\'t close STDERR: $! \\n\\n\" ; 
    }

    # STOP OO
    # =============================================================================

    sub Initialize { 

        $confHolder = { Foo => \'Bar\',} unless ( $confHolder ) ; 
        # if the log dir does not exist create it 
        my $LogDir = \'\' ; 
        $LogDir = $confHolder->{\'LogDir\'} ; 

        # create the log file in the current directory if it is not specified 
        unless ( defined ( $LogDir )) {
        $LogDir = $RunDir  ; 
        }

    use File::Path qw(mkpath);

        if( defined ($LogDir) &&  !-d \"$LogDir\" ) {  
                mkpath(\"$LogDir\") || 
                cluck ( \" Cannot create the \\$LogDir : $LogDir $! !!! \"  ) ; 
        }

        # START set default value if value not specified =========================
        # Full debugging ....
            $confHolder->{\'LogLevel\'} = 4   
                    unless ( defined ( $confHolder->{\'LogLevel\'} ) )  ; 

            $confHolder->{\'PrintErrorMsgs\'} = 1     
                    unless ( defined ( $confHolder->{\'PrintErrorMsgs\'} ) )  ; 

            $confHolder->{\'PrintDebugMsgs\'} = 1 
                    unless ( defined ($confHolder->{\'PrintDebugMsgs\'})) ; 

            $confHolder->{\'PrintTraceMsgs\'} = 1 
                    unless ( defined ( $confHolder->{\'PrintTraceMsgs\'} )) ; 

            $confHolder->{\'PrintWarningMsgs\'} = 1   
                    unless ( defined ( $confHolder->{\'PrintWarningMsgs\'} ) )  ; 

            $confHolder->{\'LogMsgs\'} = 1
                    unless ( defined ( $confHolder->{\'LogMsgs\'} ) )  ; 

            $confHolder->{\'LogTimeToTextSeparator\'} = \'---\'
                    unless ( defined ( $confHolder->{\'LogTimeToTextSeparator\'} ) )  ; 


        #
        # STOP set default value if value not specified =========================

    } #eof sub Initialize

    # =============================================================================
    # START functions


    # logs an warning message
    sub LogErrorMsg {

        my $self = shift ; 
        my $msg = \"@_\" ; 
        my $msgType = \"ERROR\" ; 

        # Do not print anything if the PrintWarningMsgs = 0 
        return if ( $confHolder->{\'LogMsgs\'} == 0 )     ; 

        # Do not print anything if the PrintWarningMsgs = 0 
        return if ( $confHolder->{\'PrintErrorMsgs\'} == 0 )  ; 

        $self->LogMsg( $msgType,\"$msg\" ) if ( $confHolder->{\'PrintErrorMsgs\'} == 1 ) ; 

    } #eof sub

    # logs an warning message
    sub LogWarningMsg {

        my $self = shift ; 
        my $msg = \"@_\" ; 
        my $msgType = \'WARNING\' ; 

        # Do not print anything if the PrintWarningMsgs = 0 
        return if ( $confHolder->{\'LogMsgs\'} == 0 )     ; 

        # Do not print anything if the PrintWarningMsgs = 0 
        return if ( $confHolder->{\'PrintWarningMsgs\'} == 0 )    ; 

        $self->LogMsg( $msgType,\"$msg\" ) if ( $confHolder->{\'PrintWarningMsgs\'} == 1 ) ;  

    } #eof sub



    # logs an info message
    sub LogInfoMsg {

        my $self = shift ; 
        my $msg = \"@_\" ; 
        my $msgType = \'INFO\' ; 

        # Do not print anything if the PrintWarningMsgs = 0 
        return if ( $confHolder->{\'LogMsgs\'} == 0 )     ; 

        # Do not print anything if the PrintWarningMsgs = 0 
        return if ( $confHolder->{\'PrintInfoMsgs\'} == 0 )   ; 

        $self->LogMsg( $msgType,\"$msg\" ) if ( $confHolder->{\'PrintInfoMsgs\'} == 1 ) ;  

    } #eof sub

    # logs an trace message
    sub LogTraceMsg {

        my $self = shift ; 
        my $msg = \"@_\"  ; 
        my $msgType = \'TRACE\' ; 
        my ($package,$filename,$line) = caller();     


        # Do not print anything if the PrintDebugMsgs = 0 
        return  if ( $confHolder->{\'PrintTraceMsgs\'} == 0 )      ; 

        $msg = \"$msg : FROM Package: $package  FileName: $filename Line: $line  \"  ; 

        # Do not print anything if the PrintWarningMsgs = 0 
        return if ( $confHolder->{\'LogMsgs\'} == 0 )     ; 

        # Do not print anything if the PrintWarningMsgs = 0 
        return if ( $confHolder->{\'PrintTraceMsgs\'} == 0 )  ; 

        $self->LogMsg( $msgType,\"$msg\" ) if ( $confHolder->{\'PrintTraceMsgs\'} == 1 ) ;  

    } #eof sub

    # logs an Debug message
    sub LogDebugMsg {

        my $self = shift ; 
        my $msg = \"@_\" ; 
        my $msgType = \'DEBUG\' ; 

        # Do not print anything if the PrintWarningMsgs = 0 
        return if ( $confHolder->{\'LogMsgs\'} == 0 )     ; 

        # Do not print anything if the PrintWarningMsgs = 0 
        return if ( $confHolder->{\'PrintDebugMsgs\'} == 0 )  ; 

        $self->LogMsg( $msgType,\"$msg\" ) if ( $confHolder->{\'PrintDebugMsgs\'} == 1 ) ;  

    } #eof sub

    sub GetLogFile {

            my $self = shift ; 
            #debug print \"The log file is \" . $confHolder->{ \'LogFile\' } ;  
            my $LogFile = $confHolder->{ \'LogFile\' } ; 

            #if the log file is not defined we create one 
            unless  ( $confHolder->{ \'LogFile\' } ) { 

                $LogFile = \"$0.log\"  ;

            }

            return $LogFile ; 
    } #eof sub 

    sub BuildMsg { 

    my $self = shift ; 
    my $msgType = shift ; 

    my $objTimer= new Timer(); 
    my $HumanReadableTime = $objTimer->GetHumanReadableTime(); 
    my $LogTimeToTextSeparator = $confHolder->{\'LogTimeToTextSeparator\'} ; 

    my $msg = () ; 

        # PRINT TO STDOUT if 
        if (                $msgType eq \'WARNING\' 
                    ||      $msgType eq \'INFO\' 
                    ||      $msgType eq \'DEBUG\' 
                    ||      $msgType eq \'TRACE\'                     )   {

            $msg = \" $HumanReadableTime $LogTimeToTextSeparator $msgType : @_ \\n\" ; 

        }
        elsif ( $msgType eq \'ERROR\' ) {

            $msg = \" $HumanReadableTime $LogTimeToTextSeparator $msgType : @_ \\n\" ; 

        }
        else {
            $msg = \" $HumanReadableTime $LogTimeToTextSeparator $msgType @_ \\n\" ; 
        }



        return $msg ; 
    } #eof sub BuildMsg

    sub LogMsg { 

    my $self = shift ; 
    my $msgType = shift ; 

    my $msg = $self->BuildMsg ( $msgType,@_ ) ; 
    my $LogFile = $self -> GetLogFile();                            


    # Do not print anything if the LogLevel = 0 
    return              if ( $confHolder->{\'LogLevel\'} == 0 ) ; 

        # PRINT TO STDOUT if 
        if (                
                                $confHolder->{\'PrintMsgs\'} == 1 
                    ||      $confHolder->{\'PrintInfoMsgs\'} == 1 
                    ||      $confHolder->{\'PrintDebugMsgs\'} == 1 
                    ||      $confHolder->{\'PrintTraceMsgs\'} == 1 
                    )   {

            print STDOUT $msg ; 
        }

        elsif ( $confHolder->{\'PrintErrorMsgs\'}  ) {

            print STDERR $msg ; 
        }


        if ( $confHolder->{\'LogToFile\'} == 1 )  {   

            my $LogFile = $self -> GetLogFile();
            my $objFileHandler = new FileHandler();

            $objFileHandler->AppendToFile( $LogFile,\"$msg\"  );

        } #eof if

        #TODO: ADD DB LOGGING

    } #eof LogMsg



    # STOP functions
    # =============================================================================


    1;

    __END__



    =head1 NAME

    Logger 

    =head1 SYNOPSIS

    use Logger  ; 


    =head1 DESCRIPTION

    Provide a simple interface for dynamic logging. This is part of the bigger Morphus tool : google code morphus
    Prints the following type of output : 

    2011.06.11-13:33:11 --- this is a simple message  
    2011.06.11-13:33:11 --- ERROR : This is an error message  
    2011.06.11-13:33:11 --- WARNING : This is a warning message  
    2011.06.11-13:33:11 --- INFO : This is a info message  
    2011.06.11-13:33:11 --- DEBUG : This is a debug message  
    2011.06.11-13:33:11 --- TRACE : This is a trace message  : FROM Package: Morphus  
    FileName: E:\\Perl\\sfw\\morphus\\morphus.0.5.0.dev.ysg\\sfw\\perl\\morphus.pl Line: 52   

    =head2 EXPORT


    =head1 SEE ALSO

    perldoc perlvars

    No mailing list for this module


    =head1 AUTHOR

    yordan.georgiev@gmail.com

    =head1 COPYRIGHT AND LICENSE

    Copyright (C) 2011 Yordan Georgiev

    This library is free software; you can redistribute it and/or modify
    it under the same terms as Perl itself,either Perl version 5.8.1 or,at your option,any later version of Perl 5 you may have available.



    VersionHistory: 
    1.4.0 --- 2011.06.11 --- ysg --- Separated actions of building and printing msgs. Total refactoring. Beta . 
    1.3.0 --- 2011.06.09 --- ysg --- Added Initialize 
    1.2.0 --- 2011.06.07 --- ysg --- Added LogInfoErrorMsg print both to all possible
    1.1.4 --- ysg --- added default values if conf values are not set 
    1.0.0 ---  ysg --- Create basic methods 
    1.0.0 ---  ysg --- Stolen shamelessly from several places of the Perl monks ...

    =cut