Проверка log-файлов в Zenoss

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

Итак, имеется сервер MySQL, в котором включен лог долгих запросов. Одним из способов включения этого лога является добавление 2 строк в файл /etc/my.cnf:

set-variable=long_query_time=30
log-slow-queries=/var/log/mysql/log-slow-queries.log

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

 Вот отрывок файла, который дает представление о его структуре:

[collapse collapsed]
/usr/libexec/mysqld, Version: 5.0.77-log (Source distribution). started with:
Tcp port: 0  Unix socket: /var/lib/mysql/mysql.sock
Time                 Id Command    Argument
/usr/libexec/mysqld, Version: 5.0.77-log (Source distribution). started with:
Tcp port: 0  Unix socket: /var/lib/mysql/mysql.sock
Time                 Id Command    Argument
# Time: 101125 13:07:05
# User@Host: sugar[sugar] @  [192.168.42.97]
# Query_time: 158  Lock_time: 0  Rows_sent: 1  Rows_examined: 200
SELECT SQL_CALC_FOUND_ROWS * FROM `email_addr_bean_rel` , `emails` , `email_addresses` LIMIT 1;
/usr/libexec/mysqld, Version: 5.0.77-log (Source distribution). started with:
Tcp port: 0  Unix socket: /var/lib/mysql/mysql.sock
Time                 Id Command    Argument
[/collapse]

Теперь поговорим о том, каким образом мы будем решать поставленную задачу:

  1. для передачи лога (а точнее его фрагмента) в Zenoss будем использовать datasource, получающий информацию через SSH
  2. обработку поступившей информации проведем с помощью собственного парсера, благо в Zenoss имеется стандартный механизм назначения парсера для обработки вывода SSH команд в datasource c последующим формированием не только datapoints, но и евентов.
  3. созданным евентам с помощью маппинга будем определять их класс и назначать их на нужные устройства (логичнее, чтобы евент о долгом запросе пришел не с сервера MySQL, а с того хоста, который этот запрос отправил)
  4. и, наконец, чтобы каждый раз не забирать и не обрабатывать весь лог, создадим Configuration Property, в котором будем хранить информацию, однозначно определяющую последний полученный евент о долгом запросе, с тем, чтобы включить эту информацию в SSH команду.

 Начнем с последнего пункта , поскольку он отражается практически на всех остальных:

Есть 2 способа создать новый configuration property: модифицируя файлы Zenoss и с помощью зенпака. Второй вариант более правильный (имхо), поскольку в зенпак мы сможем включить также и другие результаты нашей работы с тем, чтобы в дальнейшем добавлять всю функциональность максимально просто и быстро. Ну а тем, кто интересуется первым способом, рекомендую обратиться к developers guide.

Для добавления configuration property из зенпака нужно в файл __init__.py из каталога зенпака (здесь и далее под каталогом зенпака будет иметься в виду каталог "ZenPacks.community.<zenpack_name>/ZenPacks/community/<zenpack_name>/") добавить несколько строк. Конечный его вид будет следующим:

[collapse collapsed]
import Globals
import os.path
 
skinsDir = os.path.join(os.path.dirname(__file__), 'skins')
from Products.CMFCore.DirectoryView import registerDirectory
if os.path.isdir(skinsDir):
    registerDirectory(skinsDir, globals())
 
from Products.ZenModel.ZenPack import ZenPackBase
class ZenPack(ZenPackBase):
    """     ZenPacks.community.MySqlSlowQueries ZenPack loader.     """
 
    packZProperties = [
            ('zMySqlLastSlowQuery', '', 'string'),
            ]
[/collapse]

Как видно из кода, для добавления configuration property достаточно в список packZProperties добавить новый кортеж вида ("имя", "значение по умолчанию", "тип").

Теперь создадим новый Monitoring Template (его потом можно будет добавить в наш зенпак), в котором создадим datasource типа COMMAND и включим опцию "Use SSH". В нем определим команду, которая будет выполняться на хосте и забирать последние изменения в логе:

$$(which awk) '/Time: ${here/zMySqlLastSlowQuery}/,EOF' /var/log/mysql/log-slow-queries.log |grep -v -e 'mysqld, Version:' -e 'Tcp port:' -e 'Id Command'

Здесь ${here/zMySqlLastSlowQuery} - это ссылка на значение configuration property для текущего устройства, в котором хранится время последнего медленного запроса. Запись этого значения будет производиться в маппинге.

Результат выполнения команды передается парсеру, указанному в свойствах datasource. Zenoss ищет парсеры в каталоге $ZENHOME/Products/ZenRRD/parsers/, а также в каталогах parsers/ для каждого из зенпаков. Поскольку мы создаем новый зенпак, то наш парсер лучше поместить в каталог parsers нашего зенпака.

Вот код этого парсера:

[collapse collapsed]
__doc__ = """Parser for log-slow-queries.log on MySQL Server (logging of slow queries must be enabled in my.cnf) """
 
import re
 
import Globals
from Products.ZenRRD.CommandParser import CommandParser
 
class mysql_slow_queries(CommandParser):
 
    def sendEvent(self,results,dic):
        results.events.append(dict(
                    summary='Slow query (%(query_time)s secs) at %(date)s by %(user)s@%(host)s: %(query)s' % dic,
                    component='mysqld',
                    eventClassKey='mysqld',
                    severity=3))
 
    def parse(self,query):
        query=query.split("n")
        dat=query[0]
        regexp=re.search("# User@Host: (.*)[.*] @ (.*) [(.*)]",query[1])
        usr=regexp.group(1)
        hst=regexp.group(2)
        if not len(hst): hst=regexp.group(3)
        qtim=re.search("# Query_time: (.*?) .*",query[2]).group(1)
        quers=' '.join(query[3:])
        return {'date':dat,'user':usr,'host':hst,'query_time':qtim,'query':quers}
 
    def processResults(self, cmd, results):
        firstRun=re.search("$(which awk) '/Time: (.*)/,EOF'(.*)",cmd.command).group(1)==''
        res=cmd.result.output
        if len(res):
            for query in res.split("# Time: ")[1 if firstRun else 2:]: self.sendEvent(results,self.parse(query))
[/collapse]

На последнем этапе необходимо создать маппинг получаемых евентов.

В качестве класса евентов я выбрал /App/MySQL, в котором добавил новый mapping. Для определения нужного евента в маппинге был задан следующий Regex:

Slow query (d+ secs) at (?P<date>d{6} d{2}:d{2}:d{2}) by (?P<user>S+)@(?P<host>S+): (?P<query>.*)

А в трансформе указаны следующие действия:

[collapse collapsed]
from transaction import commit
device.zMySqlLastSlowQuery=evt.date
commit()
if not evt.host=='localhost':
    dvc=dmd.Devices.findDevice(evt.host)
    if dvc:
        evt.device=evt.host
        evt.prodState=dvc.productionState
        evt.Location=dvc.getLocationName()
        evt.DeviceClass=dvc.getDeviceClassName()
[/collapse]

Первые 3 строки необходимы для записи времени последнего долгого запроса в configuration property, а остальная часть переопределяет в евенте устройство с сервера MySQL на то, с которого пришел долгий запрос.

В результате теперь каждые 5 минут Zenoss будет проверять лог на сервере MySQL и, в случае появления в нем записей о новых долгих запросах, будут созданы новые евенты для тех устройств, с которых эти запросы были отправлены.

3800

Комментарии

Спасибо за статью. Есть несколько только одно уточнение: добавление нового command datasource. Этот момент вроде описан в документации, но ввиду довольно широкого спектра использования command datasource можно скинуть ссылку.

Добрый день! Очень вас прошу описать поэтапно создание своего парсера.

Например я создала свой парсер в папке $ZENHOME/Products/ZenRRD/parsers/, а также сделала его компиляцию. Видим его в списке при создании Datasource.

Но он не работает..... Что еще нужно сделать. Я уже пробовала просто сделать копию имеющегося рабочего парсера, просто имя изменила, и это не работает. :( Почему? Еще нужно что-то скомпилить или наставить или рестартовать! Пожалуйста, уже несколько дней потратила на это....

Добрый день! Я использую подобный datasource типа COMMAND (у меня такая команда: "awk '/${here/zLastTime}/,EOF' /root/Oxana/zenoss.log") с использованием SSH.  Но по прошествии некоторого времени возникает такая ошибка и zencommand вообще перестает работать.

2011-06-29 08:47:09,804 ERROR ZEO.zrpc: (2762) CW: error in notifyConnected (('localhost', 8100))
Traceback (most recent call last):
  File "/usr/local/zenoss/zenoss/lib/python/ZEO/zrpc/client.py", line 477, in notify_client
  File "/usr/local/zenoss/zenoss/lib/python/ZEO/ClientStorage.py", line 521, in notifyConnected
  File "/usr/local/zenoss/zenoss/lib/python/ZEO/ClientStorage.py", line 1199, in verify_cache
  File "/usr/local/zenoss/python/lib/python2.4/tempfile.py", line 466, in TemporaryFile
  File "/usr/local/zenoss/python/lib/python2.4/tempfile.py", line 236, in _mkstemp_inner
OSError: [Errno 24] Too many open files: '/tmp/tmp8z50I1.inv'

как только число открытых файлов "/root/Oxana/zenoss.log" достигает 1024, выдается такая ошибка. Как закрывать эти файлы, чтоб их число не достигало такого размера? или может еще какие-то решения есть?

Aviriel аватар

Можно попробовать для начала изменить максимальное число открытых файлов ( echo "32768" > /proc/sys/fs/file-max)..

 

 

Спасибо за ответ.

А когда число открытых файлов достигнет и этого числа, снова возникнет ошибка? или все таки эти файлы когда-то закрываются?

avasyukov аватар

Ровно с вашей проблемой не сталкивался. Но вообще при ситуации вида "Too many open files" есть два варианта развития событий - или вам повезет, или нет. ) Если повезет - приложению (в данном случае Zenoss) нужно больше чем 1024 открытых дескрипторов, но это число какое-то разумное и конечное. В этом случае после увеличения лимита все будет работать хорошо. Такая ситуация встречается на самом деле часто. Если не повезет - в приложении (вряд ли Zenoss вообще, скорее отдельный сторонний Zenpack) действительно есть баг из-за которого файлы открываются, но потом не закрываются. В этом случае увеличение лимита поможет ненадолго, а для решения проблемы придется найти баг и думать что с ним делать.

Спасибо большущее за разъяснения.

С первым вариантом решения я пока маюсь, я изменила конфиг /etc/security/limits.conf:

* hard nofile 65000
zenoss soft nofile 60000
zenoss hard nofile 60000

Добавила строчку session required pam_limits.so в несколько файлов в каталоге /etc/pam.d/

А при входе как суперпользователь zenoss все равно имею лимит 1024.

Может что-то надо перерестартовать?

avasyukov аватар

Ничего не надо делать. Только перелогиниться. Максимум в конфиги pam'а один раз вписать то самое session required pam_limits.so, хотя оно и так должно быть по умолчанию (по крайней мере это так в Fedora/RHEL и их клонах).

 

Немного смущает "при входе как суперпользователь zenoss" - все-таки как root или как zenoss?

 

P.S. Первую строчку (* hard nofile 65000) лучше бы убрать - нет смысла ломать умолчания для всей системы.

Первую строчку убрала. Хоть как рут захожу, хоть как zenoss, все равно 1024.

Может вы знаете ответ еще на такой вопрос.

В шаблоне прописана например такая команда:

head /root/Oxana/zenosstime.txt, и каждые 5 минут (как прописано в cycletime) вижу по ulimit -n, как число открытых файлов увеличивается сразу на 3. Почему на 3? Я думала, хотя бы на 1.