Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Perl: Общие вопросы > дописать из файла в базу


Автор: box 24.1.2010, 00:53
есть текстовый файл с логом и мне ничего не стоит распарсить его и закидать в базу на примере :

Код

open(LOG, "/var/log/access_data.log") or die "Can't open log $!";
while(<LOG>){
    my ($ip_temp, $host_temp, $string_temp) = split(/\|/, $_);
$query = "INSERT INTO log (ip, host, string) VALUES ('".$ip_temp."', '".$host_temp."', '".$string_temp."')";
$query_handle = $connect->prepare($query);
$query_handle->execute();
}
close('LOG');

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

Автор: Egik2 24.1.2010, 10:53
Если у тебя в логе есть временная метка, а если нет то лучше сделать  smile,
То как вариант могу предложить писать в какой-нить отдельный файл, дату записи, которую ты последнюю
вставил в базу, а потом начинать с этой даты.

Автор: arto 24.1.2010, 12:46
perldoc File::Tail

Добавлено через 31 секунду
или просто tail

Автор: box 24.1.2010, 14:21
Цитата(Egik2 @ 24.1.2010,  10:53)
Если у тебя в логе есть временная метка, а если нет то лучше сделать  smile,
То как вариант могу предложить писать в какой-нить отдельный файл, дату записи, которую ты последнюю
вставил в базу, а потом начинать с этой даты.

дело в том что я спецом убрал временную метку из логов дабы урезать размер лога , да и время выполнения скрипта возрастет если я буду парсить временную мерку и сравнивать ее с записью из базы. а если лог будет 1-5 гигов то сервер то и будет делать что все время парсить логи. нужно както по другому .

Автор: klem4 24.1.2010, 15:11
Не использовать временные метки - странное решение, но можно и без них решить Вашу задачу. Хранить (где угодно) кол-во строк лог-файла, при запуске скрипта сравнивать хранимую цифру с реальным кол-вом строк в логе, и если она меньше, парсить строки, номера которых больше хранимого индекса. Затем пересохранять индекс.

Автор: box 24.1.2010, 15:59
а как быть если файл логов обнулен , тогда счетчик не поможет 

Автор: klem4 24.1.2010, 16:13
обнулен файл, обнуляй и счетчик, не вижу в чем проблема.

Автор: ramus 24.1.2010, 16:22
0) Лог не содержащий время - не лог smile

1) Если лог размером 1-5 гига, то предложение "при запуске скрипта сравнивать хранимую цифру с реальным кол-вом строк в логе" ОЧЕНЬ сильно нагрузит бессмысленной работой сервер (надо читать 1-5 гигов одного и того же файла при каждом запуске программы)

2) Вам рекомендовал уважаемый arto команду tail - это правильное решение.

3) если же хочется реализовать самому, то посмотрите команды seek и tell. 
Идея такова: Когда Ваша программа в первый раз дочитает лог до конца (с записью в БД распарсенных значений), надо в конце файла вывод команды tell сохранить для следующего запуска вашей программы(в БД или в файле). При следующем запуске вы зачитываете последнее сохраненное значение и встаете на эту позицию файла лога командой seek, пропустив уже отпарсенное. Команда seek работает почти мгновенно, почти не зависит от размера файла и практически не нагружает сервер. Опять читаете лог до конца файла с записью в БД распарсенных значений и записываете новое значение tell в БД или файл. 
Узнать, что файл лога отрезался можно сравнив размер зачитанный из БД и реальный размер файла лога. Если реальный размер меньше, то наверняка файл отрезался. При этом Ваша программа должна читать файл с начала. Правда могут быть нюансы если запуски программы реже чем отрезки файла лога. Если Ваша программа должна работать ТОЛЬКО на UNIX(Linux) я бы для этого кроме размера проверял бы еще i-node файла, так как имя лога обычно бывает одно и тоже, а меняется только сам файл, т.е. i-node.

Автор: klem4 24.1.2010, 16:33
Цитата

1) Если лог размером 1-5 гига, то предложение "при запуске скрипта сравнивать хранимую цифру с реальным кол-вом строк в логе" ОЧЕНЬ сильно нагрузит бессмысленной работой сервер (надо читать 1-5 гигов одного и того же файла при каждом запуске программы)


Никогда не слышали про замечательную команду wc ? На логе 300+mb, wc -l Отрабатывает менее чем за 1 секунду.

Автор: ramus 24.1.2010, 16:34
Кстати, а что за БД используется Вами? Она поддерживает переменные привязки (bind)?
Я бы на месте админа БД убил бы эту сессию вместе с разработчиком такой проги ;)
Если БД - ORACLE, то код

while(<LOG>){
    my ($ip_temp, $host_temp, $string_temp) = split(/\|/, $_);
$query = "INSERT INTO log (ip, host, string) VALUES ('".$ip_temp."', '".$host_temp."', '".$string_temp."')";
$query_handle = $connect->prepare($query);
$query_handle->execute();
}

очень быстро забьет sql area сервера БД, понизив тем самым общую производительность сервера

код должен быть таким (примерно, я не проверял):

my $query = "INSERT INTO log (ip, host, string) VALUES (?, ?, ?)";
$query_handle = $connect->prepare($query);

while(<LOG>){
    my ($ip_temp, $host_temp, $string_temp) = split(/\|/, $_);
   $query_handle->execute($ip_temp, $host_temp, $string_temp);
}

Автор: klem4 24.1.2010, 16:37
К моему последнему сообщению: на логе 1.2G результат более плачевный. Так что если файлы большие то вариант мой действительно не проходит.

Автор: ramus 24.1.2010, 16:39
На логе 300+mb, wc -l Отрабатывает менее чем за 1 секунду. 

Забавно smile
А знаете что она (wc) реально делает?
Это говорит, что либо файл уже в кеше, либо сервер ОЧЕНЬ мощный (например дисковая система на внешнем массиве по FC), так как чтение 300MB/секунду это хорошо (естественно если ВЕСЬ сервер не в вашем распоряжении и на нем еще идет полезная работа)

Я против загрузки сервера тупой работой.

Автор: box 24.1.2010, 21:11
да нет , не то это все и сервер ложится при использовании базы , не вывозит нагрузки уже при 10 метровом логе.хоть проц 2 ядра и рама 4 гига .
не вариант юзать базу данных в моем случае , а так хотелось !

Автор: shamber 25.1.2010, 00:19
box, а ложиться скорее всего из-за, того что Вам ramus написал по поводу prepare и execute 

Автор: Egik2 25.1.2010, 09:57
Цитата(shamber @  25.1.2010,  00:19 Найти цитируемый пост)
box, а ложиться скорее всего из-за, того что Вам ramus написал по поводу prepare и execute  

А почему она должна ложиться?
Я всегда думал, что prepare statement наоборот помогает, позволяет держать базе в кэше объект SQL statement, что избавляет от необходимости
каждый раз парсить запрос.


Автор: shamber 25.1.2010, 10:20
Цитата(Egik2 @  25.1.2010,  09:57 Найти цитируемый пост)
что prepare statement наоборот помогает

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

Автор: Egik2 25.1.2010, 10:44
Ну ведь он, это и советует:
Не надо:
Цитата(ramus @  24.1.2010,  16:34 Найти цитируемый пост)
while(<LOG>){
    my ($ip_temp, $host_temp, $string_temp) = split(/\|/, $_);
$query = "INSERT INTO log (ip, host, string) VALUES ('".$ip_temp."', '".$host_temp."', '".$string_temp."')";
$query_handle = $connect->prepare($query);
$query_handle->execute();
}


То есть prepare происходит для каждой записи, что неправильно - и по идее одно и то же, что вставка без prepare.
И как должно быть 

Цитата(ramus @  24.1.2010,  16:34 Найти цитируемый пост)
my $query = "INSERT INTO log (ip, host, string) VALUES (?, ?, ?)";
$query_handle = $connect->prepare($query);

while(<LOG>){
    my ($ip_temp, $host_temp, $string_temp) = split(/\|/, $_);
   $query_handle->execute($ip_temp, $host_temp, $string_temp);
}


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

Разве это не правильно?

Автор: shamber 25.1.2010, 12:16
ramus, предложил верное решение.
позднее время, а также то,что чукча писатель, а не читатель(чукча -> я), и не позволило, вам увидеть эту мысль в моей фразе.
Каюсь

Автор: ramus 26.1.2010, 00:00
"хоть проц 2 ядра и рама 4 гига ."

У нас особо "одаренные" программисты таким образом "напрягали" гораздо более крутой сервер - 8 двухядерных итаниумов с 64 гигами оперативки.
Я не говорю, что это предложение панацея от всего, но то, что надо один раз отпарсить SQL, а потом многократно использовать с разными данными-это существенно облегчит жизнь серверу и админу.

"что prepare statement наоборот помогает, позволяет держать базе в кэше объект SQL statement, что избавляет от необходимости
каждый раз парсить запрос."
Как раз в Вашем варианте происходит парсинг для каждой строки лога, так как каждый раз строка разная, например
INSERT INTO log (ip, host, string) VALUES ('1.1.1.1', 'host_temp', 'string')
INSERT INTO log (ip, host, string) VALUES ('2.2.2.2', 'host_temp', 'string')

В предложенном мной варианте парсинг (в том числе определение плана выполнения) выполняется один раз, 
INSERT INTO log (ip, host, string) VALUES (:ip, :host, :string)
в области хранения SQL запросов ( SQL area) будет только ОДНА строчка (от нашей сессии). Число execute для нее будет равняться числу строк в логе.

В Вашем же случае число строк в sql area БД будет равно числу строк в логе и для каждой такой строки число execute будет =1.

PS
Лучше много раз по разу, чем ни разу много раз ;)

Автор: box 26.1.2010, 06:29
пишу следующее :
Код

$query = "SELECT COUNT(*) FROM log";
$query_handle = $connect->prepare($query);
$query_handle->execute();
$query_handle->bind_columns(\$count);
$query_handle->fetch();
$cnt=$count+1;
$c=1;

#####################

open(LOG, "/var/log/access.log") or die "Can't open log $!";

my $query = "INSERT INTO log (ip, host, string) VALUES (?, ?, ?)";
$query_handle = $connect->prepare($query);

my %hash = ();
while(<LOG>){
    my ($ip_temp, $host_temp, $string_temp) = split(/\|/, $_);
        $hash{$host_temp."|".$ip_temp."|".$string_temp}++;
if($c>$cnt){
 $query_handle->execute($ip_temp, $host_temp, $string_temp);
        }
$c++;
}

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

Автор: shamber 26.1.2010, 11:27
Цитата(box @  26.1.2010,  06:29 Найти цитируемый пост)
$cnt=$count+1;

Цитата(box @  26.1.2010,  06:29 Найти цитируемый пост)
if($c>$cnt){

Цитата(box @  26.1.2010,  06:29 Найти цитируемый пост)
$c++;


У вас тут с логикой все совершенно жестко. Вам предложили пару вариантов, но вы их не хотите использовать, вместо этого изобретаете велосипед, и удивляетесь что ничего не работает. 
Чем Вам не нравится вариант с seek?

Добавлено @ 11:29
Цитата(box @  26.1.2010,  06:29 Найти цитируемый пост)
о у меня он периодически обнуляется

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

Автор: ginnie 26.1.2010, 12:50
box, у меня вот какой вопрос возник: если у Вас лог периодически обнуляется и этот процесс никак не завязан со скриптом, Вы, случайно, часть данных не теряете (которые добавилась после запуска скрипта, но до обнуления лога)?

Автор: box 26.1.2010, 17:02
"ну добавьте в скрипт, чтение параметров файла, к примеру время последней модификации. и все проблемы решены"

к сожалению это проблемы не решает потому как время модификации файла будет меняться каждый раз когда добавляется новая строка файла. у меня была идея создавать lock файл или запись в бд о том что файл обновлен и потом соответсвующим образом обрабатывать лог но это займет больше ресурсов.

Автор: shamber 26.1.2010, 17:05
Цитата(box @  26.1.2010,  17:02 Найти цитируемый пост)
модификации файла будет меняться каждый раз когда добавляется новая строка файла

Цитата(box @  26.1.2010,  17:02 Найти цитируемый пост)
запись в бд о том что файл обновлен 

Цитата(box @  26.1.2010,  17:02 Найти цитируемый пост)
потом соответсвующим образом обрабатывать лог

что в моем предложении, мешает осуществить перечисленное вами?

Автор: arto 26.1.2010, 17:16
интересно, когда вы повторите функциональность обыкновенного tail?
который может следить хоть за дескриптором, хоть за именем...

Автор: box 26.1.2010, 18:32
приведите пожалуйста пример использования tail  в перле , а то я нашел только для командной строки .

Автор: klem4 26.1.2010, 19:22
А почему не хотите использовать tail коммандной строки из perl ? 

Автор: arto 26.1.2010, 20:57
tail | perl самое простое.
либо perldoc -f open

Автор: klem4 26.1.2010, 21:39
Дак можно прямо в перл получать результат tail

Код

my $some_result = `tail ...`;

Автор: arto 26.1.2010, 21:54
нет.
я так понял, что вы не прочитали man tail ? 

Автор: klem4 26.1.2010, 23:34
Цитата(arto @ 26.1.2010,  21:54)
нет.
я так понял, что вы не прочитали man tail ?

не совсем понял, какоe отношение имеет способ вызова tail к прочтению/не прочтению мною man'а.

Автор: arto 26.1.2010, 23:48
а вы прочитайте.

Автор: mvsgt 27.1.2010, 00:11
Цитата(klem4 @  26.1.2010,  23:34 Найти цитируемый пост)
не совсем понял, какоe отношение имеет способ вызова tail к прочтению/не прочтению мною man'а. 


В мане есть опция, позволяющая tail отследить смену файла.

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)