Redirecting STDOUT to a child process

Create a parent process that starts the multipe child process through fork. I want the log files of the parent and child processes to be separate. The problem is the child process. STDOUT is redirected to the parent log file, as well as to the child log file. Not sure what I need to change to avoid the process event log message in order to get into the parent log file. Also, I do not understand the purpose of creating the file descriptor OUT and ERR in the setEnvironment function below. This is existing code, so I saved it. In the parent process and the child process, I set the $ g_LOGFILE variable to contain a different file name in order to create separate log files. I also call the setEnvironment function in both the parent and child processes. I tried closing STDOUT, STDERR, STDIN in the child process and calling setenvironment, but it did not work properly.

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
source share
5 answers

Ok, I tested this code. Here is my sample code . My code has a similar (not exact) problem: all messages are written twice to the childs log file.

So my answers to your questions:

The problem is the child process. STDOUT is redirected to the parent log file as well as to the log file.

This is because when you open a file using pipe ( open(STDOUT, "|tee ... ) as the main result, your fork() process creates a child process and then exec into the program you are running (tee) The fork (for the tee) accepts the STDOUT of the main process, so tee will write to the parent log file. Therefore, I think you should cancel the use of the STDOUT handle for the master process. Or, secondly, remove the use of tee - its easiest way.

Also, I do not understand the purpose of creating the file descriptor OUT and ERR in the setEnvironment function below.

Someone seems to be bothering the problem above. You can grep -rE ' \bERR\b' . look in the code if it is used or not. Probably someone wanted to keep the real STDOUT and STDERR for later use.

+3
source

It seems that the intention of the source code is as follows:

  • when the script is run, say, from the terminal, and then provides aggregated parent and child output to the terminal
  • optionally specify a copy of the parent output in parent.log and a copy of the child output in child.log

Please note that @Unk answer is correct until 2. goes and has fewer moving parts than any code using tee , but fails to execute 1.

If it is important to achieve both 1. and 2. above , then take the source code and just add the following at the top of your setEnvironment method

 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") ) ... 

By the way, be sure to also add $pid to @pids if your actual code does not:

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

Why and how does it work? We just want to temporarily restore the original STDOUT just before reconfiguring it to tee so that tee inherits it as standard output and actually writes directly to the original STDOUT (for example, your terminal) instead of writing (in the case of bifurcated children) through the parent tee (exactly where the STDOUT child usually indicated before this change, due to the inheritance from the paremnt process, and which is what these child lines entered in parent.log .)

Therefore, when answering one of your questions, the one who wrote the code for installing OUT and ERR should keep this in mind. (I cannot help but wonder if the difference in the indentation in your source code is an indicator of who in the past deleted the code, similar to the one you should add back.)

Here is what you now get at the end of the day:

 $ 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 
+1
source

You can always redirect STDOUT to a log file , close it first, and then open it again :

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

A small drawback is that after the STDOUT redirect, you will not see any output on the terminal during the execution of the script.

If you want the parent and child processes to have different log files, just do this redirection in both log files after fork() , something like this:

 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"; } 

I tested that this works on both Linux and Windows.

0
source
 #!/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; 
0
source

All other answers are correct (in particular, PSIalt). I just hope I can answer with a revised code that exactly matches this question. Key factors to note:

"| tee -ai ..."

Tee commands print their standard in the standard order, and also print to this file. As PSIalt says, deleting is the easiest way to ensure that each process only outputs the correct file.

setEnvironment () inside the loop for the parent

The source code constantly redirects STDOUT back to the tee ed file. Therefore return STDOUT. Given my code below, if you moved setEnvironment to the above #parent process code goes here , you will see all but one of “Real STDOUT” and “Real STDERR” that actually appear in parent.log.

Functions

Ideal is to remove any STDOUT / STDERR redirect dependency for logging. I will have a dedicated log($level, $msg) function log($level, $msg) and start moving all the code to use it. Initially, this is normal if it’s just a façade for existing behavior - you can just turn it off when you reach the appropriate threshold for the code that is covered.

If this is a basic script and doesn't produce stupidly large logs, why not just print everything in STDOUT with some kind of prefix that you can grep for (for example, "PARENT: '/' CHILD: ')?

This is a bit out of the question, but consider using a more structured approach to logging. I would consider using a CPAN registration module, for example. Log :: Log4perl . Thus, the parent and children can simply request the correct category of magazines, and not interfere with the work with files. Additional benefits:

  • Standardize output
  • Allow reconfiguration on the fly - change the logging level from ERROR to DEBUG on a running system with an error
  • It is easy to redirect output - you do not need to change the code to reorder log files, rotate files, redirect to socket / database, etc.
 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 taken from the terminal:

 Real STDOUT (x5 lines) 

STDERR taken from terminal:

 Real STDERR (x5 lines) 
0
source

Source: https://habr.com/ru/post/1443369/


All Articles