Продолжаем о UCSVLOG. Начало читайте тут – Часть 1. Проблема и Идея
Давайте, для начала, сформируем еще раз требования к логам.
* Легко писать / читать. – никто не хочет тратить ресурсы на такую пустяковую операцию, как логи. Они должны быть читабельны глазами без тулсов. И они должны быть простыми – чем проще механизм, тем он надежнее. Соответственно парсинг таких логов не должен быть сильно тяжелым.
* Shit Happens – отказоустойчивость. Я не хочу сломать структуру логов в момент сбоя. Если в момент записи случится какой-нибудь сбой, то может не дописаться часть записи, и я хочу, чтоб структура такого лога осталась неизменной.
* Индексы – о них и о их преимуществах мы уже успели рассказать ( Мне не надо про Свету каждый раз рассказывать, достаточно познакомиться один раз )

python-ucsvlog

Формат
Общий формат логов выглядит так:

Каждая запись должна начинаться с новой строки ( \n ) …

… а каждая ячейчка должна начинаться с кавычки ( “ ).

Ячейки должны быть разделены запятыми ( , ) …

… а кавычка внутри ячейки экранируется двумя кавычками ( “” )

Вот такая простая для записи базовая структура. А отказа устойчивость такой структуры заключается в том, что маркера конца нет, т.е. конец ячейки или записи – это начало новой ячейки или записи. Т.е. в случае если случится сбой в момент записи, то мы потеряем только одну ячейку или одну запись, но не весь файл логов. Ну и конечно-же нет ограничения на количество ячеек в одной записи и их количество не обязательно должно быть равным, это дает существенную гибкость и кучу плюшек.
Парсить такие логи очень просто, они парсятся потоково, можно начинать их парсинг с любой точки файла и без использования регекспов. На CheckIO, кстати, лежит задачка на эту тему, попробуйте решить, может у Вас получится лучше чем у меня
Формат данных
Теперь я расскажу – какие поля и в каком порядке кладутся запись лога
Index – это индек, который дает нам древовидность и недублируемость данных. Он представляет собой запись времени и рандомный дополнительный параметр. Под индекс выделены первые 2 ячейки. Первая ячейка это твой индекс, вторая – индекс твоего родителя.

call_info – Опциональна. Информация о месте вызова функции логирования, например имя файла, срока, имя функции, класс, модуль. Список этих данных кастомизируется в момент создания объекта логера.

log_info – сюда мы кладем классический элемент важности логов dev, err, imp, log. Например при создании логера на продакшене мы можем указать, что не хотим записывать dev-логи.

log_data – передается уже в момент вызова. Причем вызывать функцию записи логов можно не только со строковым аргументом но и с массивом, чтоб в одну запись уместить несколько значений. Это очень удобно для того, чтоб в последствии организовать поиск по ним, либо использовать эти данные для анализа.

Как видите логи в этом формате очень гибкие, можно записать сколько угодно много данных, они будут структурированы, А читать их можно не только сначала но и с середины, при этом вы легко найдете начало первой валидной записи.
CODE
Теперь давайте посмотрим, как это выглядит в коде.
-
glog.a_log(‘REQ’,’log_data1’)
-
glog(‘Hi all’)
-
glog.log([‘Payment’,’CardNumber’,'4111 *** **** ****’])
-
glog.a_log(‘IN’,[‘UserID’,98])
-
glog.imp([‘UserBalance’,1500])
-
glog.c_log(‘REQ’)
-
1 a_log – мы открываем индекс. При открытии можно указывать имя, что-то типа метки, которую можно будет использовать как ссылку в коде. Вообще это не обязательно. Можно им пользоваться как структурированным линейным логом, без открытия индекса. Но я считаю древовидность – большим бонусом, которым надо пользоваться.
"I1,","a_log,"REQ,"log_data1
2 log – записываем строковые данные. Т.е. запись одной ячейки. Т.к. у нас есть открытый индекс, то все остальные записи идут как его чаилды. А если мы логер используем как функцию, то он автоматом пишет логи с приоритетом log
"I1,","a_log,"REQ,"log_data1
"I2,"I1,"log,"Hi all
3 log – запись еще 3х ячеек. Теперь мы явно указали, что приоритетность у нас log, и аргументом передали массив ячеек, которые надо записать
"I1,","a_log,"REQ,"log_data1
"I2,"I1,"log,"Hi all
"I3,"I1,"log,"Payment,"CardNumber,"4111 *** **** ****
4 a_log – открытие еще одного индекса. Все верно, уровень вложенности может быть бесконечный. И при открытии мы также можем указать не только строку, но и массив ячеек
"I1,","a_log,"REQ,"log_data1
"I2,"I1,"log,"Hi all
"I3,"I1,"log,"Payment,"CardNumber,"4111 *** **** ****
"I4,"I1,"a_log,"IN,"UserID,"98
&emps 5 imp – запись еще 2х ячеек теперь уже в четвертый индекс, тот, который мы открыли последним. И приоритетность у него img
"I1,","a_log,"REQ,"log_data1
"I2,"I1,"log,"Hi all
"I3,"I1,"log,"Payment,"CardNumber,"4111 *** **** ****
"I4,"I1,"a_log,"IN,"UserID,"98
"I5,"I4,"imp,"UserBalance,"1500
&emps 6 c_log – закрытия индекса. Тут закроются сразу 2 открытых индекса и REQ и IN, потому что мы при закрытии сказали метку открытия индекса. Индекс можно закрывать с записью в лог а можно и без. В закрытие лога как правило кладут результат выполнения этого блока. После закрытия всех индексов ведение записей будет идти без указания родителя, точно так-же как это происходило с первой строкой. А в записи закрытия родителем будет указана тот, кого закрывают, в нашем случае – первый
"I1,","a_log,"REQ,"log_data1
"I2,"I1,"log,"Hi all
"I3,"I1,"log,"Payment,"CardNumber,"4111 *** **** ****
"I4,"I1,"a_log,"IN,"UserID,"98
"I5,"I4,"imp,"UserBalance,"1500
"I6,"I1,"c_log,"REQ
Рендерингом логов занимается сам логер, т.е. при создании логера ему на вход передается не имя файла, а темплейн для его формирования. Например на основе текущей даты. Это очень удобно для того, чтоб блоки не разбрасывались по файлам.
-
glog = Logger(‘/var/log/%(year)s-%(month)s-%(day)s.ucsv’)

На этом все про ucsvlog как формат, дальше я просто пройдусь по плюшкам, которые мы используем постоянно при работе с ними. Единственное хочу отметить,последнюю идею по этим логам. Как вы помните вначале я рассказал про то, что и строки и записи только открываются, но не закрываются, но это накладывает определенную ответственность на ячейку которая идет вконце записи. Например обратите внимание на 5ую строку.

Если случится проблема с записью на последней ячейке, которая не допишет пару последних нулей – то это будет большая проблема, т.к. тогда мы будем полностью уверены в том, что у пользователя баланс в 100 раз меньше, поэтому мы ввели дополнительный параметр close_row в котором можно передать значение последней ячейки в каждой записи. И теперь вы можете принимать запись как валидную только в том случае, если ее последним значением является закрывающий символ. Т.е. в нашем случае мы просто скажем что 5ая запись невалидна.

А теперь… плюшки… плюшки….. плюшки…