Se você já usou interfaces web para visualizar logs, certamente percebeu o quão pesadas e, muitas vezes, pouco práticas e responsivas essas interfaces costumam ser. Algumas podem ser habituais, outras são simplesmente horríveis, mas, na minha opinião, a raiz de todos os problemas está no fato de que abordamos a tarefa de visualizar logs de maneira errada: tentamos criar uma interface web onde o CLI (interface de linha de comando) funciona muito melhor. Pessoalmente, sinto-me muito confortável trabalhando com tail, grep, awk e outros, e, portanto, a interface ideal para trabalhar com logs seria algo semelhante ao tail e grep, mas que pudesse ser usada para ler logs de vários servidores. Ou seja, claro, lê-los a partir do ClickHouse!
*na opinião pessoal de um usuário do Habr
Conheça o logscli
Não criei um nome para minha interface, e, para ser sincero, ela existe mais como um protótipo, mas se você quer checar o código-fonte imediatamente, sinta-se à vontade: (350 linhas de código refinado em Go).
Recursos
Eu tinha como objetivo criar uma interface que fosse familiar para aqueles acostumados com tail/grep, ou seja, suportar as seguintes funcionalidades:
- Visualizar todos os logs, sem filtragem.
- Manter linhas que contêm uma substring fixa (flag
-Fugrep). - Manter linhas que correspondem a uma expressão regular (flag
-Eugrep). - Por padrão, a visualização é em ordem cronológica reversa, pois geralmente os logs mais recentes são os mais relevantes.
- Mostrar contexto ao redor de cada linha (parâmetros
-A,-Be-Cugrep, imprimindo N linhas antes, depois e ao redor de cada linha correspondente, respectivamente). - Visualizar logs recebidos em tempo real, com ou sem filtragem (basicamente
tail -f | grep). - A interface deve ser compatível com
less,head,taile outros — por padrão, deve retornar resultados sem restrições quanto ao número; as linhas são impressas em fluxo enquanto o usuário estiver interessado em recebê-las; o sinalSIGPIPEdeve interromper silenciosamente o streaming de logs, assim como fazemtail,grepe outras utilitários UNIX.
Implementação
Vou assumir que você já sabe como enviar logs para o ClickHouse de alguma forma. Se não souber, recomendo tentar e , além de .
Para começar, é necessário definir o esquema do banco de dados. Uma vez que normalmente se deseja que os logs sejam recebidos em ordem cronológica, faz sentido armazená-los assim. Se houver muitas categorias de logs e todas forem semelhantes, pode-se usar a categoria de logs como a primeira coluna da chave primária — isso permitirá ter uma única tabela em vez de várias, o que será uma grande vantagem ao inserir dados no ClickHouse (em servidores com discos rígidos, recomenda-se inserir dados não mais que a cada ~1 segundo). para todo o servidor).
Ou seja, precisamos de um esquema de tabelas semelhante ao seguinte:
CREATE TABLE logs(
category LowCardinality(String), -- categoria dos logs (opcional)
time DateTime, -- tempo do evento
millis UInt16, -- milissegundos (pode haver também microssegundos, etc.): recomenda-se armazenar, se houver muitos eventos, para facilitar a diferenciação entre eles
..., -- seus próprios campos, por exemplo, nome do servidor, nível de log, e assim por diante
message String -- texto da mensagem
) ENGINE=MergeTree()
ORDER BY (category, time, millis)Infelizmente, não consegui encontrar imediatamente nenhuma fonte aberta com logs realistas que pudesse baixar, então usei, em vez disso, como exemplo . Sem dúvida, a estrutura deles não é exatamente igual à dos logs de texto, mas para ilustração isso não é essencial.
instruções para importação de avaliações da Amazon no ClickHouse
Vamos criar a tabela:
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=8192No conjunto de dados da Amazon, há apenas a data da avaliação, mas não há hora exata, então vamos preencher esses dados aleatoriamente.
Não é necessário baixar todos os arquivos tsv e você pode se restringir aos primeiros ~10-20, para já obter um conjunto de dados grande o suficiente que não caiba nos 16 GB de memória RAM. Para importar os arquivos TSV, usei o seguinte comando:
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'
doneEm um Persistent Disk padrão (que é HDD) no Google Cloud com tamanho de 1000 GB (esse tamanho eu escolhi principalmente para que a velocidade fosse um pouco maior, embora um SSD do volume necessário pudesse sair mais barato), a velocidade de upload foi de aproximadamente ~75 MB/s em 4 núcleos.
- Devo esclarecer que trabalho no Google, mas utilizei uma conta pessoal e este artigo não tem relação com meu trabalho na empresa.
Todas as ilustrações que vou produzir serão exatamente com este conjunto de dados, pois é tudo o que eu tinha à mão.
Mostrar o progresso da digitalização de dados
Como no ClickHouse usaremos uma varredura completa na tabela de logs, e essa operação pode levar um tempo considerável e não retornar resultados rapidamente se houver poucos matches, é desejável poder mostrar o progresso da execução da consulta até obter as primeiras linhas com resultados. Para isso, na interface HTTP existe um parâmetro que permite retornar o progresso nos cabeçalhos HTTP: send_progress_in_http_headers=1. Infelizmente, a biblioteca padrão do Go não consegue ler cabeçalhos à medida que os recebe, mas a interface HTTP 1.0 (não confundir com 1.1!) é suportada pelo ClickHouse, portanto, pode-se abrir uma conexão TCP bruta com o ClickHouse e enviar para lá GET /?query=... HTTP/1.0nn e receber em resposta os cabeçalhos e o corpo da resposta sem qualquer tipo de escape ou criptografia, assim não precisamos usar a biblioteca padrão nesse caso.
Streaming de logs do ClickHouse
No ClickHouse já há um tempo relativamente longo (desde 2019?) existe uma otimização para consultas com ORDER BY, de modo que uma consulta do tipo
SELECT time, millis, message
FROM logs
WHERE message LIKE '%something%'
ORDER BY time DESC, millis DESCcomeçará a retornar imediatamente as linhas cujas mensagens contêm a substring "something", sem esperar o término da digitalização.
Além disso, seria muito conveniente se o ClickHouse cancelasse automaticamente a consulta quando a conexão fosse encerrada, mas isso não é o comportamento padrão. O cancelamento automático da consulta pode ser ativado com a opção cancel_http_readonly_queries_on_client_close=1.
Tratamento correto de SIGPIPE em Go
Quando você executa, digamos, o comando some_cmd | head -n 10, de que forma exatamente o comando some_cmd encerra sua execução quando head lembrou-se de 10 linhas? A resposta é simples: quando head ele termina, o pipe é fechado, e stdout do comando some_cmd começa a apontar, digamos, 'para lugar nenhum'. Quando some_cmd tenta escrever em um pipe fechado, .
No Go, isso também acontece por padrão, mas o manipulador de sinal SIGPIPE no final também exibe "signal: SIGPIPE" ou uma mensagem semelhante. Para remover essa mensagem, você precisa lidar com SIGPIPE do jeito que quiser, ou seja, simplesmente sair em silêncio:
ch := make(chan os.Signal)
signal.Notify(ch, syscall.SIGPIPE)
go func() {
<-ch
os.Exit(0)
}()Mostrar o contexto da mensagem
Frequentemente, queremos ver o contexto em que ocorreu um erro (por exemplo, qual solicitação causou a falha ou quais problemas associados eram visíveis antes da queda), e grep para isso, existem as opções -A, -B e -C, que mostram o número especificado de linhas após, antes e ao redor da mensagem, respectivamente.
Infelizmente, não encontrei uma maneira simples de fazer o mesmo no ClickHouse, então, para exibir o contexto, uma consulta adicional é enviada para cada linha do resultado aproximadamente da seguinte forma (os detalhes dependem da ordenação e se o contexto é mostrado antes ou depois):
SELECT time, millis, review_body FROM amazon
WHERE (time = 'TEMPO_DO_EVENTO' AND millis < MILISSEGUNDOS_DO_EVENTO) OR (time < 'TEMPO_DO_EVENTO')
ORDER BY time DESC, millis DESC
LIMIT QUANTIDADE_DE_LINHAS_DE_CONTEXTO
SETTINGS max_threads=1Uma vez que a consulta é enviada quase imediatamente após o ClickHouse retornar a linha correspondente, ela entra no cache e, de modo geral, a consulta é executada rapidamente, consumindo um pouco de CPU (normalmente a consulta leva cerca de ~6 ms na minha máquina virtual).
Mostrar novas mensagens em tempo real
Para mostrar mensagens recebendo em modo (quase) real, basta executar a consulta a cada poucos segundos, lembrando o último timestamp que encontramos até então.
Exemplos de comandos
Como são os típicos comandos logscli na prática?
Se você carregou o conjunto de dados da Amazon que mencionei no início do artigo, poderá executar os seguintes comandos:
# Показать строки, где встречается слово 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
O código da utilidade (sem documentação) está disponível no github em . Fico feliz em ouvir suas opiniões sobre minha ideia para uma interface de console para visualizar logs com base no ClickHouse.
Fonte: habr.com
