Постепенный вывод в Web-страницу (tail -f для Web)

Date February 2nd, 2010 Author Vitaly Agapov

tailf-iframeПредыстория такова, что мне потребовалось сделать web-интерфейс, позволяющий запускать и останавливать на сервере определённый процесс. И при этом хотелось в режиме реального времени видеть отладочную информацию, которую процесс пишет в свой лог-файл, примерно так, как это делает tail -f в командной строке. В моём случае это был Tomcat и его лог catalina.out, но это не принципиально. С тем же успехом это мог быть, например, Postfix и mail.log. В процессе работы встретились кое-какие подводные камни и интересные решения. Но обо всём по порядку…

Сразу оговорюсь, что для cgi-сценариев в данном случае  использую Perl, поэтому все примеры будут именно на нём.

Итак, на нашей странице есть ссылка start для запуска процесса:

<a onclick="ctrl('start', this)">start</a>
<div id="monitor" />

Клик по этой ссылке будет вызывать JS-функцию, задачей которой является открытие iframe для вывода информации из лог-файла, а также отправка GET-запроса на сервер, который сможет инициировать запуск процесса и вернуть в ответе содержимое лог-файла в асинхронном режиме для помещения в тот самый iframe.

function ctrl(action,el) {
     var browser=navigator.appName;
     var ifrstr = browser.isIE ? '<iframe name="imonitor" src="/blank.html">' : 'iframe';
     var iframe = document.createElement(ifrstr);
     with(iframe){
          name = "imonitor";                     //  не для IE
          setAttribute("name", "imonitor");
          id = "imonitor";                          // для всех браузеров
     }
     document.getElementById('monitor').appendChild(iframe);
     iframe.setAttribute('src',"/cgi-bin/script?action="+action);
}

Естественно, это лишь основной костяк нужной нам функциональности. Можно добавить, например, скрытие ссылки start на запуск приложения и вывод ссылки stop на его остановку. Но это всё легко решается, да к тому же и не касается темы статьи.

Осталось определиться непосредственно со скриптом script, выполняющим основную работу:

#!/usr/bin/perl
use CGI;
use warnings;
use CGI::Carp qw/fatalsToBrowser warningsToBrowser/;
$cgi = new CGI;
my $action=$cgi->param("action");
if ($action eq "start")
{
     use File::Tail;
     $|=1;
     print qq~
     ~;
     my $command="/external/command/for/starting/application";
     system $command;
     my $file=File::Tail->new(name=>"/path/to/logfile", maxinterval=>1, adjustafter=>7);
     my $time=time();
     my $curr_time;
     my $work_trigger=1;
     while ($work_trigger) {
          ($nfound,$timeleft,@pending)=File::Tail::select(undef,undef,undef,1,$file);
          unless ($nfound) {
               $curr_time=time();
i               f ($curr_time-$time>40) { $work_trigger=0; }
          } else {
               $time=time();
               foreach (@pending) {
                     print $_->read;
               }
          }
      }
      print "Finish monitoring...";
exit(0);
}

Что здесь можно прокомментировать…

Во-первых, переменная $| определяет autoflush, то есть как раз нужный нам асинхронный режим вывода.
Во-вторых, используется модуль File::Tail, который легко найти на cpan.org и там же прочитать про него более подробную документацию. основная его полезность определяется возможностью определения таймаута, по истечении которого при условии, что в лог-файл не добавляются никакие новые записи, http-транзакция закрывается. Эксперименты с системным вызовом tail -f не принесли нужных плодов, потому как задача прекращения работы сценария так и не нашла красивого решения. В моём примере таймаут составляет 40 секунд, по истечении которых выдаётся сообщение “Finish monitoring…” и сценарий закрывается. Соответственно, в iframe больше никакая информация не выводится.

На этом вроде всё. Есть смысл еще в своих таблицах стилей указать стиль для iframe для более удобного чтения выводимых логов. Например:

iframe { width: 600px; height: 200px; }

А также можно подумать об автоматической прокрутке iframe к последней добавленной строчке.

But not yet. Not yet.

Ссылки:

http://search.cpan.org/~mgrabnar/File-Tail-0.99.3/Tail.pm

Tags: ,
Category: Apache, Perl, Web-dev | 5 Comments »

Comments

5 комментариев на “Постепенный вывод в Web-страницу (tail -f для Web)”

  1. n0mad

    Спасибо, пытаюсь запустить по рецепту, но в плане перла ничего специально не уставливал (никогда его не использовал), поэтому поймал ошибку. Может подскажете какой именно порт перла заинсталлить. Об этом вообще ни слова в рецепте

    Software error:

    Can't locate File/Tail.pm in @INC (you may need to install the File::Tail module) (@INC contains: /usr/local/lib/perl5/site_perl/mach/5.18 /usr/local/lib/perl5/site_perl /usr/local/lib/perl5/5.18/mach /usr/local/lib/perl5/5.18 /usr/local/lib/perl5/site_perl/5.18 /usr/local/lib/perl5/site_perl/5.18/mach .) at /usr/local/www/apache24/cgi-bin/script line 9.
    BEGIN failed–compilation aborted at /usr/local/www/apache24/cgi-bin/script line 9.

    For help, please send mail to the webmaster (admin@craftnet.loc), giving this error message and the time and date of the error.

  2. n0mad

    В общем сделал cpan install File::Tail с автоконфигурацией, чтото подхватилось и стало вылазить это. 

    Software error:

    syntax error at /usr/local/www/apache24/cgi-bin/script line 23, near ") {"

    syntax error at /usr/local/www/apache24/cgi-bin/script line 24, near "} else"

    syntax error at /usr/local/www/apache24/cgi-bin/script line 30, near "}"

    syntax error at /usr/local/www/apache24/cgi-bin/script line 33, near "}"

    Execution of /usr/local/www/apache24/cgi-bin/script aborted due to compilation errors.

     

    #!/usr/bin/perl
    use CGI;
    use warnings;
    use CGI::Carp qw/fatalsToBrowser warningsToBrowser/;
    $cgi = new CGI;
    my $action=$cgi->param("action");
    if ($action eq "start")
    {
         use File::Tail;
         $|=1;
         print qq~
         ~;
         # my $command="/usr/local/bin/sudo /files/backup/20150427-backup/scripts/ttest.sh";
         my $command="/files/backup/20150427-backup/scripts/ttest.sh";
         system $command;
         my $file=File::Tail->new(name=>"/web/sites/restore.log", maxinterval=>1, adjustafter=>7);
         my $time=time();
         my $curr_time;
         my $work_trigger=1;
         while ($work_trigger) {
              ($nfound,$timeleft,@pending)=File::Tail::select(undef,undef,undef,1,$file);
              unless ($nfound) {
                  $curr_time=time();
                  if ($curr_time-$time>40) $work_trigger=0; else { $time=time(); foreach (@pending) { print $_->read; } }
              }
          }
          print "Finish monitoring…";
    exit(0);
    }

     

  3. n0mad

    хм. в общем поправил код, if в частности, в статье оно на 23 строчке разъехалось. в общем, сейчас девствнно чистое окно никаких ошибок, только лог пишет о таймауте. 

    upstream timed out (60: Operation timed out) while reading upstream, client: 192.168.0.101, server: localhost, request: "GET /cgi-bin/script?action=start HTTP/1.1", upstream: "http://127.0.0.1:81

  4. n0mad

    в общем полный Operation timed out  ловлю, когда пытаюсь скормить ему sh как то так: 

    my $command="/usr/local/bin/sudo /files/backup/scripts/ttest.sh";

    на

    my $command="ls -l /"

    только после того как увеличил значения read/send_timeout в nginx

    http {

        proxy_send_timeout  600s; 
        proxy_read_timeout  600s;

    }

    вывелось дерево корня, спустя минуты ожидания. 

    картинка:

    http://funkyimg.com/i/WoCf.png

    в логах апача чтото типа 

    [Tue Apr 28 11:15:49.553413 2015] [cgi:error] [pid 16442] [client 192.168.0.101:14702] AH01215: [Tue Apr 28 11:15:49 2015] script: Name "main::timeleft" used only once: possible typo at /usr/local/www/apache24/cgi-bin/script line 23.: /usr/lo

    cal/www/apache24/cgi-bin/script, referer: http://192.168.0.1/

    [Tue Apr 28 11:16:49.566555 2015] [cgi:warn] [pid 16442] [client 192.168.0.101:14702] AH01220: Timeout waiting for output from CGI script /usr/local/www/apache24/cgi-bin/script, referer: http://192.168.0.1/

    [Tue Apr 28 11:16:49.566623 2015] [core:error] [pid 16442] (70007)The timeout specified has expired: [client 192.168.0.101:14702] AH00574: ap_content_length_filter: apr_bucket_read() failed, referer: http://192.168.0.1/

    щас разбираюсь с ошибками в логе. 

    Но вопросы все таки есть по sh – как ему кормить sh скрипт восстановления бекапа? (в данном случае sh восстановления из бекапа генерится самим sh бекапа, бекап выполняется по cron, и скрипты восстановления разные для каждой даты), и с какими правами эти скрипты будут выполняться?

  5. n0mad

    сократил  перл до вот такого состояния:

    #!/usr/bin/perl
    use CGI;
    use warnings;
    use CGI::Carp qw/fatalsToBrowser warningsToBrowser/;
    $cgi = new CGI;
    my $action=$cgi->param("action");
    if ($action eq "start")
    {
         use File::Tail;
         $|=1;
         print qq~
         ~;
         my $command="ls -l /";
         system $command;
         print->read; 
    }

    все мигом транслируется в фрейм без всяких таймаутов, только с кучей ошибок после выполнения команды типа

    <h1>Software error:</h1>

    <pre>Can't locate object method &quot;read&quot; via package &quot;1&quot; (perhaps you forgot to load &quot;1&quot;?) at /usr/local/www/apache24/cgi-bin/script line 17.

    </pre>

    <p>

    For help, please send mail to the webmaster (<a href="mailto:admin@craftnet.loc">admin@craftnet.loc</a>), giving this error message 

    and the time and date of the error.

     

    </p>

    <!– warning: Use of uninitialized value $_ in print at /usr/local/www/apache24/cgibin/script line 17. –>

    картинка:

    http://funkyimg.com/i/WoD7.png

    значит это не вина апача с нжинксом, – видимо чтото в перлах изменилось за 5 лет. надеюс, дорогой автор, появишся тут когда нибудь, и поможешь, тем кто тебя читает, все таки хочется добить этот tail -f  :) 

    еще вопрос с sh остался в силе и еще не совсем понятно для чего нужна секция с 

    my $file=File::Tail->new(name=>"/path/to/logfile", maxinterval=>1, adjustafter=>7);

    в процессе работы он на первый взгляд его совсем не трогает – не читает, не пишет

     

Leave a comment

 Comment Form