6

フォークを介して複数の子プロセスを生成する親プロセスがあります。親プロセスと子プロセスによるログファイルを分離したい。問題は、子プロセス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実行したもの (t ティー) をプログラムするためです。フォーク (tee 用) はマスター プロセスの STDOUT を取得するためtee、親のログ ファイルに書き込みます。したがって、マスタープロセスの STDOUT ハンドルを使用して取り消す必要があると思います。または、2 番目の方法 - の使用を削除するtee- 最も簡単な方法です。

また、以下のsetEnvironment関数では、OUTおよびERRファイルハンドルを作成する目的がわかりません。

これは、上記の問題に関する誰かの回避策のようです。grep -rE ' \bERR\b' .使用されているかどうかにかかわらず、コードで検索できます。おそらく誰かが、実際の STDOUT と STDERR を後で使用するために保存したいと考えていました。

于 2012-11-05T10:37:55.490 に答える
2

元のコードの意図は次のようになります

  1. スクリプトが端末などから開始されると、集計された親と子の出力が端末に提供されます
  2. さらに、親出力parent.logのコピーを に、子出力のコピーを に提供します。child.log

@Unk の回答は 2. に関する限り正しく、 を使用するどのコードよりも可動部分が少ないことにtee注意してください

上記の 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.

したがって、あなたの質問の1つに答えて、設定するコードを書いた人は誰でも、上記のことを正確に念頭に置いていたに違いありませんOUTERR(元のコードのインデントの違いは、過去に誰かが削除したことを示しているのではないかと思わずにはいられません。あなたが今追加しなければならないコードに似ています。)

一日の終わりに得られるものは次のとおりです。

$ 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

他のすべての答えは正しいです(特にPSIaltの場合)-私は、質問のコードに明らかに近い修正されたコードで答えられることを望んでいます。注意すべき重要な点:

「|tee-ai...」

teeコマンドは、指定されたファイルに印刷しながら、標準入力を標準出力に出力します。PSIaltが言うように、それを削除することは、各プロセスの出力が正しいファイルにのみ送られるようにする最も簡単な方法です。

親のループ内のsetEnvironment()

元のコードは常にSTDOUTをteeedファイルにリダイレクトしています。したがって、STDOUTを再キャプチャします。以下の私のコードを考えると、setEnvironment上に移動すると#parent process code goes here、1つを除くすべての「RealSTDOUT」と「RealSTDERR」が実際にparent.logに表示されます。

オプション

理想は、ロギングのためにSTDOUT/STDERRをリダイレクトすることへの依存を取り除くことです。私は専用のlog($level, $msg)関数を持っていて、すべてのコードをそれを使用するように動かし始めます。最初は、それが既存の動作の単なるファサードである場合は問題ありません。対象となるコードの適切なしきい値に達したときに、単に切り替えることができます。

それが基本的なスクリプトであり、ばかげて大きなログを生成しない場合は、grepできるプレフィックス(例:'PARENT:' /'CHILD:')を付けてすべてをSTDOUTに出力してみませんか?

質問の範囲から少し外れていますが、ロギングに対してより構造化されたアプローチを使用することを検討してください。Log::Log4perlなどのCPANロギングモジュールの使用を検討します。このようにして、親と子は、ファイルハンドルをいじくり回すのではなく、正しいログカテゴリを要求するだけで済みます。その他の利点:

  • 出力を標準化する
  • オンザフライでの再構成を許可します-実行中のシステムでログレベルを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.log:

child
child
child
child
child

parent.log:

parent
parent
parent
parent
parent

端末から取得したSTDOUT:

Real STDOUT (x5 lines)

端末から取得したSTDERR:

Real STDERR (x5 lines)
于 2012-11-10T13:54:44.153 に答える
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

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 に答える