LogRock: testando através do log
Por mais de 2 anos, trabalhamos em nosso projeto
Cleverbrush . Este é um software para trabalhar com gráficos vetoriais. Trabalhar com um editor gráfico implica em um grande número de casos de uso de aplicativos. Estamos tentando economizar dinheiro e tempo, então otimizamos tudo, incluindo testes. Cobrir os casos de teste com cada opção é muito caro e irracional, principalmente porque é impossível cobrir todas as opções.
Durante o desenvolvimento, um módulo para aplicativos React JS foi criado -
LogRock (github) .
Este módulo permite organizar aplicativos de registro modernos. Com base nos logs, realizamos testes. Neste artigo, falarei sobre os meandros do uso deste módulo e como organizar os testes por meio do log.
Qual é o problema?
Se você comparar o programa com um organismo vivo, um bug nele é uma doença. A causa dessa "doença" pode ser uma série de fatores, incluindo o ambiente de um usuário específico. Isto é especialmente verdade se estamos considerando uma plataforma web. Às vezes, um relacionamento causal é muito complexo, e o bug encontrado durante o teste é o resultado de vários eventos.
Como nas doenças humanas, ninguém explica melhor seus sintomas do que o paciente, nenhum testador será capaz de dizer o que aconteceu, melhor do que o próprio programa.
O que fazer?
Para entender o que está acontecendo, precisamos de uma lista de ações que o usuário executou em nosso aplicativo.
Para que nosso próprio programa possa nos dizer o que "dói",
pegaremos o módulo
LogRock (github) e o associaremos ao ElasticSearch, LogStash e Kibana.
O ElasticSearch é um poderoso mecanismo de pesquisa de texto completo. Você pode assistir ao tutorial do ElasticSearch
aqui .
O LogStash é um sistema para coletar logs de várias fontes, que podem enviar logs, inclusive para o ElasticSearch.
Kibana é uma interface da web para o ElasticSearch com muitos complementos.
Como isso funciona?
Em caso de erro (ou apenas sob demanda), o aplicativo envia logs para o servidor onde eles são salvos em um arquivo. O logstash salva incrementalmente os dados no ElasticSearch - no banco de dados. O usuário efetua login no Kibana e vê os logs salvos.
Parece um Kibana bem ajustado. Ele exibe dados do ElasticSearch. O Kibana pode exibir dados na forma de tabelas, gráficos, mapas etc., o que é muito conveniente para analisar e entender o que está acontecendo com nosso aplicativo.
Neste artigo, NÃO discutirei a configuração do ElasticStack!Criando um sistema de log
Como exemplo, integraremos o sistema de registro em um aplicativo JS de uma página escrito em React. Realmente não importa em qual estrutura seu aplicativo será gravado. Vou tentar descrever a abordagem da construção de um sistema de log.
1. Cliente
1.0 LogRock. Instalação
Link para LogRockPara instalar, você deve executar:
npm install logrock yarn add logrock
1.1 LogRock. Configuração do aplicativo
Para começar, envolva nosso aplicativo em um componente
import { LoggerContainer } from "logrock"; <LoggerContainer> <App /> </LoggerContainer>
LoggerContainer é um componente que responde aos erros do aplicativo e forma uma pilha.
Uma pilha é um objeto com informações sobre o sistema operacional do usuário, navegador, qual botão do mouse ou teclado foi pressionado e, é claro, a sub-matriz de ações, onde são registradas todas as ações do usuário que ele executou em nosso sistema.
O LoggerContainer possui várias configurações, considere algumas delas
<LoggerContainer active={true|false} limit={20} onError={stack => { sendToServer(stack); }} > <App /> </LoggerContainer>
active - ativar ou desativar o criador de logs
limite - define um limite para o número de ações recentes salvas pelo usuário. Se o usuário executar 21 ações, o primeiro nesta matriz será excluído automaticamente. Assim, teremos as últimas 20 ações que precederam o erro.
onError - o retorno de chamada que é chamado quando ocorre um erro. O objeto Stack aparece nele, no qual todas as informações sobre o ambiente, ações do usuário etc. são armazenadas. É a partir desse retorno de chamada que precisamos enviar esses dados para o ElasticSearch ou o back-end ou salvá-los em um arquivo para análise e monitoramento adicionais.
1.2 LogRock. Registo
Para fazer o log de alta qualidade das ações do usuário, teremos que cobrir nosso código com chamadas de log.
O módulo LogRock vem com um logger associado a um LoggerContainer
Suponha que tenhamos um componente
import React, { useState } from "react"; export default function Toggle(props) { const [toggleState, setToggleState] = useState("off"); function toggle() { setToggleState(toggleState === "off" ? "on" : "off"); } return <div className={`switch ${toggleState}`} onClick={toggle} />; }
Para cobri-lo adequadamente com um log, precisamos modificar o método de alternância
function toggle() { let state = toggleState === "off" ? "on" : "off"; logger.info(`React.Toggle|Toggle component changed state ${state}`); setToggleState(state); }
Adicionamos um logger no qual as informações são divididas em 2 partes. React.Toggle nos mostra que essa ação ocorreu no nível de React, o componente Toggle, e então temos uma explicação verbal da ação e do estado atual que veio a esse componente. Essa separação em níveis não é necessária, mas com essa abordagem ficará mais claro onde exatamente nosso código foi executado.
Também podemos usar o método “componentDidCatch”, que foi introduzido no React versão 16, em caso de erro.
2. Interação com o servidor
Considere o seguinte exemplo.
Suponha que tenhamos um método que coleta dados do usuário de um back-end. O método é assíncrono, parte da lógica está oculta no back-end. Como registrar esse código corretamente?
Primeiro, como temos um aplicativo cliente, todas as solicitações enviadas ao servidor passarão por uma única sessão de usuário, sem recarregar a página. Para associar ações no cliente a ações no servidor, devemos criar um SessionID global e adicioná-lo ao cabeçalho de cada solicitação no servidor. No servidor, podemos usar qualquer criador de logs que cubra nossa lógica como um exemplo do frontend e, em caso de erro, envie esses dados com o sessionID anexado no Elastic para a placa de back-end.
1. Geramos o SessionID no cliente
window.SESSION_ID = `sessionid-${Math.random().toString(36).substr(3, 9)}`;
2. Devemos definir o SessionID para todas as solicitações ao servidor. Se usarmos bibliotecas para consultas, isso é muito simples, declarando um SessionID para todas as consultas.
let fetch = axios.create({...}); fetch.defaults.headers.common.sessionId = window.SESSION_ID;
3. No LoggerContainer, há um campo especial para SessionID
<LoggerContainer active={true|false} sessionID={window.SESSION_ID} limit={20} onError={stack => { sendToServer(stack); }} > <App /> </LoggerContainer>
4. A solicitação em si (no cliente) terá a seguinte aparência:
logger.info(`store.getData|User is ready for loading... User ID is ${id}`); getData('/api/v1/user', { id }) .then(userData => { logger.info(`store.getData|User have already loaded. User count is ${JSON.stringify(userData)}`); }) .catch(err => { logger.error(`store.getData|User loaded fail ${err.message}`); });
Como tudo funcionará: registramos um log antes da solicitação no cliente. De acordo com nosso código, vemos que o carregamento de dados do servidor começará agora. Anexamos um SessionID à solicitação. Se nosso back-end estiver coberto de logs com a adição deste SessionID e a solicitação falhar, podemos ver o que aconteceu no back-end.
Assim, monitoramos todo o ciclo de nossa aplicação, não apenas no cliente, mas também no back-end.
3. O testador
Trabalhar com um testador merece uma descrição separada do processo.
Como temos uma startup, não temos requisitos formais e, às vezes, nem tudo é lógico no trabalho.
Se o testador não entender o comportamento, esse é um caso que pelo menos precisa ser considerado. Além disso, frequentemente, um testador simplesmente não pode repetir uma situação duas vezes. Como as etapas que levam ao comportamento incorreto podem ser numerosas e não triviais. Além disso, nem todos os erros levam a consequências críticas, como a exceção. Alguns deles podem alterar apenas o comportamento do aplicativo, mas não podem ser interpretados pelo sistema como um erro. Para esses fins, na preparação, você pode adicionar um botão no cabeçalho do aplicativo para forçar o envio de logs. O testador vê que algo não está funcionando direito, clica no botão e envia a pilha com ações para o ElasticSearch.
Se, no entanto, ocorreu um erro crítico, devemos bloquear a interface para que o testador não clique mais e não atinja um beco sem saída.
Para esses fins, exibimos a tela azul da morte.
Vemos no topo o texto com Pilha desse erro crítico e abaixo - as ações que o precederam. Também obtemos o ID do erro, basta o testador selecioná-lo e anexá-lo ao ticket. Posteriormente, esse erro pode ser facilmente encontrado no Kibana por esse ID.
Para esses fins, o LoggerContainer possui suas próprias propriedades.
<LoggerContainer active={true|false} limit={20} bsodActive={true} bsod={BSOD} onError={stack => { sendToServer(stack); }} > <App /> </LoggerContainer>
bsodActive - ativa / desativa o BSOD (desabilitar o BSOD se aplica ao código de produção)
O bsod é um componente. Por padrão, parece com a captura de tela acima.
Para exibir um botão em um UI LoggerContainer, podemos usar em contexto
context.logger.onError(context.logger.getStackData());
4. LogRock. Interação do usuário
Você pode enviar logs para o console ou mostrá-los ao usuário. Para isso, é necessário usar o método stdout:
<LoggerContainer active={true|false} limit={20} bsodActive={true} bsod={BSOD} onError={stack => { sendToServer(stack); }} stdout={(level, message, important) => { console[level](message); if (important) { alert(message); } }} > <App /> </LoggerContainer>
stdout é um método responsável pela exibição de mensagens.
Para que a mensagem se torne importante, basta passar o segundo parâmetro verdadeiro para o criador de logs. Portanto, essa mensagem pode ser exibida ao usuário em uma janela pop-up; por exemplo, se o carregamento dos dados falhar, podemos exibir uma mensagem de erro.
logger.log('Something was wrong', true);
Registro avançado
Se você usar Redux ou soluções semelhantes em uma loja, poderá colocar um registrador no Middleware processando suas ações, assim, todas as ações significativas passarão pelo nosso sistema.
Para um registro eficaz, você pode agrupar seus dados em um objeto proxy e colocar os registradores em todas as ações com o objeto.
Para cobrir métodos de terceiros com o log (métodos de biblioteca, métodos de código herdado), você pode usar decoradores - “@”.
Dicas
Aplicativos de log, inclusive na produção, porque, melhor que os usuários reais, nenhum testador encontrará gargalos.
Não se esqueça de indicar a coleção de logs no contrato de licença.
NÃO registre senhas, dados bancários e outras informações pessoais!A redundância de logs também é ruim, torne as assinaturas o mais claras possível.
Alternativas
Como abordagens alternativas, destaco:
- A barra de rolagem é altamente personalizável. Permite registrar 500 mil erros por US $ 150 por mês. Eu recomendo usá-lo se você estiver desenvolvendo um aplicativo a partir do zero.
- O Sentry é mais fácil de integrar, mas menos personalizável. Permite registrar 1 milhão de eventos por US $ 200 por mês.
Ambos os serviços permitem que você faça quase a mesma coisa e integre-se ao back-end.
O que vem a seguir
O registro não é apenas a busca de erros, mas também o monitoramento de ações do usuário, coleta de dados. O registro em log pode ser um bom complemento para os testes do Google Analytics e da experiência do usuário.
Conclusões
Quando você lança o aplicativo, a vida está apenas começando para ele. Seja responsável por sua ideia, obtenha feedback, monitore logs e aprimore-o. Escreva software de alta qualidade e prospere :)
PS Se você quiser ajudar no desenvolvimento de módulos para Angular, Vue, etc. Ficarei feliz em receber solicitações -
aqui .