ThorstenHirsch has asked for the wisdom of the Perl Monks concerning the following question:

Hi.

I'm writing a script, which shall be capable of running an external application in the background, catching its STDOUT and STDERR and then write it into a logfile.

The problem is, that the application never exits (e.g. think of edonkey client) and the name of the logfile changes every day.

I tried the following:
while (1){ #generate filename for today => $SCRIPTLOG if ($SCRIPTLOG ne $SCRIPTLOG_OLD){ if ($pid ne "") { close(LOGTO); } open(LOGTO, ">> $SCRIPTLOG") select(LOGTO); } $SCRIPTLOG_OLD=$SCRIPTLOG; if ($pid eq "") $pid=open3(NULL, ">&LOGTO", ">&LOGTO", "application.ex +e $parameters"); sleep(30); }
So every 30 secs is tested if a new day has broken and if so, the logfile $SCRIPTLOG is changed.

However, this doesn't work. application.exe still writes in the old $SCRIPTFILE.

Please help! I already asked in two other Perl places on the web, but they didn't know a solution, either :(

Thorsten

20030410 Edit by Corion: Fixed small typo in title (s/cath/catch/)

Replies are listed 'Best First'.
Re: how to cath STDOUT from an external app, that never exits?
by Corion (Patriarch) on Apr 09, 2003 at 08:20 UTC

    The problem is that the program (and file handles) work differently than you think :

    1. You open the log file. The OS gives you back a number. Let's call this number the 'file handle'.
    2. The external program gets run, and is handed that number.
    3. A new day breaks.
    4. Your program closes its file handle via the number.
    5. Your program opens a new file and gets a new handle.
    6. The still running application still writes to the old file handle.

    Under Linux and possibly also under Windows NT and higher, you can rename the file while it is still open, depending on the open mode / locking mode (there is a savelogs script that does just that). If this does not work with the external program, you will have to stop and restart the application to rotate the logfile.

    Update: Fixed link, thanks to Tomte

    perl -MHTTP::Daemon -MHTTP::Response -MLWP::Simple -e ' ; # The $d = new HTTP::Daemon and fork and getprint $d->url and exit;#spider ($c = $d->accept())->get_request(); $c->send_response( new #in the HTTP::Response(200,$_,$_,qq(Just another Perl hacker\n))); ' # web
      Thank you, so far.

      But I'd prefer not to rely on the savelogs script, but use a self written method.
      Is it possible to use pipes, which don't wait for the application to terminate? I mean to open a pipe and run the application in the background. That's what I tried with open(), but it seemed for me that open() always waits for application to terminate, before it gives STDOUT and STDERR to the pipe.

      Btw: the OS is Win2000 Server.

      Bye. Thorsten

        I think that you will have to write a wrapper application that reads the executable output line by line and writes that output into a different logfile - as long as you can make the external application write everything to stdout/stderr, this should be possible. I don't have the time to create a working script, but here are some simple cases that could work (depending on the application) :

        open APP, 'application.exe |' or die "Uhoh : $!"; while (<APP>) { my $filename = get_logfilename_of_today; open LOG, "<", $filename; print LOG $_; close LOG; };

        The above part only works if the application does not write to STDERR. perldoc perlipc suggests the following snipped if you need to read and write :

        use FileHandle; use IPC::Open2; $pid = open2(*Reader, *Writer, "cat -u -n" ); print Writer "stuff\n"; $got = <Reader>;

        If you need to read and write, also take a look at IO::Select.

        perl -MHTTP::Daemon -MHTTP::Response -MLWP::Simple -e ' ; # The $d = new HTTP::Daemon and fork and getprint $d->url and exit;#spider ($c = $d->accept())->get_request(); $c->send_response( new #in the HTTP::Response(200,$_,$_,qq(Just another Perl hacker\n))); ' # web
Re: how to cath STDOUT from an external app, that never exits?
by BrowserUk (Patriarch) on Apr 09, 2003 at 10:42 UTC

    You don't say what else your script would be doing besides rotating the logfile for the external process, if anything?

    If this is the only purpose of the script, then you might be better to pipe the output from the external process directly into the script via command line redirection and have the script re-write its input to the logfile, rotating the logs as required.

    The simplest way to do this is to use the pl2bat command to convert your script into a .bat file as CMD.exe doesn't handle redirection from/to perl script directly. See the docs on pl2bat for further information on this.

    Here is a short example script (with very crude date handling) to demonstrate the process.

    #! perl -sw use strict; use POSIX qw[strftime]; my $lastdate = 0 ; while ( <STDIN> ) { my $now = strftime '%Y%m%d', localtime(); if ( $lastdate ne $now ) { close STDOUT; open STDOUT, '>', "log$now" or die $!; $|++; $lastdate = $now; } print STDOUT '>', $_; }

    This is what it looks like once it has been pl2bat'd

    You would use it like this (assuming you named the script logger.pl and did pl2bat logger.pl)

    extcommand | logger

    As is, it will create logfiles in the cwd with names like log20030409, with the name changing when the first line is written by the external command after midnight. It might get you going on something.


    Examine what is said, not who speaks.
    1) When a distinguished but elderly scientist states that something is possible, he is almost certainly right. When he states that something is impossible, he is very probably wrong.
    2) The only way of discovering the limits of the possible is to venture a little way past them into the impossible
    3) Any sufficiently advanced technology is indistinguishable from magic.
    Arthur C. Clarke.
      My mainscript does a few more things, but...

      ...if I understand you correctly, I can do the following:

      Mainscript:
      system("application.exe 2>&1 | logscript.bat &");
      which opens application.exe in the background and gives all STDOUT and STDERR to logscript.bat.

      And I have to write a pl2bat'ed logscript.bat, which only has to cope with its STDIN, the way you coded it. Right?
      I'll try this now.

      Thanks!

      Thorsten
        Hmmm...the thing with the logfile works. Thank you, my hero ;-)

        But I cannot get my application.exe starting up in the background! Both
        system("application.exe 2>&1 | logscript.bat &"); open3(NULL, ">&NULL", ">&NULL", "application.exe 2>&1 | logscript.bat +&");

        wait for application.exe to be finished or terminated.

        I'd be glad if someone would help me with this last piece of the puzzle.

        Thorsten
Re: how to cath STDOUT from an external app, that never exits?
by Thelonius (Priest) on Apr 09, 2003 at 15:44 UTC
    Here is a version for ActivePerl:
    #!perl -w use Getopt::Std; use POSIX; use strict; my %opt; getopts("c", \%opt); if ($opt{c}) { dochild(); } else { # MSWin version use Win32::Process; my $ProcessObj; Win32::Process::Create($ProcessObj, 'C:\Perl\bin\wperl.exe', "perl $0 -c", 0, NORMAL_PRIORITY_CLASS|DETACHED_PROCESS, ".")|| die ErrorReport(); } sub bgerror { # Here I am just dying, but you may want to do something # else since this is a background process and the # errors are not going anywhere die $_[0]; } sub dochild { my $tomorrow = 0; my $SCRIPTLOG; open APP, "cat testfile |" or bgerror("Cannot execute app.exe: $!\n" +); while (<APP>) { if (time() >= $tomorrow) { my $now = time(); $SCRIPTLOG = logname($now); $tomorrow = gettomorrow($now); open LOG, ">", $SCRIPTLOG or bgerror("Cannot open $SCRIPTLOG: $!\n"); select(LOG); $| = 1; } print $_; } bgerror("App.exe exited\n"); } sub logname { my ($sec,$min,$hour,$mday,$mon,$year) = localtime($_[0]); sprintf "log%d%02d%02d", $year+1900, $mon+1, $mday; } sub gettomorrow { my ($sec,$min,$hour,$mday,$mon,$year) = localtime($_[0]); my $tomorrow = 25*60*60 + POSIX::mktime(0 ,0, 0, $mday, $mon, $year,0 +,0,-1); # 25 hours from midnight today (25 because of DST shift) ($sec,$min,$hour,$mday,$mon,$year) = localtime($tomorrow); return POSIX::mktime(0 ,0, 0, $mday, $mon, $year,0,0,-1); } sub ErrorReport{ Win32::FormatMessage( Win32::GetLastError() ) . "\n"; }
    and here is a version for Unix or for cygwin Perl (cygwin is Unix-like environment for Win32):
    #!perl -w use Getopt::Std; use POSIX; use strict; # Unix/cygwin version my $child = fork(); if (not defined($child)) { die "Fork unsuccessful: $!\n"; } if ($child == 0) { dochild(); } sub bgerror { # Here I am just dying, but you may want to do something # else since this is a background process and the # errors are not going anywhere die $_[0]; } sub dochild { my $tomorrow = 0; my $SCRIPTLOG; open APP, "cat testfile |" or bgerror("Cannot execute app.exe: $!\n" +); while (<APP>) { if (time() >= $tomorrow) { my $now = time(); $SCRIPTLOG = logname($now); $tomorrow = gettomorrow($now); open LOG, ">", $SCRIPTLOG or bgerror("Cannot open $SCRIPTLOG: $!\n"); select(LOG); $| = 1; } print $_; } bgerror("App.exe exited\n"); } sub logname { my ($sec,$min,$hour,$mday,$mon,$year) = localtime($_[0]); sprintf "log%d%02d%02d", $year+1900, $mon+1, $mday; } sub gettomorrow { my ($sec,$min,$hour,$mday,$mon,$year) = localtime($_[0]); my $tomorrow = 25*60*60 + POSIX::mktime(0 ,0, 0, $mday, $mon, $year,0 +,0,-1); # 25 hours from midnight today (25 because of DST shift) ($sec,$min,$hour,$mday,$mon,$year) = localtime($tomorrow); return POSIX::mktime(0 ,0, 0, $mday, $mon, $year,0,0,-1); }
      Thank you for all your code.

      Thorsten
Re: how to cath STDOUT from an external app, that never exits?
by ThorstenHirsch (Novice) on Apr 09, 2003 at 07:53 UTC
    sorry, i meant: ...application.exe still writes in the old $SCRIPTLOG.