image

Acesse bootcamps ilimitados e +650 cursos

50
%OFF
Article image
Kevin Santana
Kevin Santana27/06/2024 17:20
Compartilhe

Práticas Recomendadas para Monitoramento e Logging de APIs GraphQL em Node.js

    Com a alta demanda de novos projetos de SaaS, Erps e Webapps seja em servidores microservices ou monolíticos e suas evoluções ridiculamente rápida, aumentando as cargas de trabalho por causa da complexidade cada vez maior, principalmente agora com o boom da inteligência artificial sendo aplicado em tuuudo (não que eu concorde 100% com isso, mas isso é história pra outro artigo), precisamos ter um refúgio seguro para manter tudo isso sob controle sem fazer vista grossa.

    O nosso queridinho das linguagens de comunicação, ele mesmo, o GraphQL, apesar das inúmeras vantagens em sua utilização, devido a sua natureza e “jeito de executar”, além da camada extra de codificação e implementação, uma das suas dificuldades é que ele pode deixar as coisas um tanto obscuras em produção quando se trata de saber tudo o que se passa por “debaixo dos panos”, ninguém quer ver o seu servidor sofrer com problemas técnicos em produção não é mesmo? bom, é aí que a gente começa a mexer com monitoramento e logging.

    A importância do Monitoramento e Logging

    Sabe porque monitoramento e logging são tão importantes? Imagina que sua API GraphQL é como um carro. O monitoramento é o painel de controle que te mostra se está tudo funcionando bem. Já o logging é como o histórico de manutenção, registrando tudo o que acontece. Sem eles, fica difícil saber se está tudo ok ou se algo precisa ser ajustado. E ninguém quer descobrir um problema só quando o usuário reclama, né?

    “Certo, mas pra mim parece tudo a mesma coisa, monitoramento não é o mesmo que logging?”

    Não jovem mancebo, venha comigo!

    Diferença entre Monitoramento e Logging

    Beleza, agora vamos falar sobre a diferença entre esses dois. Monitoramento é o ato de ficar de olho nos seus sistemas em tempo real. Ele te avisa se algo está fora do normal, tipo um alarme. Já o logging é o registro detalhado de tudo o que acontece na sua aplicação. Ele guarda esses dados para que você possa investigar problemas depois. Pense no monitoramento como um vigia e no logging como um detetive.

    Melhores práticas

    Certo! Sem mais enrolação, vamos falar um pouco sobre algumas boas práticas que você meu caro, vai poder utilizar nas suas aplicações. Como eu disse, são boas práticas, é recomendado, não obrigatório, então, jovem gafanhoto, não vá inventar de querer reescrever aplicações em perfeito funcionamento só pra implementar essas coisas sem um fundamento estudado em (mas eu recomendo se o seu servidor não tem nenhum tipo de monitoramento ou logging, só lembra de fazer tudo isso em um branch de DEV primeiro em amiguinho!)

    Monitoramento

    1. Utilizar Ferramentas de Monitoramento de Desempenho:
    2. APM (Application Performance Management): Ferramentas como New Relic, Datadog, ou AppDynamics ajudam a monitorar o desempenho das suas APIs, identificando gargalos e monitorando métricas como tempo de resposta, taxa de erro e throughput (quantidade de dados transferidos de um lugar a outro).
    3. Prometheus e Grafana: Para um sistema mais personalizado de monitoramento e visualização de métricas.
    4. Métricas Específicas para GraphQL:
    5. Tempo de Execução das Queries: Monitorar o tempo que cada query leva para ser processada.
    6. Tamanho da Resposta: Analisar o tamanho das respostas pode ajudar a identificar consultas que retornam dados excessivos.
    7. Erros e Exceções: Contabilizar e analisar os tipos de erros que ocorrem.
    8. Taxa de Sucesso das Queries: Monitorar a taxa de sucesso/falha das requisições.
    9. Monitoramento de Infraestrutura:
    10. CPU, Memória e Utilização de Rede: Utilizar ferramentas como Prometheus para monitorar os recursos do servidor.
    11. Logs de Sistema: Analisar logs do sistema operacional e do servidor para identificar problemas de infraestrutura.
    12. Tracing Distribuído:
    13. Ferramentas como Jaeger ou Zipkin ajudam a rastrear as requests através de diferentes serviços, útil em arquiteturas de microsserviços.

    Logging

    1. Log Structured:
    2. Utilizar formatos estruturados como JSON para facilitar a análise e pesquisa dos logs.
    3. Bibliotecas como Winston ou Bunyan podem ser úteis para gerar logs estruturados.
    4. Níveis de Log:
    5. Error: Logs de erros críticos que necessitam de atenção imediata.
    6. Warn: Advertências sobre possíveis problemas que não interrompem o serviço.
    7. Info: Informações gerais sobre o funcionamento normal do sistema.
    8. Debug: Informações detalhadas para desenvolvimento e debugging.
    9. Log de Queries e Mutations:
    10. Registrar queries e mutations, especialmente aquelas que falham, com detalhes suficientes para reproduzir e entender os problemas.
    11. Redacted Logs: Garantir que dados sensíveis sejam mascarados ou removidos dos logs.
    12. Contexto nos Logs:
    13. Incluir informações contextuais como ID do usuário, ID da transação, e tempo da requisição para facilitar a correlação de eventos.
    14. Centralização de Logs:
    15. Utilizar sistemas como ELK Stack (Elasticsearch, Logstash, Kibana) ou Splunk para agregar, analisar e visualizar os logs.
    16. Log Rotation e Retenção: Configurar políticas de rotação e retenção de logs para evitar sobrecarga de armazenamento.

    Essas são algumas técnicas que eu consegui pensar trabalhando no dia-a-dia, mas fique a vontade para melhorar o conteúdo. É óbvio que eu também vou colocar um exemplo de código bem simples, mas poderoso que você pode utilizar como base.

    Implementação Prática

    A implementação abaixo é básica e está em TypeScript, mas pode ser feita com a maioria das linguagem levando em consideração as práticas e não o framework ou biblioteca utilizada.

    Não leve em consideração o tamanho do arquivo, as responsabilidades ou a quantidade de linhas.. é óbvio que tudo pode ser feito de maneira a ficar mais robusto, performático ou “profissional”, enfim, neste caso é um exemplozinho tá!

    logger.ts - Aqui você configura o logger com o winston, falei dele mais acima.

    import { createLogger, format, transports } from 'winston';
    
    
    const logger = createLogger({
    level: 'info',
    format: format.json(),
    transports: [
      new transports.File({ filename: 'error.log', level: 'error' }),
      new transports.File({ filename: 'combined.log' }),
    ],
    });
    
    
    if (process.env.NODE_ENV !== 'production') {
    logger.add(new transports.Console({
      format: format.simple(),
    }));
    }
    
    
    export default logger;
    
    

    newRelicConfig.js - Aqui é a config do NewRelic, sim, ta em JS eu sei.. é só um 

    exports, calma, jovem.

    // newrelic.js
    'use strict';
    
    
    /**
     * Configuração do New Relic agent.
     *
     * Em lib/config/default.js nos arquivos do new relic vc encontra todas
     * as descrições possíveis de configurações e variais com seus valores em potencial.
     */
    exports.config = {
    app_name: ['Nome da sua Aplicação'],
    license_key: 'SUA_CHAVE_NEW_RELIC_AQUI',
    logging: {
      level: 'info'
    }
    };
    
    

    server.ts - e aqui é o restante do servidor em si, deixei tudo junto pra que você

    faça uma leitura corrida, a famosa leitura em F… 

    import 'newrelic'; // Importar o New Relic no início
    import { ApolloServer, ApolloServerPlugin, GraphQLRequestListener } from 'apollo-server';
    import { makeExecutableSchema } from '@graphql-tools/schema';
    import { v4 as uuidv4 } from 'uuid';
    import typeDefs from './schema'; // Importar o esquema GraphQL
    import resolvers from './resolvers'; // Importar os resolvers
    import logger from './logger'; // Importar o módulo de logger
    
    
    interface Context {
    requestId: string;
    }
    
    
    const schema = makeExecutableSchema({ typeDefs, resolvers });
    
    
    const requestLoggingPlugin: ApolloServerPlugin = {
    requestDidStart(requestContext) {
      const requestId = requestContext.request.http?.headers.get('x-request-id') || uuidv4();
    
    
      logger.info('GraphQL Request Start', {
        requestId,
        query: requestContext.request.query,
        variables: requestContext.request.variables,
      });
    
    
      return {
        willSendResponse(responseContext) {
          logger.info('GraphQL Response', {
            requestId,
            response: responseContext.response.data,
          });
        },
      } as GraphQLRequestListener<Context>;
    },
    };
    
    
    const newRelicPlugin: ApolloServerPlugin = {
    requestDidStart(requestContext) {
      const transactionName = requestContext.request.operationName || 'anonymous';
      const transaction = newrelic.startWebTransaction(transactionName, () => {
        const transaction = newrelic.getTransaction();
    
    
        requestContext.context.transaction = transaction;
    
    
        return {
          willSendResponse() {
            transaction.end();
          },
        };
      });
    
    
      return {
        willSendResponse() {
          transaction.end();
        },
      } as GraphQLRequestListener<Context>;
    },
    };
    
    
    const server = new ApolloServer({
    schema,
    context: ({ req }): Context => {
      return {
        requestId: req.headers['x-request-id'] || uuidv4(),
      };
    },
    formatError: (err) => {
      logger.error('GraphQL Error', { message: err.message, locations: err.locations, path: err.path });
      newrelic.noticeError(err); // Reportar o erro ao New Relic
      return err;
    },
    plugins: [requestLoggingPlugin, newRelicPlugin],
    });
    
    
    server.listen().then(({ url }) => {
    console.log(`🚀 Server ready at ${url}`);
    });
    
    

    Ok! mas aos detalhes técnicos, o que seria winston? 

    Winston é uma biblioteca de logging universal que aceita diversos tipos de transport (transporte aqui meu caro, são os “armazéns” de seus logs, que pode ser literalmente qualquer coisa definida, pode ser um banco de dados para guardar cada tipo de log, ou simplesmente uma pasta em um local específico que você definiu na função), ou seja, você não precisa instalar 30 tipos diferentes de pacotes de bibliotecas de logs para cada coisa que você vá querer registrar.

     

    O winston consegue registrar qualquer tipo de log que você necessite, sabe aquela função de auditoria que tem que estar isolada da execução do serviço e sempre tem que funcionar mesmo que tudo dê errado? “apóis”, é pra isso mesmo que ele serve.

    Já o New Relic é para monitoramento, é um tanto mais robusto, porque não se trata de apenas um package, apesar de ser OpenSource e inicialmente grátis, digamos que se a sua aplicação for muito grande e você precisa de algumas capacidades a mais, vai ter que desembolsar uma grana, sabe como é né… “é por causa dos energéticos”, quem pegou essa referência… tem mais de 25 anos.. 

    O New Relic não é tão simples de configurar pois precisa agir como se fosse um middleware para pegar a requisição no ar no ato da chamada HTTP para observar o que está acontecendo, é meio invasivo ele, mas é gente boa, sabe aquele seu amigo intrometido, é tipo ele.

    Observações:

    1. Imports: Certifique-se de que os caminhos dos imports (./schema, ./resolvers, ./logger) estejam corretos conforme sua estrutura de projeto.
    2. Tipos: Adicionei tipagem explícita em vários lugares para garantir que o TypeScript reconheça e verifique corretamente os tipos.
    3. UUID: A função uuidv4 é utilizada para gerar um ID único para cada requisição, caso o cabeçalho x-request-id não esteja presente.
    4. Logger: O logger utiliza o Winston configurado em um arquivo separado (logger.ts).
    5. importação do New Relic: Certifique-se de importar o New Relic logo no início do seu arquivo principal para garantir que ele seja inicializado antes de qualquer outra coisa.
    6. Transações: No plugin newRelicPlugin, cada requisição GraphQL é tratada como uma transação que é iniciada no início da requisição e terminada quando a resposta é enviada.
    7. Relatório de Erros: Use newrelic.noticeError(err) para reportar erros ao New Relic.
    8. Licença e Nome do Aplicativo: Substitua SUA_CHAVE_NEW_RELIC_AQUI pelo seu chave de licença do New Relic e Nome da sua Aplicação pelo nome do seu aplicativo no arquivo newrelic.js.

    Conclusão

    Seguir essas práticas recomendadas ajudará a garantir que suas APIs GraphQL sejam eficientes, seguras e confiáveis, proporcionando uma melhor experiência tanto para os desenvolvedores quanto para os usuários finais.

    Curtiu as dicas? Então, bora se conectar nas redes sociais! Me segue para mais conteúdos sobre desenvolvimento, dicas de carreira e novidades tech. Vamos crescer juntos nessa jornada! 

    Github Linkedin Portfólio

    #GraphQL #NodeJS #APIMonitoring #Logging #TechTips

    E aí, pronto para melhorar o monitoramento e logging da sua API GraphQL? 🚀

    Compartilhe
    Comentários (0)