Reputation: 5247
Have a Parent process which spawns multipe child process via fork. I want the log files by the parent and child process to be separate. The Problem is child process STDOUT gets redirected into the parent log file as well as the child log file. Not sure what i need to change to avoid child process log message to get into the parent log file. Also i dont understand in the below setEnvironment function the purpose of creating OUT and ERR file handle. This is a existing code so i kept as it is. In the parent process and child process i set the variable $g_LOGFILE to contain different file name so that separate log files are created. Also i call setEnvironment function in both parent and child process. I tried by closing STDOUT,STDERR,STDIN in the child process and calling the setenvironment but it was not working 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
Upvotes: 6
Views: 8140
Reputation: 1103
All the other answers are correct (PSIalt's in particular) - I'm merely hoping that I can answer with corrected code that is identifiably close to that in the question. The key things to notice:
The tee commands prints its standard in to its standard out while also printing to the given file. As PSIalt says, removing it is the easiest way to ensure each process' output goes only to the correct file.
The original code is constantly redirecting STDOUT back to the tee
ed file. Therefore recapturing STDOUT. Given my code below, if you moved setEnvironment
to above #parent process code goes here
you would see all but one 'Real STDOUT' and 'Real STDERR' actually appearing in parent.log.
The ideal is to remove any reliance on redirecting STDOUT / STDERR for logging. I would have a dedicated log($level, $msg)
function and start to move all code to using it. Initially it's OK if it is simply a façade for the existing behaviour - you can simply switch it out when you reach an appropriate threshold of code covered.
If it's a basic script and doesn't produce stupidly large logs, why not just print everything to STDOUT with some prefix you can grep for (e.g. 'PARENT:' / 'CHILD:')?
It's a bit outside the scope of the question, but consider using a more structured approach to logging. I would consider using a CPAN logging module, e.g. Log::Log4perl. This way, the parent and children can simply request the correct log category, rather than mess around with file handles. Additional advantages:
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 terminal:
Real STDOUT (x5 lines)
STDERR taken from terminal:
Real STDERR (x5 lines)
Upvotes: 0
Reputation: 66721
It would appear that the intent of the original code is as follows:
parent.log
, and a copy of child output in child.log
Note that @Unk's answer is correct as far as 2. goes, and has less moving parts than any code using tee
, but fails to achieve 1.
If it is important to achieve both 1. and 2. above, then take your original code and simply 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") )
...
Incidentally, do not forget to also add $pid
to @pids
if your actual code does not do that already:
...
my $pid = fork;
if ( $pid ) {
push @pids, $pid;
...
Why and how does this work? We simply want to temporarily restore the original STDOUT
immediately before rewiring it into tee
, so that tee
inherits it as its standard output and actually writes directly to the original STDOUT
(e.g. your terminal) instead of writing (in the case of the forked children) through the parent's tee
(which is where the child's STDOUT
normally pointed to before this change, by virtue of inheriting from the paremnt process, and which is what injected those child
lines into parent.log
.)
So in answer to one of your questions, whoever wrote the code to set OUT
and ERR
must have had exactly the above in mind. (I cannot help but wonder whether or not the difference in indentation in your original code is indicative of someone having removed, in the past, code similar to the one you have to add back now.)
Here's 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
Upvotes: 2
Reputation: 158
#!/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;
Upvotes: 0
Reputation: 7357
Ok i tested this code alitle. Here is my sample code. In my code there is similar(not exact) problem: all messages are double-written to childs log file.
So my answers to your questions:
The Problem is child process STDOUT gets redirected into the parent log file as well as the child log file.
This because when you open file with pipe (open(STDOUT, "|tee ...
) as a underlying result your process fork()
to create child process and then exec
into program what you run (tee). Forking(for tee) takes STDOUT of master process so tee
will write into parent's logfile. So i think you must revoke using STDOUT handle for master process. Or, second way - remove use of tee
- its simplest way.
Also i dont understand in the below setEnvironment function the purpose of creating OUT and ERR file handle.
Seems this is someone's woraround about problem above. You can grep -rE '
\bERR\b' .
to search in code if it used or not. Probably someone wanted to save real STDOUT and STDERR to further use.
Upvotes: 3
Reputation: 116407
You can always redirect STDOUT to log file by closing it first and then reopening:
close STDOUT;
open STDOUT, ">", $logfile;
Small downside to this is that once STDOUT is redirected, you will not see any output on terminal during script execution.
If you want parent and child process have different log files, just perform this redirection in both to different 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 have tested that this works as expected on Linux and Windows.
Upvotes: 0