Hvis du nogensinde har brugt webgrænseflader til logvisning, har du sikkert bemærket, hvor ofte disse grænseflader er klodsede og (oftest) ikke særlig intuitive eller responsive. Nogle kan man vænne sig til, nogle er helt forfærdelige, men jeg mener, at årsagen til alle problemerne ligger i, at vi tilgår opgaven med logvisning forkert: vi forsøger at skabe en webgrænseflade, hvor CLI (kommandolinjegrænseflade) fungerer bedre. Jeg selv føler mig meget komfortabel med tail, grep, awk og andre, og derfor ville den ideelle grænseflade til arbejde med logs være noget, der ligner tail og grep, men som samtidig kunne bruges til at læse logs fra mange servere. Det vil sige, selvfølgelig læse dem fra ClickHouse!
*efter personlig mening fra en habra-bruger
Mød logscli
Jeg har ikke fundet på et navn til min grænseflade, og for at være ærlig, så eksisterer den mest som en prototype. Men hvis du vil se koden med det samme, så er du velkommen: (350 linjer udvalgt kode i Go).
Muligheder
Jeg satte mig for at lave en grænseflade, der ville føles kendt for dem, der er vant til tail/grep, dvs. understøtte følgende funktioner:
- Visning af alle logs uden filtrering.
- Behold linjer, der indeholder en fast understreng (flag
-Fafgrep). - Behold linjer, der matcher et regulært udtryk (flag
-Eafgrep). - Som standart visning i revers kronologisk rækkefølge, da de nyeste logs som regel er de mest interessante.
- Vis kontekst omkring hver linje (parametre
-A,-Bog-Cafgrep, udskriver N linjer før, efter samt omkring hver matchende linje henholdsvis). - Visning af indkommende logs i realtid, med og uden filtrering (i det væsentlige
tail -f | grep). - Grænsefladen skal være kompatibel med
less,head,tailog andre – som standard returneres resultaterne uden begrænsning i antal; linjerne udskrives streamet, så længe brugeren er interesseret i at modtage dem; signalSIGPIPEskal stille og roligt afbryde log-streamingen, ligesomtail,grepog andre UNIX-værktøjer.
Implementering
Jeg vil forudsætte, at du allerede på en eller anden måde ved, hvordan du får logs til ClickHouse. Hvis ikke, anbefaler jeg at prøve og , samt .
For det første skal vi bestemme os for skemaet for databasen. Da logs ofte ønskes modtaget sorteret efter tid, giver det mening at gemme dem på den måde. Hvis der er mange logkategorier, og de alle er ens, kan man gøre logkategorien til den første kolonne i den primære nøgle — dette vil give mulighed for at have én tabel i stedet for flere, hvilket ved indsættelse i ClickHouse vil være en stor fordel (på servere med hårde diske anbefales det at indsætte data ikke oftere end ca. 1 gang i sekundet. på hele serveren).
Så vi har brug for et omtrent følgende skema for tabeller:
CREATE TABLE logs(
category LowCardinality(String), -- logkategori (valgfrit)
time DateTime, -- tidspunkt for hændelsen
millis UInt16, -- millisekunder (kan også være mikrosekunder osv.): anbefales at gemme, hvis der er mange hændelser, for lettere at skelne mellem dem
..., -- dine egne felter, såsom servernavn, logniveau osv.
message String -- meddelelsestekst
) ENGINE=MergeTree()
ORDER BY (category, time, millis)Desværre kunne jeg ikke straks finde nogen åbne kilder med realistiske logs, som jeg kunne tage og downloade, så jeg i stedet tog dem som eksempel . Deres struktur er bestemt ikke helt som tekstlogs, men dette er ikke afgørende for illustrationen.
vejledning til upload af Amazon-anmeldelser til ClickHouse
Lad os oprette tabellen:
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=8192I Amazons datasæt er der kun datoen for anmeldelsen, men ikke det nøjagtige tidspunkt, så vi udfylder disse data med tilfældigt genererede værdier.
Det er ikke nødvendigt at downloade alle tsv-filerne, og man kan begrænse sig til de første ~10-20 for allerede at have et stort nok datasæt, der ikke kan være i 16 GB RAM. For at uploade TSV-filerne brugte jeg følgende kommando:
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'
donePå den standard Persistent Disk (der er HDD) i Google Cloud på 1000 GB (jeg valgte denne størrelse primært for at opnå en lidt højere hastighed, selvom SSD i den ønskede størrelse muligvis ville have været billigere) var indlæsningshastigheden cirka ~75 MB/s på 4 kerner.
- Jeg skal nævne, at jeg arbejder i Google, men jeg har brugt en personlig konto, og denne artikel har ikke noget med mit arbejde i firmaet at gøre.
Alle illustrationer vil jeg lave med lige præcis dette datasæt, da det er alt, hvad jeg havde ved hånden.
Visning af scanningens fremgang
Da vi i ClickHouse vil anvende en fuld scanning af logtabellen, og denne operation kan tage betydelig tid og længe ikke returnere nogen resultater, hvis der findes få match, er det ønskeligt at kunne vise fremdriften af forespørgslen, indtil de første rækker med resultaterne modtages. Derfor er der i HTTP-grænsefladen en parameter, der tillader at returnere fremgang i HTTP-hovederne: send_progress_in_http_headers=1. Desværre kan den standard bibliotek Go ikke læse hovederne, mens de modtages, men HTTP 1.0-grænsefladen (forveksl ikke med 1.1!) understøttes af ClickHouse, så vi kan åbne en rå TCP-forbindelse til ClickHouse, sende derhen GET \/?query=... HTTP\/1.0nn og modtage hovedene og kroppens svar uden nogen form for escape og kryptering, så i dette tilfælde kræves selv den standardbibliotek ikke.
Streaming af logs fra ClickHouse
ClickHouse har allerede for relativt lang tid siden (siden 2019?) haft optimering for forespørgsler med ORDER BY, så en forespørgsel som
SELECT time, millis, message
FROM logs
WHERE message LIKE '%something%'
ORDER BY time DESC, millis DESCvil straks begynde at returnere rækker, hvor message indeholder substrengen "something", uden at vente på, at skaningen er afsluttet.
Desuden ville det være meget praktisk, hvis ClickHouse selv annullerede forespørgslen, når forbindelsen blev lukket, men dette er ikke standardadfærden. Automatisk annullering af forespørgslen kan aktiveres med indstillingen cancel_http_readonly_queries_on_client_close=1.
Korrekt behandling af SIGPIPE i Go
Når du udfører, lad os sige, kommandoen some_cmd | head -n 10, på hvilken måde afslutter kommandoen some_cmd sin sin udførelse, når head den har læst 10 rækker? Svaret er enkelt: når head slutter, pipen lukkes, og stdout for kommandoen some_cmd begynder at pege, betinget, "ingen steder hen". Når some_cmd den forsøger at skrive til en lukket pipe, .
I Go sker dette også som standard, men SIGPIPE signalhåndterer også skriver "signal: SIGPIPE" eller en lignende besked, og for at fjerne denne besked skal man blot håndtere SIGPIPE, som man vil, det vil sige bare afslutte stille og roligt:
ch := make(chan os.Signal)
signal.Notify(ch, syscall.SIGPIPE)
go func() {
<-ch
os.Exit(0)
}()Vis konteksten for beskeden
Det er ofte ønskeligt at se konteksten, hvori en fejl opstod (for eksempel, hvilken anmodning der forårsagede panik, eller hvilke ledsagende problemer der var synlige før nedbruddet), og grep til dette bruges mulighederne -A, -B og -C, som viser det angivne antal linjer efter, før, og omkring beskeden henholdsvis.
Desværre har jeg ikke fundet en enkel måde at gøre det samme i ClickHouse, så for at vise konteksten sendes der en ekstra forespørgsel for hver resultatlinje af omtrent følgende art (detaljer afhænger af sortering og om konteksten vises før eller efter):
SELECT time,millis,review_body FROM amazon
WHERE (time = 'BEGIVENHEDENS_TID' AND millis < MILLISEKUNDERNE_DERFOR) OR (time < 'BEGIVENHEDENS_TID')
ORDER BY time DESC, millis DESC
LIMIT ANTAL_LINJER_KONTEKST
SETTINGS max_threads=1Da forespørgslen sendes næsten med det samme efter, at ClickHouse har returneret den relevante linje, havner den i cache, og generelt udføres forespørgslen ganske hurtigt og bruger lidt CPU (normalt tager forespørgslen cirka ~6 ms på min virtuelle maskine).
Vis nye meddelelser i realtid
For at vise de indkommende meddelelser i (næsten) realtid skal man bare køre forespørgslen hver få sekunder, idet man gemmer det sidste tidsstempel, man har mødt før.
Eksempler på kommandoer
Hvordan ser typiske logscli-kommandoer ud i praksis?
Hvis du har uploadet datasættet Amazon, som jeg nævnte i begyndelsen af artiklen, kan du udføre følgende kommandoer:
# Показать строки, где встречается слово 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' -tailfLinks
Koden til værktøjet (uden dokumentation) er tilgængelig på github på adressen . Jeg vil glæde mig til at høre dine tanker om min idé til et konsolinterface til visning af logger baseret på ClickHouse.
Kilde: habr.com
