Разработваме най-удобния интерфейс в света* за преглед на логове

Разработваме най-удобния интерфейс в света* за преглед на логове Ако някога сте ползвали уеб интерфейси за преглед на логове, вероятно сте забелязали колко трудоемки и (често) не особено удобни и отзивчиви са тези интерфейси. Някои може да свикнете, други са направо ужасни, но, според мен, причината за всички проблеми е, че неправилно подхождаме към задачата за преглед на логовете: опитваме се да създадем уеб интерфейс там, където CLI (интерфейс команден ред) работи по-добре. Лично на мен ми е много удобно да работя с tail, grep, awk и подобни, затова идеалният интерфейс за работа с логове за мен би бил нещо подобно на tail и grep, но което да може да се използва за четене на логове, идващи от множество сървъри. Тоест, разбира се, да ги чета от ClickHouse!

*поредно мнение на хабрапотребител youROCK

Представяме logscli

Не съм измислил име за интерфейса си, и честно казано, той по-скоро съществува в вид на прототип, но ако искате веднага да видите изходния код, заповядайте: https://github.com/YuriyNasretdinov/logscli (350 реда подбрано код на Go).

Възможности

Поставих си за цел да направя интерфейс, който да изглежда познат на тези, които са свикнали на tail/grep, т.е. да поддържа следните неща:

  1. Преглед на всички логове, без филтриране.
  2. Остави редове, в които присъства фиксирана подстрока (флаг -F у grep).
  3. Остави редове, които отговарят на регулярното изразяване (флаг -E у grep).
  4. По подразбиране прегледът е в обратен хронологичен ред, тъй като най-често интересуват най-новите логове.
  5. Показване на контекста около всеки ред (параметри -A, -B и -C у grep, извеждащи N реда преди, след и около всеки съвпадащ ред съответно).
  6. Преглед на постъпващите логове в реално време, с филтриране и без (по същество tail -f | grep).
  7. Интерфейсът трябва да бъде съвместим с less, head, tail и подобни — по подразбиране резултатите трябва да се връщат без ограничения в тяхното количество; редовете се извеждат потоково, докато потребителят проявява интерес към тях; сигнал SIGPIPE трябва безшумно да прекъсва стрийминга на логовете, точно както правят tail, grep и другите UNIX утилити.

Реализация

Предполагам, че вече по някакъв начин знаете как да доставяте логовете до ClickHouse. Ако не, препоръчвам да опитате lsd и kittenhouse, както и тази статия за доставката на логове.

Първо трябва да определим схемата на базата. Тъй като обикновено логовете искат да се получават сортирани по време, логично е да ги съхраняваме по този начин. Ако има много категории на логовете и всички те са еднотипни, можем да направим категорията на логовете първата колона на основния ключ — това ще ни позволи да имаме една таблица вместо няколко, което при добавяне в ClickHouse ще бъде голямо предимство (на сървъри с твърди дискове се препоръчва да се добавят данни не по-често от ~1 път в секунда. за целия сървър).

Тоест, необходима ни е следната схема на таблици:

CREATE TABLE logs(
    category LowCardinality(String), -- категория на логовете (по желание)
    time DateTime, -- време на събитието
    millis UInt16, -- милисекунди (може да бъде и микросекунди и т.н.): препоръчително е да се съхранява, ако има много събития, за да е по-лесно да се разпознават събитията помежду им
    ..., -- вашите собствени полета, например име на сървъра, ниво на логиране и т.н.
    message String -- текст на съобщението
) ENGINE=MergeTree()
ORDER BY (category, time, millis)

За съжаление не можах веднага да намеря открити източници с реалистични логове, които можеха да се изтеглят, така че вместо това взех за пример отзиви за стоки от Amazon до 2015 година.. Безусловно, структурата им не е точно такава, каквато е на текстовите логове, но за илюстрация това не е от съществено значение.

инструкция за зареждане на отзивите от Amazon в ClickHouse

Да създадем таблица:

CREATE TABLE amazon(
   review_date Date,
   time DateTime DEFAULT toDateTime(toUInt32(review_date) * 86400 + rand() % 86400),
   millis UInt16 DEFAULT rand() % 1000,
   marketplace LowCardinality(String),
   customer_id Int64,
   review_id String,
   product_id LowCardinality(String),
   product_parent Int64,
   product_title String,
   product_category LowCardinality(String),
   star_rating UInt8,
   helpful_votes UInt32,
   total_votes UInt32,
   vine FixedString(1),
   verified_purchase FixedString(1),
   review_headline String,
   review_body String
)
ENGINE=MergeTree()
ORDER BY (time, millis)
SETTINGS index_granularity=8192

В набора от данни на Amazon има само дата на отзива, но няма точно време, затова ще запълним тези данни с произволно генерирано.

Не е необходимо да изтегляте всички tsv файлове и можете да се ограничите до първите ~10-20, за да получите достатъчно голям набор от данни, който няма да се побере в 16 Гб оперативна памет. За зареждане на TSV файловете използвах следната команда:

for i in *.tsv; do
    echo $i;
    tail -n +2 $i | pv |
    clickhouse-client --input_format_allow_errors_ratio 0.5 --query='INSERT INTO amazon(marketplace,customer_id,review_id,product_id,product_parent,product_title,product_category,star_rating,helpful_votes,total_votes,vine,verified_purchase,review_headline,review_body,review_date) FORMAT TabSeparated'
done

На стандартен Persistent Disk (който HDD) в Google Cloud с размер от 1000 Гб (такъв размер избрах основно за да е малко по-бързо, въпреки че вероятно SSD с необходимия капацитет би излязъл по-евтино) скоростта на качване достигна около ~75 Мб/сек на 4 ядра.

  • Трябва да уточня, че работя в Google, но използвах личен акаунт и тази статия няма отношение към работата ми в компанията.

Всички илюстрации ще правя именно с този датасет, тъй като това е всичко, което имах на разположение.

Показване на напредъка на сканирането на данни

Тъй като в ClickHouse ще използваме full scan по таблицата с логовете, а тази операция може да отнеме значително време и да не предоставя никакви резултати, ако намерените съвпадения са малко, е желателно да можем да показваме напредъка на изпълнението на запитването преди да получим първите редове с резултат. За това в HTTP интерфейса има параметър, който позволява даването на напредък в HTTP заглавията: send_progress_in_http_headers=1. За съжаление, стандартната библиотека Go не може да чете заглавията по време на получаването им, но интерфейсът HTTP 1.0 (не бъркайте с 1.1!) се поддържа от ClickHouse, затова може да се отвори сурово TCP свързване с ClickHouse и да се изпрати там GET /?query=... HTTP/1.0nn и да се получат в отговор заглавията и тялото на отговора без никакво екраниране и криптиране, така че в този случай не се нуждаем от стандартната библиотека.

Стрийминг на логове от ClickHouse

В ClickHouse вече от сравнително дълго време (от 2019 година?) има оптимизация за запитвания с ORDER BY, така че запитването от вида

SELECT time, millis, message
FROM logs
WHERE message LIKE '%something%'
ORDER BY time DESC, millis DESC

ще започне веднага да връща редове, в които message съдържа подстринга "something", без да чака края на сканирането.

Също така, би било много удобно, ако ClickHouse само отменя запитването, когато с него се затвори свързването, но това не е поведение по подразбиране. Автоматичното отменяне на запитването може да се включи с опцията cancel_http_readonly_queries_on_client_close=1.

Коректна обработка на SIGPIPE в Go

Когато изпълнявате, да речем, командата some_cmd | head -n 10, по какъв точно начин командата some_cmd прекратява своето изпълнение, когато head е прочела 10 реда? Отговорът е прост: когато head завърши, pipe се затваря, и stdout на командата some_cmd започва да сочи, условно, «никъде». Когато some_cmd се опита да запише в затворен pipe, я получава сигнал SIGPIPE, който по подразбиране тихо приключва програмата..

В Go по подразбиране също става така, но обработчикът на сигнала SIGPIPE накрая отпечатва "signal: SIGPIPE" или подобно съобщение, и за да се предотврати това съобщение, просто трябва да обработите SIGPIPE по начина, по който желаете, а именно просто да излезете безшумно:

ch := make(chan os.Signal)
signal.Notify(ch, syscall.SIGPIPE)
go func() {
    <-ch
    os.Exit(0)
}()

Показване на контекста на съобщението

Често е необходимо да се види контекста, в който е възникнала определена грешка (например, кой запитване е предизвикало паника или какви свързани проблеми са били видими преди срив), и в grep за това служат опции -A, -B и -C, които показват указаното количество редове след, преди и около съобщението съответно.

За съжаление, не намерих прост начин да направя същото в ClickHouse, така че, за да се покаже контекстът, за всеки ред от резултата се изпраща допълнителен запитване, приблизително от следния вид (детайлите зависят от сортирането и от това дали контекстът се показва преди или след):

SELECT time,millis,review_body FROM amazon
WHERE (time = 'ВРЕМЕ_НА СЪБИТИЕТО' AND millis < МИЛИСЕКУНДИ_НА СЪБИТИЕТО) OR (time < 'ВРЕМЕ_НА СЪБИТИЕТО')
ORDER BY time DESC, millis DESC
LIMIT КОЛИЧЕСТВО_НА РЕДОВЕ_КОНТЕКСТ
SETTINGS max_threads=1

Тъй като запитването се изпраща почти веднага след като ClickHouse е върнал съответния ред, той влиза в кеша и изобщо запитването се изпълнява доста бързо и харчи малко CPU (обикновено запитването отнема около ~6 мс на моята виртуалка).

Показване на нови съобщения в режим на реално време

За да показвате идващите съобщения в (почти) реално време, просто изпълнявайте запитването на всеки няколко секунди, запомняйки последния timestamp, който сме срещали преди това.

Примери за команди

Как изглеждат типичните команди logscli на практика?

Ако сте качили датасета Amazon, който споменах в началото на статията, можете да изпълните следните команди:

# Показать строки, где встречается слово walmart
$ logscli -F 'walmart' | less

# Показать самые свежие 10 строк, где встречается "terrible"
$ logscli -F terrible -limit 10

# То же самое без -limit:
$ logscli -F terrible | head -n 10

# Показать все строки, подходящие под /times [0-9]/, написанные для vine и у которых высокий рейтинг
$ logscli -E 'times [0-9]' -where="vine='Y' AND star_rating>4" | less

# Показать все строки со словом "panic" и 3 строки контекста вокруг
$ logscli -F 'panic' -C 3 | less

# Непрерывно показывать новые строки со словом "5-star"
$ logscli -F '5-star' -tailf

Връзки

Кодът на утилитата (без документация) е достъпен в github на адрес https://github.com/YuriyNasretdinov/logscli. Ще се радвам да чуя вашите мисли относно моята идея за конзолен интерфейс за преглед на логове, основан на ClickHouse.

Източник: habr.com

Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри 🔥 Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри | ProHoster