Pracujemy nad najwygodniejszym na świecie* interfejsem do przeglądania logów

Pracujemy nad najwygodniejszym na świecie* interfejsem do przeglądania logów Jeśli kiedykolwiek korzystałeś z interfejsów internetowych do przeglądania logów, prawdopodobnie zauważyłeś, że interfejsy te są z reguły uciążliwe i (często) niezbyt wygodne i responsywne. Do niektórych można się przyzwyczaić, niektóre są absolutnie okropne, ale wydaje mi się, że przyczyną wszystkich problemów jest to, że niepoprawnie podchodzimy do zadania przeglądania logów: staramy się stworzyć interfejs sieciowy, w którym CLI (interfejs wiersza poleceń) działa lepiej. Osobiście bardzo dobrze czuję się w pracy z tailem, grepem, awk i innymi, dlatego dla mnie idealnym interfejsem do pracy z logami byłoby coś podobnego do taila i grep, ale którego można by również używać do czytania logów pochodzących z wielu serwerów. Oznacza to oczywiście, że przeczytasz je w ClickHouse!

*według osobistej opinii użytkownika Habra rządzisz

Poznaj logscli

Nazwy dla mojego interfejsu nie wymyśliłem i, szczerze mówiąc, istnieje ona raczej w formie prototypu, ale jeśli chcesz od razu zobaczyć kod źródłowy, to nie ma za co: https://github.com/YuriyNasretdinov/logscli (350 linii wybranego kodu Go).

Możliwości

Moim celem było stworzenie interfejsu, który wydawałby się znajomy dla tych, którzy są przyzwyczajeni do tail/grep, to znaczy do obsługi następujących rzeczy:

  1. Przeglądaj wszystkie logi, bez filtrowania.
  2. Pozostaw linie zawierające stały podciąg (flaga -F у grep).
  3. Pozostaw linie pasujące do wyrażenia regularnego (flaga -E у grep).
  4. Domyślnie przeglądanie odbywa się w odwrotnej kolejności chronologicznej, ponieważ najnowsze logi są zazwyczaj interesujące w pierwszej kolejności.
  5. Pokaż kontekst obok każdej linii (options -A, -B и -C у grep, wypisując N linii odpowiednio przed, po i wokół każdej pasującej linii).
  6. Przeglądaj przychodzące logi w czasie rzeczywistym, z filtrowaniem lub bez (zasadniczo tail -f | grep).
  7. Interfejs musi być kompatybilny z less, head, tail i inne - domyślnie wyniki powinny być zwracane bez ograniczeń co do ich liczby; linie drukowane są strumieniowo, o ile użytkownik jest zainteresowany ich otrzymaniem; sygnał SIGPIPE powinny po cichu przerywać przesyłanie dzienników, tak jak to robią tail, grep i inne narzędzia UNIX.

realizacja

Zakładam, że już w jakiś sposób wiesz, jak dostarczać logi do ClickHouse. Jeśli nie, polecam spróbować LSD и kociaka także ten artykuł na temat dostarczania logów.

Najpierw musisz zdecydować o schemacie podstawowym. Ponieważ zazwyczaj chcesz otrzymywać logi posortowane według czasu, logiczne wydaje się przechowywanie ich w ten sposób. Jeżeli kategorii logów jest wiele i wszystkie są tego samego typu, to możesz ustawić kategorię logu jako pierwszą kolumnę klucza podstawowego - dzięki temu będziesz mieć jedną tabelę zamiast kilku, co będzie dużym plusem, gdy wstawianie do ClickHouse (na serwerach z dyskami twardymi zaleca się wstawianie danych nie częściej niż ~1 razy na sekundę dla całego serwera).

Oznacza to, że potrzebujemy w przybliżeniu następującego schematu tabeli:

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

Niestety, nie mogłem od razu znaleźć żadnych otwartych źródeł z realistycznymi logami, które mógłbym pobrać i pobrać, więc wziąłem to jako przykład recenzje produktów z Amazon do 2015 roku. Oczywiście ich struktura nie jest dokładnie taka sama jak logów tekstowych, ale dla celów ilustracyjnych nie jest to istotne.

instrukcje dotyczące przesyłania recenzji Amazon do ClickHouse

Stwórzmy tabelę:

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

W zbiorze danych Amazona jest tylko data przeglądu, ale nie ma dokładnej godziny, więc uzupełnijmy te dane randonem.

Nie musisz pobierać wszystkich plików tsv i ograniczać się do pierwszych ~10-20, aby uzyskać dość duży zestaw danych, który nie zmieści się w 16 GB RAM-u. Aby przesłać pliki TSV, użyłem następującego polecenia:

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

Na standardowym dysku trwałym (czyli HDD) w Google Cloud o pojemności 1000 GB (ten rozmiar wziąłem głównie po to, żeby prędkość była trochę większa, chociaż być może dysk SSD o wymaganej wielkości byłby tańszy) przesyłanie prędkość wynosiła około ~ 75 MB/s na 4 rdzeniach.

  • Muszę zastrzec, że pracuję w Google, ale korzystałem z konta osobistego i ten artykuł nie ma nic wspólnego z moją pracą w firmie

Wszystkie ilustracje wykonam z wykorzystaniem tego konkretnego zbioru danych, ponieważ to wszystko, co miałem pod ręką.

Pokaż postęp skanowania danych

Ponieważ w ClickHouse zastosujemy pełne skanowanie tabeli z logami, a operacja ta może zająć znaczną ilość czasu i może nie dać żadnych rezultatów przez długi czas w przypadku znalezienia kilku dopasowań, wskazane jest, aby móc wyświetlić postęp zapytania do momentu otrzymania pierwszych wierszy z wynikiem. W tym celu w interfejsie HTTP znajduje się parametr pozwalający na przesyłanie postępu w nagłówkach HTTP: send_progress_in_http_headers=1. Niestety, standardowa biblioteka Go nie może czytać nagłówków w momencie ich otrzymania, ale interfejs HTTP 1.0 (nie mylić z 1.1!) jest obsługiwany przez ClickHouse, więc możesz otworzyć surowe połączenie TCP z ClickHouse i wysłać je tam GET /?query=... HTTP/1.0nn i odbieraj nagłówki i treść odpowiedzi bez ucieczki lub szyfrowania, więc w tym przypadku nie musimy nawet korzystać ze standardowej biblioteki.

Dzienniki przesyłania strumieniowego z ClickHouse

ClickHouse ma optymalizację dla zapytań z ORDER BY od stosunkowo długiego czasu (od 2019?), więc zapytanie takie jak

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

Natychmiast zacznie zwracać linie zawierające podciąg „coś” w swojej wiadomości, bez czekania na zakończenie skanowania.

Ponadto byłoby bardzo wygodne, gdyby ClickHouse sam anulował żądanie po zamknięciu połączenia z nim, ale nie jest to zachowanie domyślne. Za pomocą opcji można włączyć automatyczne anulowanie żądania cancel_http_readonly_queries_on_client_close=1.

Prawidłowa obsługa SIGPIPE w Go

Kiedy wykonasz, powiedzmy, polecenie some_cmd | head -n 10, dokładnie jak polecenie some_cmd zatrzymuje wykonywanie, gdy head odjęto 10 linii? Odpowiedź jest prosta: kiedy head kończy się, potok się zamyka, a standardowe wyjście polecenia Some_cmd zaczyna warunkowo wskazywać „donikąd”. Gdy some_cmd próbuje pisać do zamkniętego potoku, odbiera sygnał SIGPIPE, który domyślnie cicho kończy działanie programu.

W Go również dzieje się to domyślnie, ale procedura obsługi sygnału SIGPIPE wypisuje również na końcu „sygnał: SIGPIPE” lub podobny komunikat, a aby wyczyścić ten komunikat, musimy po prostu sami obsłużyć SIGPIPE tak, jak chcemy, to znaczy po prostu po cichu Wyjście:

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

Pokaż kontekst wiadomości

Często chcesz zobaczyć kontekst, w którym wystąpił jakiś błąd (na przykład, które żądanie spowodowało panikę lub jakie powiązane problemy były widoczne przed awarią), a w grep Dokonuje się tego za pomocą opcji -A, -B i -C, które pokazują określoną liczbę wierszy odpowiednio po, przed i wokół komunikatu.

Niestety nie znalazłem łatwego sposobu na zrobienie tego samego w ClickHouse, więc aby wyświetlić kontekst, do każdej linii wyniku wysyłane jest dodatkowe żądanie tego typu (szczegóły zależą od sortowania i tego, czy kontekst był pokazywany wcześniej lub później):

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

Ponieważ żądanie jest wysyłane niemal natychmiast po zwróceniu przez ClickHouse odpowiedniej linii, trafia do pamięci podręcznej i ogólnie żądanie jest wykonywane dość szybko i zużywa trochę procesora (zwykle żądanie zajmuje około ~6 ms na mojej maszynie wirtualnej).

Pokazuj nowe wiadomości w czasie rzeczywistym

Aby wyświetlać przychodzące wiadomości w (prawie) czasie rzeczywistym, po prostu wykonujemy żądanie raz na kilka sekund, zapamiętując ostatni znacznik czasu, który napotkaliśmy wcześniej.

Przykłady poleceń

Jak w praktyce wyglądają typowe polecenia logscli?

Jeśli pobrałeś zbiór danych Amazon, o którym wspomniałem na początku artykułu, możesz uruchomić następujące polecenia:

# Показать строки, где встречается слово 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

referencje

Kod narzędzia (bez dokumentacji) jest dostępny na githubie pod adresem https://github.com/YuriyNasretdinov/logscli. Chętnie poznam Twoją opinię na temat mojego pomysłu na konsolowy interfejs do przeglądania logów oparty na ClickHouse.

Źródło: www.habr.com

Dodaj komentarz