Постепенный вывод в Web-страницу (tail -f для Web)
Date February 2nd, 2010 Author Vitaly Agapov
Предыстория такова, что мне потребовалось сделать 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: iframe, Perl
Category:
Apache, Perl, Web-dev |
5 Comments »
28 April 2015 - 4:11
Спасибо, пытаюсь запустить по рецепту, но в плане перла ничего специально не уставливал (никогда его не использовал), поэтому поймал ошибку. Может подскажете какой именно порт перла заинсталлить. Об этом вообще ни слова в рецепте
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.
28 April 2015 - 4:41
В общем сделал 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);
}
28 April 2015 - 5:31
хм. в общем поправил код, 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
28 April 2015 - 8:32
в общем полный 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, и скрипты восстановления разные для каждой даты), и с какими правами эти скрипты будут выполняться?
28 April 2015 - 9:15
сократил перл до вот такого состояния:
#!/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 "read" via package "1" (perhaps you forgot to load "1"?) 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);
в процессе работы он на первый взгляд его совсем не трогает – не читает, не пишет