themylog — это сердце вашей home automation system. Он может выступать как:
- Централизованный logging facility. Основное назначение themylog — приём и обработка записей по аналогии с, например, syslog. Непосредственно системный демон сообщений он не заменяет, нацеливаясь на обработку тех логов, которые кто-то действительно будет читать, причём не только тогда, когда начнёт происходить что-то странное.
- Message bus. Подключив в качестве обработчика клиент для какого-нибудь брокера сообщений (например, AMQP + RabbitMQ) и имея на входе информацию о всех важных и не очень событиях, происходящих в системе, мы получаем шину сообщений, с помощью которой различные приложения и устройства смогут обмениваться информацией в реальном времени, имея минимум зависимостей друг от друга.
- Watchdog. Некоторые события должны происходить регулярно, другие — не происходить вовсе, и если эти условия нарушаются, значит, где-то возник беспорядок. Указав themylog признаки беспорядка, можно получить оперативную помощь в его обнаружении и уверенность, что все системы работают верно.
- Collector. Многие приложения, например, «погода» или «баланс» работают по схожему принципу: периодически запускаясь, собирают некоторую обновляющуюся информацию, которую затем распространяют локально. themylog предоставляет шаблоны timeline и timeseries для написания подобных приложений, управляет их запуском по расписанию, уведомляет в случае, если в заданный период времени приложению ни разу не удалось обновить информацию, а так же предоставляет WebSocket-сервер для мгновенного уведомления об обновлениях.
- Analytic. Обладая большим количеством данных, вполне очевидна возможность получать интересные результаты путём их анализа. themylog позволяет создавать аналитики — функции, принимающие определённые наборы данных и возвращающие некий результат. Значения этих функций пересчитываются при поступлении новых данных и доступны в реальном времени через WebSocket-сервер.
Концепция записи в themylog схожа с таковой в любой logging facility (например, logging в Python): это просто структура, состоящая из следующих полей:
- application — приложение, создавшее запись. Например,
smarthome
(«умный дом») илиweather
(«погода»). - logger — подсистема приложения, создавшая запись. При создании записей из приложений на
Python
при помощи модуляlogging
, этому полю соответствуетLogRecord.name
. Примеры:root
(основной код приложения),werkzeug
(модуль веб-сервера),paramiko.transport
(транспортный модуль SSH-клиента),door_bell
(дверной звонок в умном доме). - datetime — дата, когда произошло событие, описываемое записью.
- level — уровень важности события. Внутреннее представление — неотрицательное целое число (больше значение — выше уровень важности). Существуют следующие уровни важности:
- debug (10) — отладочное сообщение, вне приложения не имеющее смысла. В основном, они нужны для трассировки приложений при отлове каких-то нетипичных ошибок, поэтому сбор таких записей имеет смысл, а вот длительное хранение — нет. Старые отладочные записи можно удалять при помощи модуля уборки. Пример: «демон обновления погоды открывает такую-то страницу».
- info (20) — информационное сообщение о каком-то обыденном, малоинтересном событии. Например, «загружена информация о погоде».
- report (30) — отчёт о проделанной работе. Например, «выполнено ежедневное резервное копирование объёмом 15 гигабайт».
- warning (40) — предупрежение и
- error (50) — ошибка. Подобные сообщения игнорировать не стоит, а чтобы их не игнорировали — не стоит ими разбрасываться попусту.
- msg — идентификатор вашего события внутри logger. Для записей из модуля
logging
это полеLogRecord.msg
, приведённое к виду[a-z0-9_]+
. Примеры:device_list_was_changed
,creating_device_s
. - args — словарь, описывающий детали события в структурированном виде. Например:
{"mac-address": "00:22:d4:06:38:11"}
. - explanation — понятное человеку описание события. Например,
Creating device 00:22:d4:06:38:11
. Для записей из модуляlogging
это полеLogRecord.msg
, отформатированное аргументами.
Для фильтрации записей и объединения их в группы используются правила на манер iptables. Запись проходит по цепочке правил, каждое из которых сравнивает определённые её поля с некоторым шаблоном и либо окончательно принимает/отклоняет запись, либо отправляет её дальше. Если никакое из правил не приняло запись, она отклоняется. Например, следующий набор правил отбросит записи, у которых не заполнено человекочитаемое описание, записи подсистем paramiko.transport
и werkzeug
, записи с уровнем важности меньшим, чем report (кроме как от приложения smarthome
), но примет все остальные:
-
explanation: ""
action: reject
-
logger: [paramiko.transport, werkzeug]
action: reject
-
application: smarthome
action: accept
-
level: < report
action: reject
-
action: accept
Для актуального (и большего) количества примеров можно почитать unit-тесты соответствующего модуля.
Ленты предназначены для группировки записей. Они описываются в секции feeds
конфигурационного файла. Каждая лента идентифицируется уникальным именем и содержит правила, принимающие записи для попадания в ленту. Пример конфигурации:
feeds:
sms:
-
application: sms
action: accept
-
action: reject
problems:
-
msg: nonzero_exit_code
action: reject
-
msg: timeout
action: reject
-
msg: disorder_found
action: reject
-
level: ">= warning"
action: accept
-
action: reject
Для лент создаются exchange
в AMQP, а их содержимое доступно по адресам http://<themylog-web-server>/feed/<feed name>?limit=<limit>
(по умолчанию 50) и ws://<themylog-web-server>/feed/<feed name>
веб-сервера.
Приёмники записей принимают записи. Приёмники описываются в секции receivers
конфигурационного файла. Для каждого приёмника указывается формат принимаемых записей: text или json (по умолчанию json). Пример записи в формате text:
logger=test
level=info
msg=message
key=value
list[0]=5
list[2]=625
Message with value!
У этой записи будет application с IP-адресом отправителя (при использовании TCPServer/UDPServer), соответствующими значениями level
и msg
, args
, равным {"key": "value", "list": [5, null, 625]}
и описанием Message with value!
Для актуального (и большего) количества примеров можно почитать unit-тесты парсера текстовых записей.
-
Как очевидно из названия, эти приёмники принимают TCP-соединения / UDP-дейтаграммы с сообщениями:
receivers: - TCPServer: host: 192.168.0.1 port: 46404 format: text - UDPServer: host: 192.168.0.1 port: 46404 format: text
Очень просто послать сюда сообщение, например, из bash-скрипта:
echo -e "application=kindle\nlogger=watchdog.browser\nlevel=warning\nmsg=restarting_browser\n\nПерезапуск Chromium для Kindle" | nc 192.168.0.1 46404
-
Более производительный, нежели сетевые сокеты, приёмник. Для работы
themylog.client.LoggingHandler
необходим приёмник UnixServer с форматом json:receivers: - UnixServer: path: /run/themylog/themylog.sock
Обработчики записей обрабатывают принятые записи. Обработчики описываются в секции handlers
конфигурационного файла.
-
Этот обработчик создаёт для каждой ленты
exchange
типаtopic
с именем<exchange>.<feed>
в RabbitMQ (а так же простоexchange
, куда попадают все записи) и рассылает в них записи сrouting_key
, состоящим изapplication
.logger
.msg
, обрезанным под длину в 128 символов:handlers: - AMQP: exchange: themylog
Теперь на эти записи можно подписываться:
import pika import themyutils.json mq_connection = pika.BlockingConnection(pika.ConnectionParameters(host="localhost")) mq_channel = mq_connection.channel() mq_channel.exchange_declare(exchange="themylog", type="topic") result = mq_channel.queue_declare(exclusive=True) queue_name = result.method.queue mq_channel.queue_bind(exchange="themylog", queue=queue_name, routing_key="smarthome.sleep_tracker.sleep_tracked") mq_channel.basic_consume(lambda ch, method, properties, body:\ HANDLE(themyutils.json.loads(body)["args"]), queue=queue_name, no_ack=True) mq_channel.start_consuming()
-
Этот обработчик сохраняет все принимаемые записи в таблицу базы данных. Таблица создаётся автоматически. За возможными значениями
dsn
обратитесь к соответствующему разделу документации SQLAlchemy.handlers: - SQL: dsn: mysql://root@localhost/themylog?charset=utf8
themylog позволяет детектировать различные неполадки в системе. JSON неполадок (как выявленных, так и не выявленных) доступен по адресу http://<themylog-web-server>/disorders
и через WebSocket. Например:
[
{
"title": "Резервное копирование",
"is_disorder": false,
"disorder": {
datetime: "datetime(2014-08-19T12:08:29.227862)",
reason: "Последняя запись 19.08 в 12:08",
data: ...
},
},
{
"title": "Камеры",
"is_disorder": true,
"disorder": {
datetime: "datetime(2014-08-20T02:00:04.189521)",
reason: [
{
"is_disorder": false,
"disorder": "Камера в подъезде: Работает"
},
{
"is_disorder": true,
"disorder": "Камера в отсечке: Не работает"
}
],
data: ...
},
}
]
Обнаруживаются беспорядки при помощи искателей (seeker), описываемых в секции disorders
конфигурационного файла. В themylog встроены несколько шаблонов искателей:
-
Этот искатель обнаруживает беспорядок тогда, когда последняя запись, попадающая под критерий, не удовлетворяет некоторому условию. Например:
disorders: seekers: - class: check_record title: "Достаточно места для резервного копирования" condition: - application: backup logger: root msg: finish action: accept function: | from themyutils.file_size import human_bytes return record.args["free"] > 25e9, "Свободно %s" % human_bytes(record.args["free"])
Будет создан беспорядок, если после последней выполненной резервной копии на диске осталось свободно менее 25 гигабайт.
-
Этот искатель обнаруживает беспорядок тогда, когда в течение указанного периода (задаваемого в формате ISO8601 Duration) не обнаруживает указанную запись. Например:
disorders: seekers: - class: expect_record title: "Резервное копирование" condition: - application: backup logger: root msg: finish action: accept interval: P1DT6H
Будет создан беспорядок, если за последние 30 часов не было успешно выполнено ни одной резервной копии.
-
Этот искатель объединяет беспорядки в группы (возможно, вложенные). Например:
disorders: seekers: - class: group title: "Резервное копирование" seekers: - class: expect_record title: "Резервное копирование выполняется" ... - class: check_record title: "Достаточно места для резервного копирования" ...
Будет создан беспорядок, если за последние 30 часов не было успешно выполнено ни одной резервной копии.
-
themylog позволяет также создавать вам собственные искатели беспорядков, которые будут запускаться по расписанию. Будут запущены все файлы с расширением
.py
из указанной директории:disorders: directory: /home/themylogin/www/apps/themylog_disorder_seekers
Поддерживаются следующие аннотации:
crontab
(обязательно) — расписание запуска искателя. Эквивалент crontab schedule из celerytitle
(обязательно) — человекочитаемое название группы беспорядков, которую пытается обнаружить искатель
Искатель импортирует создаёт экземпляры класса
Disorder(title)
из пакетаthemylog.disorder.script
и вызывает у них методыok
/fail
/exception
в случае, если беспорядок не обнаружен/обнаружен/не удалось провести процедуру обнаружения. Примеры искателей беспорядков вы можете найти в репозитарии themylog_disorder_seekers.
themylog
позволяет собирать данные по расписанию при помощи написанных пользователем скриптов *.py
из указанной директории:
collectors:
directory: /home/themylogin/www/apps/themylog_collectors
Поддерживаются следующие аннотации:
crontab
(обязательно) — расписание запуска собирателя. Эквивалент crontab schedule из celerytitle
(обязательно) — человекочитаемое название собирателяallowed_downtime
(по умолчанию 1 час) — период, в течение которого собирателю разрешено завершаться с ошибкой. По истечении этого периода будет создан беспорядокtimeout
(по умолчанию 1 минута) — максимальное время выполнения собирателя
Собиратели работают по одному из следующих шаблонов:
-
Timeline — это множество упорядоченных записей, имеющих идентификатор. Пример: твиты, поездки на транспорте, транзакции по банковской карте. Скрипт создаёт экземпляр объекта
themylog.collector.timeline.Timeline
, находит записи, проверяет их существование при помощи методаcontains(string_id)
и в случае, если запись не обнаружена, сохраняет её при помощи методаstore(string_id, args, **kwargs)
.application
записи будет равен имени скрипта без расширения,logger
—root
(или аргумент конструктораTimeline
),msg
— идентификатору, вkwargs
можно переопределить, например,datetime
. По адресуhttp://<themylog-web-server>/timeline/<application>?limit=<limit>
(по умолчанию 1) и через WebSocket можно наблюдатьargs
последнихlimit
записей из указанного timeline:[ { text: "Иван Иванович! Сообщаем Вам, что 02.08.2014 в 21:43:17 по Вашей банковской карте ВТБ24 **** произведена транзакция по оплате на сумму 39.00 RUR. Доступно к использованию 387.84 RUR. Детали платежа: место - PEREKRESTOK KIEVSKAYA P, код авторизации - 213479.", balance: 387.84, write_off_currency: "RUR", write_off: 39, details: "PEREKRESTOK KIEVSKAYA P" } ]
Примеры скриптов, собирающих такие данные, вы можете найти в репозитарии themylog_collectors, в частности, vtb24.py.
Для работы
Timeline
необходим хотя бы один обработчик, реализующий интерфейсIRetrieveCapable
(способный хранить старые данные и обращаться к ним), например, SQL. -
Timeseries — это множество записей, собираемых через равные промежутки времени. Пример: погода, баланс мобильного телефона, геолокация смартфона. Скрипт создаёт экземпляр объекта
themylog.collector.time_series.TimeSeries
и сохраняет информацию вызовом методаmsg(args)
.application
записи будет равен имени скрипта без расширения,msg
— имени вызванного метода. По адресуhttp://<themylog-web-server>/timeseries/<application>/<logger>/<msg>
(logger
иmsg
необязательны) и через WebSocket можно наблюдатьargs
последней записи:# http://192.168.0.1:46405/timeseries/weather { romance: "Восход: 06:11, закат: 20:52", temperature_trend: "-", temperature: "+23,4°C", description: "ясная погода, без осадков" } # http://192.168.0.1:46405/timeseries/t_card/balance 802
Примеры скриптов, собирающих такие данные, вы можете найти в репозитарии themylog_collectors, в частности, weather.py и t_card.py.
Необязательный GET-параметр
timeout
(по умолчанию отсутствует) позволяет выводитьnull
вместо устаревших записей. Например, по адресуhttp://<themylog-web-server>/timeseries/theMediaShell?timeout=5
можно следить за статусом theMediaShell, но если завершить плеер некорректно, через пять секунд по этому адресу будетnull
, несмотря на то, что сообщений о завершении от него не поступало.
themylog
позволяет обрабатывать входящие записи, порождая новые. Например, демон забирает SMS-сообщения с USB-модема и кладёт их в themylog
, а процессор alfa-bank
вытаскивает из них данные о транзакциях по банковской карте, которые тоже сохраняются в themylog
. Каждый процессор — это *.py
-файл из указанной директории, содержащий функцию process(record)
:
processors:
directory: /home/themylogin/www/apps/themylog_processors
Примеры процессоров вы можете найти в репозитарии themylog_processors.
Для того, чтобы запустить вновь написанный процессор для уже существующих записей, выполните следующую команду:
python -m themylog.utils run_processor alfa-bank
Если у вас много записей и вы заранее знаете, что большинство из них процессор проигнорирует, для ускорения работы можно ограничить обрабатываемую выборку:
python -m themylog.utils run_processor backup application backup logger root msg finish
Процессор будет запущен только для записей с application
= backup
, logger
= root
, msg
= finish
.
themylog
позволяет создавать функции, принимающие определённые наборы данных и возвращающие результаты их анализа, которые будут доступны по адресу http://<themylog-web-server>/analytics/<name>
. Например, аналитика awake принимает данные о времени засыпания/времени подъёма от умного дома и данные о нажатиях клавиатуры и движении мыши от UsageStats на рабочем компьютере и выводит информацию о том, сколько я бодрствую, и какое время из этого я провёл за работой. Каждая аналитика — это *.py
-файл из указанной директории, содержащий переменную feeds
и функцию analyze(...)
:
analytics:
directory: /home/themylogin/www/apps/themylog_analytics
Переменная feeds
— это словарь, описывающий ленты, которые будут поданы на вход функции analyze
. Каждая лента описывается деревом правил, состоящим из кортежей в префиксной нотации с использованием модуля operator; дополнительно можно указать максимальное количество записей, которые будут выбраны из ленты (по убыванию поля datetime
):
from themylog.rules_tree import RecordField as F
feeds = {"last_sleep_track": {"rules_tree": (operator.and_, (operator.eq, F("logger"), "sleep_tracker"),
(operator.or_, (operator.eq, F("msg"), "woke_up"),
(operator.eq, F("msg"), "fall_asleep"))),
"limit": 1}}
В этом примере функции analyze
будет подан аргумент last_sleep_track
, содержащий запись от логгера sleep_tracker
с msg
либо woke_up
, либо fall_asleep
.
Ленты могут зависеть друг от друга при помощи вычисляемых параметров. Каждый параметр — это функция, принимающая аргументы с именами, равными именам лент, от которых параметр зависит, и возвращающая значение параметра:
from themylog.rules_tree import Param as P, RecordField as F
feeds = {"last_sleep_track": {"rules_tree": (operator.and_, (operator.eq, F("logger"), "sleep_tracker"),
(operator.or_, (operator.eq, F("msg"), "woke_up"),
(operator.eq, F("msg"), "fall_asleep"))),
"limit": 1},
"odometer_logs": {"rules_tree": (operator.and_, (operator.eq, F("application"), "usage_stats"),
(operator.gt, F("datetime"), P("last_sleep_track_datetime"))),
"params": {"last_sleep_track_datetime": lambda last_sleep_track: last_sleep_track.args["at"]
if last_sleep_track.msg == "woke_up"
else datetime.max}}}
В этом примере будет сначала выбрана запись «заснул или проснулся», вычислено время последнего пробуждения и лишь затем выбраны все записи от приложения usage_stats
за период с момента последнего пробуждения.
Сама функция analyze
принимает списки записей или одну запись/None, если limit
указан и равен 1, а так же следующие специальные аргументы:
- now — текущий
datetime.now()
. Функция, принимающая такой аргумент, будет вызываться каждую секунду.
Примеры аналитик вы можете найти в репозитарии themylog_analytics.
Web/WebSocket-сервер позволяет пользовательским приложениям читать из themylog
. Через него доступны ленты, таймлайны, временные ряды, беспорядки и аналитики. Полный (и актуальный) список URL доступен в исходниках соответствующего модуля.
Каждый URL доступен как по протоколу HTTP, так и по протоколу WebSocket. Во втором случае сначала высылается тот же ответ, что и по HTTP, а затем в реальном времени его обновления (новые записи лент, новые значения временного ряда, новый список беспорядков). Пример использования WebSocket-сервера:
$(function(){
collector("timeseries/find_my_iphone", function(data){
$("#charge").text(Math.round(data["content"][0]["batteryLevel"] * 100, 0) + " %");
});
collector("timeline/alfa-bank", function(data){
$("#alfa-bank").text(Math.floor(data["balance"]).toLocaleString("en-US").replace(",", " ") + " р.");
});
});
function collector(url, callback)
{
var ws,
connect,
on_error;
connect = function(){
ws = new WebSocket("ws://192.168.0.1:46405/" + url);
ws.onclose = on_error;
ws.onmessage = function(event){
callback($.parseJSON(event.data));
}
};
on_error = function(){
setTimeout(connect, 1000);
};
connect();
}
Настраивается Web-сервер в соответствующем разделе конфигурационного файла:
web_server:
host: 192.168.0.1
port: 46405
Модуль уборки периодически удаляет из хранилищ ненужные данные:
cleanup:
-
period: PT1H
records:
-
logger: [paramiko.transport]
level: <= info
action: accept
Отладочные и информационные сообщения от модуля paramiko.transport
(из всех приложений) будут храниться не дольше часа.