我需要将普通 Perl 代码的输出同时显示到屏幕和日志文件中。然而,一个问题是该工具的运行时间可能是几个小时。使用 Capture::Tiny 的 tee 意味着只有在脚本终止后才会写入日志文件,这不是很有用。
为了使事情进一步复杂化,我需要从同一进程中捕获直接 perl 的输出,以及使用 system() 调用的进程的输出。
最后,由于雇主的限制,它也需要在 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 读取标准输入并写入标准输出和指定文件。
例子:
myprogram.pl 2>&1 |tee mylog.txt
唯一需要注意的是 stdout 和 stderr 将合并到同一个文件中。
由于您是在 Windows 平台上,您可以搜索 Google 端口 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。
如果您不想使用任何模块,请创建自己的“myprint”函数。它可以打印到标准输出,如果启用了全局标志,也可以打印到日志文件。
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