我有一个 perl 脚本,它将消息写入 STDOUT 和 STDERR(通过打印/croak 语句),但我也将 STDOUT 和 STDERR 重定向到日志文件:
File::Tee::tee STDOUT, ">>", "$logFile" ;
File::Tee::tee STDERR, ">>", "$logFile" ;
Run Code Online (Sandbox Code Playgroud)
现在输出日志文件有来自 STDOUT 和 STDERR 的消息显示乱序。此外,终端上的实际输出也出现故障。我试过刷新缓冲区(如这里推荐的那样:https : //perl.plover.com/FAQs/Buffering.html)但它没有帮助:
select(STDERR) ;
$| = 1 ;
select(STDOUT) ;
$| = 1 ;
Run Code Online (Sandbox Code Playgroud)
有谁知道我必须做什么才能按顺序查看输出(我还尝试另外刷新与 $logfile 对应的文件句柄,但它仍然相同)?
编辑:
感谢所有回复的人。关于这个问题的很多讨论都以评论结束,所以我将根据大家的反馈列出我尝试过的一些事情。
sub warning_handler {
my $msg = $_[0] ;
print STDERR $msg ;
print $log $msg if defined $log ;
}
$SIG{__WARN__} = \&warning_handler ;
Run Code Online (Sandbox Code Playgroud)
这对我控制的所有代码都非常有效。现在,控制台和日志文件上的所有内容都按顺序打印。但是我意识到我不能使用这个解决方案,因为我还为某些功能调用了其他人的 perl 包,显然我无法拦截在“现成”包中写入 STDOUT/STDERR 的打印/croak 等。所以现在,我没有很好的解决方案。但是我怀疑如果我能找到某种方法来拦截 perl 中的 STDOUT/STDERR,我也许能够得到我需要的东西。
EDIT2:我添加了我自己的答案,这可能是我通过修改使用 IO::Tee 而不是 File::Tee 的 mob 解决方案来解决问题的最接近的答案,但即使这样也会错过一些消息(尽管它修复了排序)。
EDIT3:终于找到了“解决方案”
use IO::Tee ;
use Capture::Tiny qw(capture);
...
...
select(STDERR) ;
$| = 1 ;
select(STDOUT) ;
$| = 1 ;
open (my $log, ">", $logfilename) ;
*REALSTDOUT = *STDOUT ;
*REALSTDERR = *STDERR ;
*STDOUT = IO::Tee->new(\*REALSTDOUT, $log);
*STDERR = IO::Tee->new(\*REALSTDERR, $log);
# Regular Perl code here which sends output to STDOUT/STDERR
...
...
# system calls / calls to .so needs to be catpured
&log_streams(sub { &some_func_which_calls_shared_object() ; }) ;
sub log_streams {
my ($cr, @args) = @_; # code reference, with its arguments
my ($out, $err, $exit) = capture { $cr->(@args) };
if ($out) {
print STDOUT $out;
}
if ($err) {
print STDERR $err;
}
}
Run Code Online (Sandbox Code Playgroud)
IO::Tee 的使用确保所有 perl 生成的输出到控制台也会进入日志文件,这会立即发生,从而实时更新日志和控制台。由于 IO::Tee 将 STDOUT/STDERR 文件句柄的含义更改为现在引用 teed 句柄,因此它只能从 perl 语句中拦截 stdio,它会错过 sys 调用,因为它们绕过 perl 的 STDOUT/STDERR 句柄。因此,我们捕获系统调用输出,然后使用 log_streams 例程将其转发到现在别名为 STDOUT/STDERR 的流。这会在日志/终端中显示的系统调用生成的输出中造成延迟,但 perl 生成的输出没有延迟 - 即两全其美。请注意,调用子例程 some_func_which_calls_shared_object 生成的 stderr 和 stdout 的顺序没有保留,因为在 log_streams 例程中,我们首先打印到 STDOUT 然后打印到 STDERR - 只要系统调用是原子的并且在交错 stdout/stderr 消息方面没有做太多我们应该没问题。感谢 briandfoy、mob 和 zimd 的解决方案,我结合他们的答案得出了这个解决方案!从来没有想过需要通过这个细节来解决一个看起来非常简单的问题。
使用两个单独的文件句柄,没有合同或保证您会实时看到它们。各种设置和缓冲区会对此产生影响,这就是您看到自动刷新内容 ( $|) 的原因。对于文件或终端来说,这是相同的想法。
意识到这是一个架构问题,而不是一个语法问题。你有两个东西在争夺同一个资源。这通常以泪水结束。当我不知道问题是什么时,我犹豫是否提出解决方案,但请考虑尝试写入STDOUT或STDERR写入某种消息代理的任何内容,该消息代理收集所有消息并且是唯一写入最终(共享)目的地。例如,想要向系统日志添加条目的东西不会写入系统日志;它们向写入系统日志的事物发送消息。
一个更 Perly 的例子:在 Log4perl 中,您不会写入最终目的地。您只需记录一条消息,记录器就是弄清楚如何处理它的唯一因素。当我想要模块的这种行为时,我不直接使用输出工具:
debug( "Some debug message" );
sub debug {
my $message = shift;
output( "DEBUG: $message" );
}
sub output { # single thing that can output message
...
}
Run Code Online (Sandbox Code Playgroud)
然后做任何你需要做的事情output。
但是,你不能总是在其他试图输出事物的事物中控制它。Perl 让你warn通过在$SIG{__WARN__}. 您可以捕获警告消息并对它们执行任何您喜欢的操作(例如将它们发送到标准输出)。除此之外是黑魔法,它重新打开STDERR了你可以控制的东西。它并没有那么糟糕,它被隔离在一个地方。
在某些时候,另一个人不希望合并输出,并且侵入性解决方案使它们无法分离。我更喜欢灵活性而不是硬编码约束。如果我只想要错误,我想要一种方法来获取错误。还有许多其他类型的解决方法,例如收集输出流(因此,完全没有侵入性)和各种命令重定向的包装器。
| 归档时间: |
|
| 查看次数: |
1050 次 |
| 最近记录: |