Če ste kdaj uporabljali spletne vmesnike za pregledovanje dnevnikov, ste verjetno opazili, kako so ti vmesniki običajno obsežni ter (pogosto) ne preveč prijazni in odzivni. Na nekatere se da navaditi, druge so povsem grozne, a mislim, da je težava v tem, da se pri nalogi pregledovanja dnevnikov lotimo napačno: poskušamo ustvariti spletni vmesnik tam, kjer je bolje deloval CLI (ukazna vrstica). Zame je delo s tail, grepom, awk in drugimi izjemno prijetno, zato bi bil idealen vmesnik za delo z dnevniki nekaj podobnega tail in grep, vendar bi ga lahko uporabljali za branje dnevnikov, ki prihajajo iz več strežnikov. Seveda, branje iz ClickHouse!
*po osebnem mnenju uporabnika Habr
Predstavljamo logscli
Nisem izumil imena za svoj vmesnik, in iskreno povedano, obstaja bolj v obliki prototipa, toda če želite takoj pogledati izvorno kodo, vas vabim: (350 vrstic izbranega kode v Go).
Возможности
Zavzel sem si cilj ustvariti vmesnik, ki bo znan tistim, ki so navajeni na tail/grep, torej podpirati naslednje stvari:
- Pregled vseh dnevnikov, brez filtriranja.
- Ostavite vrstice, ki vsebujejo fiksno podniz (flaga
-Fzagrep). - Ostavite vrstice, ki ustrezajo regularnemu izrazu (flaga
-Ezagrep). - Privzeto je pregled v obrnjeni kronološki vrstici, saj so ponavadi na začetku najbolj zanimivi najnovejši dnevniki.
- Prikaz konteksta okoli vsake vrstice (parametri
-A,-Bin-Czagrep, ki izpisujejo N vrstic pred, po in okoli vsake ujemajoče se vrstice). - Pregled prihajajočih dnevnikov v realnem času, z filtriranjem in brez (v bistvu
tail -f | grep). - Vmesnik mora biti združljiv z
less,head,tailin drugimi — privzeto morajo rezultati vračati brez omejitev glede števila; vrstice se izpisujejo v toku, dokler je uporabnik zainteresiran za njihovo pridobivanje; signalSIGPIPEmora tiho prekinjati pretakanje dnevnikov, tako kot to počnejotail,grepin druge UNIX utilite.
Implementacija
Predvidevam, da že na nek način znate dostaviti dnevnike do ClickHouse. Če ne, vam priporočam, da poskusite in , kao i .
Za začetek je potrebno opredeliti shemo baze. Ker običajno želimo imeti loge razvrščene po času, je logično, da jih take tudi shranimo. Če je kategorij logov veliko in so vse enake, lahko kot prvo kolono primarnega ključa uporabimo kategorijo logov — to bo omogočilo, da imamo eno tabelo namesto več, kar pri vstavljanju v ClickHouse predstavlja veliko prednost (na strežnikih s trdimi diski je priporočljivo, da se podatki vstavljajo ne pogosteje kot enkrat na ~1 sekundo) za celoten strežnik).
Torej, potrebujemo približno naslednjo shemo tabel:
CREATE TABLE logs(
category LowCardinality(String), -- kategorija logov (opcijsko)
time DateTime, -- čas dogodka
millis UInt16, -- milisekunde (lahko so tudi mikrosekunde itd.): priporočljivo je shraniti, če je dogodkov veliko, da je lažje ločevati dogodke med seboj
..., -- vaša lastna polja, na primer ime strežnika, raven logiranja, in tako naprej
message String -- besedilo sporočila
) ENGINE=MergeTree()
ORDER BY (category, time, millis)Na žalost nisem takoj našel odprtih virov z realističnimi logi, ki bi jih bilo mogoče prenesti, zato sem namesto tega vzel za primer . Nedvomno njihova struktura ni povsem enaka strukturi besedilnih logov, vendar to za ilustracijo ni bistveno.
navodilo za nalaganje ocen Amazona v ClickHouse
Ustvarimo tabelo:
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=8192V naboru podatkov Amazona je samo datum za oceno, vendar ni natančnega časa, zato te podatke zapolnimo z naključnimi vrednostmi.
Ni potrebno prenesti vseh tsv-datotek, ampak se lahko omejimo na prvih ~10-20, da pridobimo dovolj velik nabor podatkov, ki ne bo šel v 16 GB pomnilnika. Za nalaganje TSV-datotek sem uporabil naslednji ukaz:
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'
doneNa standardnem Persistent Disku (HDD) v Google Cloud velikosti 1000 GB (to velikost sem izbral predvsem zato, da bi bila hitrost nekoliko višja, čeprav bi morda SSD zahtevane velikosti bil cenejši) je hitrost nalaganja znašala približno ~75 MB/s na 4 jedrih.
- Moram omeniti, da delam za Google, vendar sem uporabil osebni račun in ta članek nima zveze z mojim delom v podjetju.
Vse ilustracije bom izdelal prav s tem naborom podatkov, saj je to vse, kar sem imel pri sebi.
Prikaz napredka skeniranja podatkov
Ker bomo v ClickHouse uporabili polno skeniranje po tabeli z dnevniki, ta operacija lahko traja precej časa in dolgo ne prinaša nobenih rezultatov, če je najdenih malo ujemanj, je zaželeno znati prikazati napredek izvršitve poizvedbe, dokler ne pridobimo prvih vrstic z rezultatom. Za to v HTTP vmesniku obstaja parameter, ki omogoča, da napredek prenaša v HTTP glave: send_progress_in_http_headers=1. Na žalost standardna knjižnica Go ne zna brati glav, ko jih prejme, vendar ClickHouse podpira HTTP 1.0 (ne mešajte z 1.1!), zato lahko odprete surovo TCP povezavo s ClickHouse, pošljete tja GET /?query=... HTTP/1.0nn in prejmemo v odgovor glave in telo odgovora brez kakršnegakoli zakodiranja in šifriranja, tako da v tem primeru ne potrebujemo niti standardne knjižnice.
Streaming dnevnikov iz ClickHouse
ClickHouse že razmeroma dolgo (od leta 2019?) ima optimizacijo za poizvedbe z ORDER BY, tako da poizvedba takšne oblike
SELECT time, millis, message
FROM logs
WHERE message LIKE '%something%'
ORDER BY time DESC, millis DESCtakoj začenja vračati vrstice, kjer v sporočilu obstaja podniz "something", ne da bi čakal na zaključek skeniranja.
Prav tako bi bilo zelo priročno, če bi ClickHouse samodejno preklical poizvedbo, ko je povezava zaprta, vendar to ni privzeto vedenje. Samodejno prekinitev poizvedbe lahko vključite z možnostjo cancel_http_readonly_queries_on_client_close=1.
Pravilno obravnavanje SIGPIPE v Go
Ko izvajate, recimo, ukaz some_cmd | head -n 10, kako natančno se ukaz some_cmd zaključi svoje izvrševanje, ko head prebere 10 vrstic? Odgovor je preprost: ko head se zaključi, se cev zapre in stdout ukaza some_cmd začne kazati, pogojno, »nikamor«. Ko some_cmd poskuša zapisati v zaprto cev, .
V Go po privzhetu to tudi se zgodi, vendar obdelovalec signala SIGPIPE na koncu prav tako izpiše "signal: SIGPIPE" ali podoben stavek, in za odstranitev tega sporočila moramo sami obdelati SIGPIPE, kot želimo, torej preprosto tiho nastopiti iz programa:
ch := make(chan os.Signal)
signal.Notify(ch, syscall.SIGPIPE)
go func() {
<-ch
os.Exit(0)
}()Prikaz konteksta sporočila
Pogosto si želimo videti kontekst, v katerem je prišlo do napake (na primer, kateri zahtevek je sprožil paniko ali katere spremljajoče težave so bile vidne pred padcem), in v grep za to so na voljo možnosti -A, -B in -C, ki prikažejo določeno število vrstic po, pred in okoli sporočila.
Žal nisem našel preprostega načina, da bi to isto naredil v ClickHouse, zato, da bi prikazal kontekst, se za vsako vrstico rezultata pošlje dodatno poizvedbo, približno takšnega videza (podrobnosti so odvisne od razvrščanja in tega, ali se prikazuje kontekst pred ali po):
SELECT time,millis,review_body FROM amazon
WHERE (time = 'ČAS_DOGODKA' AND millis < MILISEKUND_DOGODKA) OR (time < 'ČAS_DOGODKA')
ORDER BY time DESC, millis DESC
LIMIT ŠTEVILO_VRSTIC_KONTEKSTA
SETTINGS max_threads=1Ker se poizvedba pošlje skoraj takoj po tem, ko je ClickHouse vrnil ustrezno vrstico, ta pade v predpomnilnik in na splošno se poizvedba izvede razmeroma hitro ter porabi malo CPU (običajno poizvedba traja okoli ~6 ms na moji virtualki).
Prikaz novih sporočil v realnem času
Da bi prikazovali prihajajoča sporočila v (skoraj) realnem času, preprosto izvajamo poizvedbo vsakih nekaj sekund, pri čemer si zapomnimo zadnji timestamp, ki smo ga srečali prej.
Primeri ukazov
Kako izgledajo tipične ukaze logscli v praksi?
Če ste naložili podatkovni komplet Amazon, ki sem ga omenil na začetku članka, potem boste lahko izvedli naslednje ukaze:
# Показать строки, где встречается слово 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' -tailfPovezave
Koda orodja (brez dokumentacije) je na voljo na githubu na naslovu . Vesel bom vaših misli o moji ideji za konzolni vmesnik za ogled logov na osnovi ClickHouse.
Izvor: habr.com
