6

有一个通过 fork 产生多个子进程的父进程。我希望父进程和子进程的日志文件是分开的。问题是子进程 STDOUT 被重定向到父日志文件以及子日志文件。不确定我需要更改什么以避免子进程日志消息进入父日志文件。我也不明白在下面的 setEnvironment 函数中创建 OUT 和 ERR 文件句柄的目的。这是一个现有的代码,所以我保持原样。在父进程和子进程中,我将变量 $g_LOGFILE 设置为包含不同的文件名,以便创建单独的日志文件。我也在父进程和子进程中调用 setEnvironment 函数。我尝试在子进程中关闭 STDOUT、STDERR、STDIN 并调用 setenvironment,但它无法正常工作。

sub setEnvironment()
{   

  unless ( open(OUT, ">&STDOUT") )
   {
          print "Cannot redirect STDOUT";
          return 2;
    }
    unless ( open(ERR, ">&STDERR") )
    {
          print "Cannot redirect STDERR";
          return 2;
    }


  unless ( open(STDOUT, "|tee -ai $g_LOGPATH/$g_LOGFILE") )
  {
          print "Cannot open log file $g_LOGPATH/$g_LOGFILE");
          return 2;
   }
   unless ( open(STDERR, ">&STDOUT") )
   {
                print  "Cannot redirect STDERR");
                return 2 ;
    }
    STDOUT->autoflush(1);

} 


####################### Main Program ######################################

    $g_LOGFILE="parent.log";

  while ($file = readdir(DIR))
 {  
     my $pid = fork;
     if ( $pid ) {

        setEnvironment();
        #parent process code goes here
        printf "%s\n", "parent";
        next;
     }
     $g_LOGFILE="child.log";
     setEnvironment();
     #child code goes here
     printf "%s\n", "child";
     exit;
 }

wait for @pids
4

5 回答 5

3

好的,我测试了这段代码。这是我的示例代码。在我的代码中存在类似(不确切)的问题:所有消息都双重写入子日志文件。

所以我对你的问题的回答:

问题是子进程 STDOUT 被重定向到父日志文件以及子日志文件。

这是因为当您使用管道 ( open(STDOUT, "|tee ...) 打开文件作为底层结果时,您的进程fork()将创建子进程,然后exec进入您运行的程序 (tee)。Forking(for tee) 采用主进程的 STDOUT,因此tee将写入父进程的日志文件。所以我认为你必须撤销对主进程使用 STDOUT 句柄。或者,第二种方法 - 删除使用tee- 它最简单的方法。

我也不明白在下面的 setEnvironment 函数中创建 OUT 和 ERR 文件句柄的目的。

似乎这是某人对上述问题的解决方法。您可以grep -rE ' \bERR\b' .在代码中搜索是否使用过。可能有人想保存真正的 STDOUT 和 STDERR 以供进一步使用。

于 2012-11-05T10:37:55.490 回答
2

看起来原始代码的意图如下:

  1. 当脚本从终端启动时,然后向终端提供聚合的父子输出
  2. 此外,在 中提供父输出parent.log的副本,在 中提供子输出的副本child.log

请注意,就 2. 而言, @Unk 的答案是正确的,并且移动部件比使用 的任何代码都少tee,但未能达到 1.

如果实现上述 1. 和 2.都很重要,那么请使用您的原始代码并在方法顶部添加以下内容setEnvironment

sub setEnvironment()
{
    if ( fileno OUT )
    {
        unless ( open(STDOUT, ">&OUT") )
        {
            print "Cannot restore STDOUT";
            return 2;
        }
        unless ( open(STDERR, ">&ERR") )
        {
            print "Cannot restore STDERR";
            return 2;
        }
    }
    else
    {
        unless ( open(OUT, ">&STDOUT") )
        {
            print "Cannot redirect STDOUT";
            return 2;
        }
        unless ( open(ERR, ">&STDERR") )
        {
            print "Cannot redirect STDERR";
            return 2;
        }
    }
    unless ( open(STDOUT, "|tee -ai $g_LOGPATH/$g_LOGFILE") )
    ...

顺便说一句,如果您的实际代码还没有这样做$pid,请不要忘记添加:@pids

  ...
  my $pid = fork;
  if ( $pid ) {
      push @pids, $pid;
      ...

为什么以及如何工作?我们只是想在重新连接到 之前立即暂时恢复原始文件,以便继承它作为标准输出STDOUTteetee并实际直接写入原始文件STDOUT(例如您的终端),而不是通过父母的 ( 在分叉子项的情况下) 写入tee(这是孩子STDOUT在此更改之前通常指向的位置,由于继承自 paremnt 进程,这就是将这些child行注入到parent.log.)

因此,在回答您的一个问题时,无论谁编写了要设置的代码,OUT并且ERR必须牢记上述内容。(我不禁想知道您原始代码中缩进的差异是否表明有人在过去删除了与您现在必须添加回来的代码类似的代码。)

这是您现在在一天结束时得到的:

$ rm -f parent.log child.log
$ perl test.pl
child
parent
child
parent
parent
child
parent
child
parent
$ cat parent.log
parent
parent
parent
parent
parent
$ cat child.log
child
child
child
child
child
于 2012-11-10T20:42:01.703 回答
0

您始终可以通过先关闭然后重新打开将 STDOUT 重定向到日志文件:

close STDOUT;
open STDOUT, ">", $logfile;

这样做的一个小缺点是,一旦 STDOUT 被重定向,在脚本执行期间您将不会在终端上看到任何输出。

如果您希望父进程和子进程具有不同的日志文件,只需在之后执行此重定向到不同的日志文件fork(),如下所示:

print "Starting, about to fork...\n";
if (fork()) {
    print "In master process\n";
    close STDOUT;
    open STDOUT, ">", "master.log";
    print "Master to log\n";
} else {
    print "In slave process\n";
    close STDOUT;
    open STDOUT, ">", "slave.log";
    print "Slave to log\n";
}

我已经测试过它可以在 Linux 和 Windows 上按预期工作。

于 2012-11-04T23:12:55.380 回答
0
#!/usr/bin/perl 

use strict;
use warnings;
use utf8;
use Capture::Tiny qw/capture_stdout/;

my $child_log = 'clild.log';
my $parent_log = 'parent.log';

my  $stdout = capture_stdout {
        if(fork()){
            my  $stdout = capture_stdout {
                print "clild\n";
            };
            open my $fh, '>', $child_log;
            print $fh $stdout;
            close $fh;
            exit;
        }
        print "parent\n";
   };
            open my $fh, '>', $parent_log;
            print $fh $stdout;
            close $fh;
于 2012-11-07T12:27:29.410 回答
0

所有其他答案都是正确的(尤其是 PSIalt)——我只是希望我能用与问题中的代码非常接近的更正代码来回答。需要注意的关键事项:

“|tee-ai……”

tee 命令将其标准打印到其标准输出,同时也打印到给定文件。正如 PSIalt 所说,删除它是确保每个进程的输出仅发送到正确文件的最简单方法。

父级循环内的 setEnvironment()

原始代码不断将 STDOUT 重定向回teeed 文件。因此重新捕获 STDOUT。鉴于我在下面的代码,如果您移到setEnvironment上面,#parent process code goes here您会看到除了一个“Real STDOUT”和“Real STDERR”之外的所有内容实际上都出现在 parent.log 中。

选项

理想的做法是消除对重定向 STDOUT / STDERR 进行日志记录的任何依赖。我将拥有一个专用log($level, $msg)功能并开始将所有代码移至使用它。最初,如果它只是现有行为的外观是可以的 - 当您达到适当的代码覆盖阈值时,您可以简单地将其切换出来。

如果它是一个基本脚本并且不会产生愚蠢的大日志,为什么不将所有内容打印到 STDOUT 并带有一些您可以使用 grep 的前缀(例如,'PARENT:' / 'CHILD:')?

这有点超出了问题的范围,但考虑使用更结构化的日志记录方法。我会考虑使用 CPAN 日志记录模块,例如Log::Log4perl。这样,父母和孩子可以简单地请求正确的日志类别,而不是乱搞文件句柄。其他优势:

  • 标准化输出
  • 允许即时重新配置 - 在运行但行为不端的系统上将日志记录级别从 ERROR 更改为 DEBUG
  • 轻松重定向输出 - 无需更改代码即可重新排列日志文件、旋转文件、重定向到套接字/数据库等...
use strict;
use warnings;

our $g_LOGPATH = '.';
our $g_LOGFILE = "parent.log";

our @pids;

setEnvironment();

for ( 1 .. 5 ) {
    my $pid = fork;
    if ($pid) {
        #parent process code goes here
        printf "%s\n", "parent";

        print OUT "Real STDOUT\n";
        print ERR "Real STDERR\n";

        push @pids, $pid;
        next;
    }
    $g_LOGFILE = "child.log";
    setEnvironment();

    #child code goes here
    printf "%s\n", "child";
    exit;
}

wait for @pids;

sub setEnvironment {
    unless ( open( OUT, ">&STDOUT" ) ) {
        print "Cannot redirect STDOUT";
        return 2;
    }

    unless ( open( ERR, ">&STDERR" ) ) {
        print "Cannot redirect STDERR";
        return 2;
    }

    unless ( open( STDOUT, '>>', "$g_LOGPATH/$g_LOGFILE" ) ) {
        print "Cannot open log file $g_LOGPATH/$g_LOGFILE";
        return 2;
    }

    unless ( open( STDERR, ">&STDOUT" ) ) {
        print "Cannot redirect STDERR";
        return 2;
    }
    STDOUT->autoflush(1);
}

子日志:

child
child
child
child
child

父日志:

parent
parent
parent
parent
parent

从终端获取的 STDOUT:

Real STDOUT (x5 lines)

从终端获取的 STDERR:

Real STDERR (x5 lines)
于 2012-11-10T13:54:44.153 回答