Чим замінити ELK для перегляду логів?

Що зазвичай робить python-програміст, коли його відправляють воювати з помилкою?
Спочатку він лізе в sentry. Тут можна знайти час, сервер, подробиці повідомлення про помилку, traceback і, можливо, який-небудь корисний контекст. Потім, якщо цих даних недостатньо, програміст йде c пляшкою до адмінам. Ті залазять на сервер, шукають це повідомлення у файлових логах, і, може бути, знаходять його і деякі попередні помилку записи, які в рідкісних випадках можуть допомогти в розслідуванні.
А що робити, якщо в логах тільки
loglevel=ERROR
, а помилка настільки крута, що її локалізація вимагає зіставлення логіки поведінки кількох різних демонів, які запущені на десятці серверів?
Рішення — централізоване сховище логів. У самому простому випадку — syslog (за 5 років, що був розгорнутий в rutube, не використовувався жодного разу), для більш складних цілей — ELK. Скажу чесно, "ластик" — крутий, і дозволяє швидко крутити різноманітну аналітику, але ви інтерфейс Kibana бачили? Цій штуці так само далеко до консольних less/grep, як вінді до лінукса. Тому ми вирішили зробити свій велосипед, без Java і Node.js, зате з sphinxsearch і Python.
загалом, основні претензії до ELK полягають у тому, що Kibana — це зовсім не інструмент навігації по логам. Не те, щоб зовсім користуватися неможливо, але як заміна grep/less — не годиться.
Офіційний скріншот Kibana
Так що чи не основною вимогою до "велосипеду" була минималистичная верстка, позбутися вічно зависающего побудованого на "ворожих" технологіях Logstash, ну і ElasticSearch теж заодно викинути.
Частина перша: відправка
Щоб зберігати логи, їх треба кудись відправляти. В той же ElasticSearch можна писати прямо, але набагато веселіше через RabbitMQ — на те він і брокер повідомлень. Взявши за основу python-logstash, ми "докрутили" до нього коректну обробку недоступності RabbitMQ і декілька "завантажувачів". У пакеті logcollect підтримується автонастройка для Celery, Django і "рідного" python logging. При цьому до кореневого логгеру додається обробник, відправляє всі логи в форматі JSON в RabbitMQ.
Так, трохи не забув. Приклад:
from logcollect.boot import default_config

default_config(
# RabbitMQ URI
'amqp://guest:guest@127.0.0.1/',
# ідентифікатори для підсистем або ще чого-небудь схожого
# по-суті додається до кожного повідомлення окремим ключем
activity_identity={'subsystem': 'backend'},
# префікс для RabbitMQ routing key.
routing_key='site')

Частина друга, маленька: маршрутизація
Для попередньої фільтрації логів ми скористалися функціоналом RabbitMQ: використовується topic-exchange, який відправляє в чергу на обробку тільки повідомлення, що відповідають визначеним шаблоном. Наприклад, sql-запити Django проекту "site" будуть оброблятися, тільки якщо для відповідної черги задано routing key
site.django.db.backends
, а "зловити" всі логи від django можна з допомогою routing key
site.django.#
. Це дозволяє балансувати між обсягом збережених даних і повнотою охоплення логируемых повідомлень.
Частина третя, асинхронна: збереження
Спочатку, літера "A" ALCO означала "асинхронний", однак швидко з'ясувалося, що використання asyncio-based рішень тут ні до чого: все впиралося в швидкість фільтрації повідомлень питоновским процесом. Воно й зрозуміло: librabbitmq дозволяє отримувати з "кролика" відразу пачку повідомлень, кожне з яких треба розібрати, вирізати непотрібні поля, перейменувати неприпустимі, для деяких полів зберегти нові значення в Redis, згенерувати на основі мітки часу id для sphinxsearch, а ще сформувати INSERT-запит.
Надалі, правда, виявилося, що "асинхронний" — це все-таки про нас: для того, щоб ефективніше витрачати процесорний час, round-trip INSERT-запиту між пітоном і sphinxsearch виконується в окремому потоці, з синхронізацією через рідні черзі пітона.
Нові колонки зберігаються в БД, після чого стає доступним ряд налаштувань їх відображення та зберігання:
  • відображення а-ля list-filter, наприклад для фільтрування levelname,
  • індексування як окремого поля, наприклад, для celery task_id,
  • режим контексту повідомлення — дуже допомагає для відображення traceback,
  • виключення з індексації та списку колонок.
Частина четверта, фронтендовая
З технічної точки зору розповідати особливо нічого (bootstrap + backbone.js + django + rest_framework), тому наведу лише пару скріншотів.
Перегляд логів в ALCO
Логи можна фільтрувати за датами, значень певних колонок списку, за довільним значенням, а також виконувати повнотекстовий пошук по самим повідомленнями. Крім того, опціонально можна подивитися записи, що сусідять із знайденими повнотекстовим пошуком (привіт, less).
Легко впізнавана адмінка Django-1.9
Відображення колонок налаштовується через адмінку, як і налаштування індексів routing_key або період зберігання даних.
Нотатки про продуктивності
На жаль, похвалитися блискавичною завантаженням сторінки ми поки не можемо: sphinxsearch так влаштований, що будь-яка фільтрація по атрибутах вимагає fullscan всього індексу, тому швидко працює лише повнотекстовий пошук (вважай, grep). Зате він — мегабыстр! Але ми не здаємося, і вперто намагаємося продуктивність фільтрації.
наприклад, первинні ключі спеціально генеруються на основі мітки часу, т. к. sphinxsearch вміє "швидко" вигрібати дані по діапазону id. Починаючи з деякого обсягу індексу, виграш по продуктивності дає індексація окремих колонок: незважаючи на низьку потужність (cardinality), за рахунок того, що використовується повнотекстовий індекс, обробка запиту займає умовно 20 секунд проти хвилини у випадку фільтрації за json-атрибутів. Також у запиті зазначається розподілений індекс, що відповідає діапазону запитуваних дат: таким чином, дані за весь місяць не читаються, якщо потрібні логи за "вчора".
Швидкість вставки у RT-індекс з часів цієї статті в блозі sphinxsearch значно зросла: на персоналке вдалося досягти (як би не збрехати...) 8000 row/s на инсертах по 1000 записів, при одночасному отриманні записів з черги RabbitMQ і обробці в python-процесах. А ще alco вміє вставляти записи в кожен індекс в кілька потоків, хоча до повноцінного шардирования sphinxsearch по машинам ми не доросли: цікавих логів в продакшне не настільки багато, щоб це стало критичним.
Маленький фокус з конфіг
Уважний читач помітить (з), що конфігурація sphinxsearch у нас явно не статична ;-) взагалі-то це не секрет, і в документації написано, що sphinx.conf цілком може бути виконуваним файлом, якщо на його початку варто "shebang". Так що наш скрипт конфига написана на пітоні, він ходить до адмінки http alco і друкує в stdout конфіг сфінкса, згенерований за допомогою шаблонизатора django, попутно створюючи відсутні директорії і видаляючи вже не використовуються індекси.
Якщо когось зацікавив наш "велосипед", детальніше про alco можна почитати на github. Щоб спробувати, досить sphinxsearch, RabbitMQ, MySQL і Redis. Ну і, звичайно, будемо раді баг-репортам і пулл-реквестам.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.