gpt4 book ai didi

perl - STDOUT 和 STDERR 乱序

转载 作者:行者123 更新时间:2023-12-02 05:32:31 24 4
gpt4 key购买 nike

我有一个 perl 脚本,它将消息写入 STDOUT 和 STDERR(通过 print/croak 语句),但我还将 STDOUT 和 STDERR 重定向到日志文件:

File::Tee::tee STDOUT, ">>", "$logFile" ;
File::Tee::tee STDERR, ">>", "$logFile" ;

现在输出日志文件有来自 STDOUT 和 STDERR 的消息乱序显示。终端上的实际输出也有问题。我试过刷新缓冲区(这里推荐: https://perl.plover.com/FAQs/Buffering.html )但它没有帮助:
select(STDERR) ;
$| = 1 ;
select(STDOUT) ;
$| = 1 ;

有谁知道我必须做什么才能按顺序查看输出(我还尝试另外刷新与 $logfile 对应的文件句柄,但它仍然相同)?

编辑:

感谢所有回复的人。对此的很多讨论都以评论告终,所以我将根据大家的反馈列出我尝试过的几件事。
  • 在我使用 File::Tee 之前,我已经在刷新 STDOUT 和 STDERR。正如@jimtut 所怀疑的那样, File::Tee 确实是罪魁祸首——删除它恢复了控制台上的排序。但我确实想重定向 STDOUT 和 STDERR。
  • @mob 建议使用 IO::Tee 代替,但我还没有完全理解如何在我的代码中按照我想要的方式进行这项工作。
  • @briandfoy 指出,没有可靠的方法来确保以正确的顺序实时查看 2 个单独的文件句柄,并且还建议使用日志记录例程,这是唯一可以写入 STDOUT/STDERR 的地方。 @zimd 进一步指出 File::Tee 使用 fork 是问题的核心,因为 2 个进程不能保证输出的任何顺序。
  • 由于 File::Tee 是罪魁祸首,我试图从代码中删除它。我更新了我的记录器功能以打印到 STDOUT/STDERR 以及另外打印到 $log 文件句柄。为了进一步捕获日志中的警告,我执行了以下操作:
  • sub warning_handler {
    my $msg = $_[0] ;
    print STDERR $msg ;
    print $log $msg if defined $log ;
    }
    $SIG{__WARN__} = \&warning_handler ;

    这对我控制下的所有代码都很有效。现在在控制台和日志文件上都按顺序打印。但是我意识到我不能使用这个解决方案,因为我还调用了其他人的 perl 包以获得某些功能,并且显然我无法拦截在“现成”包中写入 STDOUT/STDERR 的 print/croak 等。所以现在,我没有一个好的解决方案。但是我怀疑如果我能找到在 perl 中拦截 STDOUT/STDERR 的方法,我可能能够得到我需要的东西。

    编辑2:
    我添加了自己的答案,这可能是我通过修改 mob 使用 IO::Tee 而不是 File::Tee 的解决方案来解决问题的最接近的方法,但即使这样也遗漏了一些消息(尽管它修复了排序)。

    编辑3:
    终于找到了“解决方案”
    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;
    }
    }

    使用 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 - 只要系统调用是原子的并且在就交错标准输出/标准错误消息而言,我们应该没问题。
    感谢 briandfoy、mob 和 zimd 的解决方案,我结合他们的答案得出了这个解决方案!从来没有想过对于一个看似非常简单的问题需要仔细研究这个细节。

    最佳答案

    使用两个单独的文件句柄,没有契约(Contract)或保证您会实时看到它们。各种设置和缓冲区会影响这一点,这就是为什么您会看到自动刷新的东西 ($|)。文件或终端的想法相同。

    意识到这是一个架构问题,而不是句法问题。你有两个东西在争夺同一个资源。这通常以泪水告终。当我不知道问题出在哪里时,我会犹豫提出解决方案,但请考虑尝试写入 STDOUT 的任何内容。或 STDERR写入某种收集所有消息的消息代理,并且是唯一写入最终(共享)目的地的东西。例如,想要将条目添加到 syslog 的内容不会写入 syslog;他们将消息发送到写入系统日志的事物。

    一个更 Perly 的示例:在 Log4perl 中,您不会写入最终目的地。您只需记录一条消息,记录器就是弄清楚如何处理它的唯一方法。当我想要模块的这种行为时,我不直接使用输出工具:

    debug( "Some debug message" );

    sub debug {
    my $message = shift;
    output( "DEBUG: $message" );
    }

    sub output { # single thing that can output message
    ...
    }

    然后在 output 中做任何你需要做的事情.

    但是,你不能总是在其他也试图输出东西的东西中控制它。 Perl 让你通过重新定义 warn 来解决这个问题。和 friend 通过在 $SIG{__WARN__} 中添加一个 coderef 来做到这一点.您可以捕获警告消息并对它们做任何您喜欢的事情(例如将它们发送到标准输出)。除此之外是重新打开 STDERR的黑魔法到你可以控制的东西上。没那么糟糕,它被隔离在一个地方。

    在某些时候,另一个人不想要合并的输出,而侵入性的解决方案使得无法将它们分开。我更喜欢灵 active 而不是硬编码约束。如果我只想要错误,我想要一种方法来获取错误。还有许多其他类型的解决方法,例如收集输出流(因此,完全不是侵入性)和各种命令重定向的包装器。

    关于perl - STDOUT 和 STDERR 乱序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60231691/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com