Como Usar o Winston para Registrar Aplicações Node.js no Ubuntu 20.04

Introdução

Uma solução eficaz de registro é crucial para o sucesso de qualquer aplicativo. Winston é uma biblioteca de registro versátil e uma solução popular disponível para aplicativos Node.js. As características do Winston incluem suporte para várias opções de armazenamento, níveis de registro, consultas de registro e um profiler integrado.

Neste tutorial, você usará o Winston para registrar um aplicativo Node/Express que você criará como parte deste processo. Você também verá como combinar o Winston com Morgan, outro logger de middleware de solicitação HTTP popular para Node.js, para consolidar logs de dados de solicitações HTTP com outras informações. Após completar este tutorial, seu servidor Ubuntu estará executando um pequeno aplicativo Node/Express, e o Winston será implementado para registrar erros e mensagens em um arquivo e no console.

Pré-requisitos

Para seguir este tutorial, você precisará de:

Passo 1 — Criando um Aplicativo Básico Node/Express

O Winston é frequentemente usado para registrar eventos de aplicativos da web construídos com Node.js. Neste passo, você criará um aplicativo da web Node.js simples usando o framework Express. Você usará express-generator, uma ferramenta de linha de comando, para colocar um aplicativo da web Node/Express em funcionamento rapidamente.

Como você instalou o Gerenciador de Pacotes Node durante os pré-requisitos, você pode usar o comando npm para instalar o express-generator:

  1. sudo npm install express-generator -g

A flag -g instala o pacote globalmente, o que significa que ele pode ser usado como uma ferramenta de linha de comando fora de um projeto/módulo Node existente.

Com o express-generator instalado, você pode criar seu aplicativo usando o comando express, seguido pelo nome do diretório que você deseja usar para o projeto:

  1. express myApp

Para este tutorial, o projeto será chamado myApp.

Nota: Também é possível executar a ferramenta express-generator diretamente sem instalá-la globalmente como um comando de sistema em primeiro lugar. Para fazer isso, execute este comando:

  1. npx express-generator myApp

O comando npx é um executor de comandos incluído com o Gerenciador de Pacotes Node que facilita a execução de ferramentas de linha de comando a partir do registro npm.

Durante a primeira execução, ele perguntará se você concorda em baixar o pacote:

Output
Need to install the following packages:
  express-generator
Ok to proceed? (y)

Responda y e pressione ENTER. Agora você pode usar npx express-generator no lugar de express.

Em seguida, instale Nodemon, que recarregará automaticamente o aplicativo sempre que você fizer alterações. Um aplicativo Node.js precisa ser reiniciado sempre que alterações são feitas no código-fonte para que essas alterações tenham efeito, então Nodemon irá automaticamente observar por alterações e reiniciar o aplicativo. Como você quer ser capaz de usar nodemon como uma ferramenta de linha de comando, instale-o com o sinalizador -g:

  1. sudo npm install nodemon -g

Para finalizar a configuração do aplicativo, mova-se para o diretório do aplicativo e instale as dependências da seguinte forma:

  1. cd myApp
  2. npm install

Por padrão, as aplicações criadas com express-generator são executadas na porta 3000, então você precisa garantir que o firewall não bloqueie a porta.

Para abrir a porta 3000, execute o seguinte comando:

  1. sudo ufw allow 3000

Agora você tem tudo o que precisa para iniciar sua aplicação web. Para fazer isso, execute o seguinte comando:

  1. nodemon bin/www

Este comando inicia a aplicação na porta 3000. Você pode testar se está funcionando apontando seu navegador para http://seu_ip_do_servidor:3000. Você deverá ver algo assim:

Neste ponto, você pode iniciar uma segunda sessão SSH para o seu servidor para o restante deste tutorial, deixando a aplicação web que acabou de iniciar em execução na sessão original. Para o restante deste artigo, a sessão SSH inicial que está executando a aplicação será chamada de Sessão A. Todos os comandos na Sessão A aparecerão em um fundo azul escuro como este:

  1. nodemon bin/www

Você usará a nova sessão SSH para executar comandos e editar arquivos. Esta sessão será chamada de Sessão B. Todos os comandos na Sessão B aparecerão em um fundo azul claro como este:

  1. cd ~/myApp

A menos que seja indicado o contrário, você executará todos os comandos restantes na Sessão B.

Neste passo, você criou o aplicativo básico. Em seguida, você irá personalizá-lo.

Passo 2 — Personalizando as Variáveis de Log

Enquanto a aplicação padrão criada pelo express-generator é um bom começo, você precisa personalizar a aplicação para que ela chame o logger correto quando necessário.

express-generator inclui o middleware de registro HTTP Morgan que você usará para registrar dados sobre todas as solicitações HTTP. Como o Morgan suporta fluxos de saída, ele se combina bem com o suporte a fluxos incorporado no Winston, permitindo que você consolide logs de dados de solicitação HTTP com qualquer outra coisa que escolher para registrar com o Winston.

O boilerplate do express-generator usa a variável logger ao fazer referência ao pacote morgan. Como você usará morgan e winston, que são ambos pacotes de registro, pode ser confuso chamar qualquer um deles de logger. Para especificar qual variável você deseja, você pode alterar as declarações de variável editando o arquivo app.js.

Para abrir o app.js para edição, use nano ou seu editor de texto favorito:

  1. nano ~/myApp/app.js

Encontre a seguinte linha perto do topo do arquivo:

~/myApp/app.js
...
var logger = require('morgan');
...

Altere o nome da variável de logger para morgan:

~/myApp/app.js
...
var morgan = require('morgan');
...

Esta atualização especifica que a variável declarada morgan chamará o método require() vinculado ao logger de solicitação Morgan.

Você precisa encontrar onde mais a variável logger foi referenciada no arquivo e mudá-la para morgan. Você também precisará mudar o formato de log usado pelo pacote morgan para combined, que é o formato de log padrão do Apache e incluirá informações úteis nos logs, como o endereço IP remoto e o cabeçalho de solicitação HTTP do usuário-agente.

Para fazer isso, encontre a seguinte linha:

~/myApp/app.js
...
app.use(logger('dev'));
...

Atualize para o seguinte:

~/myApp/app.js
...
app.use(morgan('combined'));
...

Essas alterações ajudarão você a entender qual pacote de registro está sendo referenciado em qualquer momento após integrar a configuração do Winston.

Quando terminar, salve e feche o arquivo.

Agora que seu aplicativo está configurado, você pode começar a trabalhar com o Winston.

Passo 3 — Instalando e Configurando o Winston

Neste passo, você instalará e configurará o Winston. Você também explorará as opções de configuração disponíveis como parte do pacote winston e criará um registro para registrar informações em um arquivo e no console.

Instale o winston com o seguinte comando:

  1. cd ~/myApp
  2. npm install winston

É útil manter quaisquer arquivos de configuração de suporte ou utilitários para suas aplicações em um diretório especial. Crie uma pasta config que conterá a configuração do winston:

  1. mkdir ~/myApp/config

Em seguida, crie uma pasta que conterá seus arquivos de log:

  1. mkdir ~/myApp/logs

Finalmente, instale app-root-path:

  1. npm install app-root-path --save

O pacote app-root-path é útil ao especificar caminhos no Node.js. Embora este pacote não esteja diretamente relacionado ao Winston, ele é útil ao determinar caminhos para arquivos no Node.js. Você o usará para especificar a localização dos arquivos de log do Winston a partir da raiz do projeto e para evitar uma sintaxe de caminho relativo feia.

Agora que a configuração para lidar com o registro está no lugar, você pode definir suas configurações. Crie e abra ~/myApp/config/winston.js para edição:

  1. nano ~/myApp/config/winston.js

O arquivo winston.js conterá sua configuração do winston.

Em seguida, adicione o seguinte código para requerer os pacotes app-root-path e winston:

~/myApp/config/winston.js
const appRoot = require('app-root-path');
const winston = require('winston');

Com essas variáveis no lugar, você pode definir as configurações para seus transportes. Transportes são um conceito introduzido pelo Winston que se refere aos mecanismos de armazenamento/saída usados para os logs. O Winston vem com quatro transportes principais integrados: Console, Arquivo, HTTP e Stream.

Você se concentrará nos transportes de console e arquivo para este tutorial. O transporte de console registrará informações no console, e o transporte de arquivo registrará informações em um arquivo especificado. Cada definição de transporte pode conter configurações, como tamanho do arquivo, níveis de log e formato de log.

Aqui está um resumo rápido das configurações que você usará para cada transporte:

  • nível: nível das mensagens a serem registradas.
  • nome_do_arquivo: o arquivo a ser usado para escrever os dados de log.
  • lidarComExceções: capturar e registrar exceções não tratadas.
  • tamanhoMax: tamanho máximo do arquivo de log, em bytes, antes que um novo arquivo seja criado.
  • maxArquivos: limitar o número de arquivos criados quando o tamanho do arquivo de log é excedido.
  • formato: como a saída de log será formatada.

Níveis de log indicam a prioridade da mensagem e são denotados por um inteiro. O Winston usa os níveis de log do npm que são priorizados de 0 a 6 (mais alto para mais baixo):

  • 0: erro
  • 1: aviso
  • 2: informação
  • 3: http
  • 4: verbose
  • 5: depuração
  • 6: bobagem

Ao especificar um nível de log para um determinado transporte, qualquer coisa nesse nível ou superior será registrada. Por exemplo, ao definir um nível de informação, qualquer coisa no nível erro, aviso ou informação será registrada.

Os níveis de log são especificados ao chamar o registro, o que significa que você pode executar o seguinte comando para registrar um erro: registro.erro('mensagem de erro de teste').

Ainda no arquivo de configuração, adicione o seguinte código para definir as configurações de configuração para os transportes arquivo e console na configuração do winston:

~/myApp/config/winston.js
...
// define as configurações personalizadas para cada transporte (arquivo, console)
const options = {
  file: {
    level: "info",
    filename: `${appRoot}/logs/app.log`,
    handleExceptions: true,
    maxsize: 5242880, // 5MB
    maxFiles: 5,
    format: winston.format.combine(
      winston.format.timestamp(),
      winston.format.json()
    ),
  },
  console: {
    level: "debug",
    handleExceptions: true,
    format: winston.format.combine(
      winston.format.colorize(),
      winston.format.simple()
    ),
  },
};

Em seguida, adicione o seguinte código para instanciar um novo logger winston com os transportes de arquivo e console usando as propriedades definidas na variável options:

~/myApp/config/winston.js
...
// instanciar um novo Logger Winston com as configurações definidas acima
const logger = winston.createLogger({
  transports: [
    new winston.transports.File(options.file),
    new winston.transports.Console(options.console),
  ],
  exitOnError: false, // não sair em exceções tratadas
});

Por padrão, morgan escreve apenas no console, então você definirá uma função de fluxo que poderá pegar a saída gerada pelo morgan nos arquivos de log do winston. Você usará o nível info para capturar a saída por ambos os transportes (arquivo e console). Adicione o seguinte código ao arquivo de configuração:

~/myApp/config/winston.js
...
// criar um objeto de fluxo com uma função 'write' que será usada pelo `morgan`
logger.stream = {
  write: function(message, encoding) {
    // usar o nível de log 'info' para que a saída seja capturada por ambos os
    // transportes (arquivo e console)
    logger.info(message);
  },
};

Por fim, adicione o código abaixo para exportar o logger para que ele possa ser usado em outras partes da aplicação:

~/myApp/config/winston.js
...
module.exports = logger;

O arquivo de configuração winston completo agora terá essa aparência:

~/myApp/config/winston.js
const appRoot = require("app-root-path");
const winston = require("winston");

// definir as configurações personalizadas para cada transporte (arquivo, console)
const options = {
  file: {
    level: "info",
    filename: `${appRoot}/logs/app.log`,
    handleExceptions: true,
    maxsize: 5242880, // 5MB
    maxFiles: 5,
    format: winston.format.combine(
      winston.format.timestamp(),
      winston.format.json()
    ),
  },
  console: {
    level: "debug",
    handleExceptions: true,
    format: winston.format.combine(
      winston.format.colorize(),
      winston.format.simple()
    ),
  },
};

// instanciar um novo Registrador Winston com as configurações definidas acima
const logger = winston.createLogger({
  transports: [
    new winston.transports.File(options.file),
    new winston.transports.Console(options.console),
  ],
  exitOnError: false, // não sair em exceções tratadas
});

// criar um objeto de fluxo com uma função 'write' que será usada pelo `morgan`
logger.stream = {
  write: function (message, encoding) {
    // usar o nível de log 'info' para que a saída seja capturada por ambos
    // transportes (arquivo e console)
    logger.info(message);
  },
};

module.exports = logger;

Salvar e fechar o arquivo.

Agora você tem o registrador configurado, mas sua aplicação ainda não está ciente disso, ou como usá-lo, então você precisa integrar o registrador com a aplicação.

Passo 4 — Integrando o Winston com a Aplicação

Para fazer com que seu registrador funcione com a aplicação, você precisa fazer com que o express esteja ciente dele. Você viu no Passo 2 que sua configuração express está localizada em app.js, então você pode importar seu registrador para este arquivo.

Abra o arquivo para edição:

  1. nano ~/myApp/app.js

Adicione uma declaração de variável winston perto do topo do arquivo com os outros require declarações:

~/myApp/app.js
...
var winston = require('./config/winston');
...

O primeiro lugar onde você usará winston é com morgan. Ainda em app.js, encontre a seguinte linha:

~/myApp/app.js
...
app.use(morgan('combined'));
...

Atualize-a para incluir a opção stream:

~/myApp/app.js
...
app.use(morgan('combined', { stream: winston.stream }));
...

Aqui, você define a opção stream para a interface de fluxo que você criou como parte da configuração do winston.

Salve e feche o arquivo.

Neste passo, você configurou sua aplicação Express para funcionar com o Winston. Em seguida, você irá revisar os dados de log.

Passo 5 — Acessando Dados de Log e Registrando Mensagens de Log Personalizadas

Agora que a aplicação foi configurada, você está pronto para ver alguns dados de log. Neste passo, você irá revisar as entradas de log e atualizar suas configurações com uma mensagem de log personalizada de exemplo.

Se você recarregar a página no navegador da web, deverá ver algo semelhante à seguinte saída no console da Sessão SSH A:

Output
[nodemon] restarting due to changes... [nodemon] starting `node bin/www` info: ::1 - - [25/Apr/2022:18:10:55 +0000] "GET / HTTP/1.1" 200 170 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36" info: ::1 - - [25/Apr/2022:18:10:55 +0000] "GET /stylesheets/style.css HTTP/1.1" 304 - "http://localhost:3000/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36"

Aqui estão duas entradas de log: a primeira para a solicitação à página HTML; a segunda para a folha de estilos associada. Como cada transporte está configurado para lidar com dados de log de nível info, você também deve ver informações semelhantes no transporte de arquivo localizado em ~/myApp/logs/app.log.

Para visualizar o conteúdo do arquivo de log, execute o seguinte comando:

  1. tail ~/myApp/logs/app.log

tail irá mostrar as últimas partes do arquivo no seu terminal.

Você deverá ver algo semelhante ao seguinte:

{"level":"info","message":"::1 - - [25/Apr/2022:18:10:55 +0000] \"GET / HTTP/1.1\" 304 - \"-\" \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36\"\n","timestamp":"2022-04-25T18:10:55.573Z"}
{"level":"info","message":"::1 - - [25/Apr/2022:18:10:55 +0000] \"GET /stylesheets/style.css HTTP/1.1\" 304 - \"http://localhost:3000/\" \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36\"\n","timestamp":"2022-04-25T18:10:55.588Z"}

A saída no arquivo de transporte será escrita como um objeto JSON, pois você utilizou winston.format.json() na opção format para a configuração do transporte de arquivo. Você pode aprender mais sobre JSON em Uma Introdução ao JSON.

Até agora, seu logger está apenas registrando solicitações HTTP e dados relacionados. Essas informações são essenciais para ter em seus logs.

No futuro, você pode querer registrar mensagens de log personalizadas, como para registrar erros ou o desempenho de consultas ao banco de dados. Como exemplo, você chamará o logger a partir da rota de manipulador de erros. Por padrão, o pacote express-generator já inclui uma rota de manipulador de erros 404 e 500, então você trabalhará com isso.

Abra o arquivo ~/myApp/app.js:

  1. nano ~/myApp/app.js

Encontre o bloco de código no final do arquivo que se parece com isto:

~/myApp/app.js
...
// manipulador de erros
app.use(function(err, req, res, next) {
  // definir locais, fornecendo erro apenas em desenvolvimento
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // renderizar a página de erro
  res.status(err.status || 500);
  res.render('error');
});
...

Esta seção é a rota de manipulação de erros final que, em última análise, enviará uma resposta de erro de volta para o cliente. Como todos os erros do lado do servidor passarão por esta rota, é um bom lugar para incluir o logger winston.

Porque agora você está lidando com erros, você quer usar o nível de log error. Ambos os transportes estão configurados para registrar mensagens de nível error, então você deve ver a saída nos logs do console e do arquivo.

Você pode incluir qualquer coisa que desejar no log, incluindo informações como:

  • err.status: O código de status de erro HTTP. Se não houver um presente, defina-o como 500.
  • err.message: Detalhes do erro.
  • req.originalUrl: O URL solicitado.
  • req.path: A parte do caminho do URL da solicitação.
  • req.method: Método HTTP da solicitação (GET, POST, PUT, etc.).
  • req.ip: Endereço IP remoto da solicitação.

Atualize a rota do manipulador de erros para incluir o log winston:

~/myApp/app.js
...
// manipulador de erros
app.use(function(err, req, res, next) {
  // definir locais, fornecendo apenas erro no desenvolvimento
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // incluir registro winston
  winston.error(
    `${err.status || 500} - ${err.message} - ${req.originalUrl} - ${req.method} - ${req.ip}`
  );

  // renderizar a página de erro
  res.status(err.status || 500);
  res.render('error');
});
...

Salve e feche o arquivo.

Para testar esse processo, tente acessar uma página inexistente em seu projeto. Acessar uma página inexistente lançará um erro 404. Em seu navegador da web, tente carregar o seguinte URL: http://seu_endereço_ip_do_servidor:3000/foo. Graças ao modelo de inicialização criado pelo express-generator, a aplicação está configurada para responder a esse erro.

Seu navegador exibirá uma mensagem de erro como esta:

Quando você olha para o console na Sessão SSH A, deve haver uma entrada de log para o erro. Graças ao formato colorize aplicado, deve ser fácil de identificar:

Output
[nodemon] starting `node bin/www` error: 404 - Not Found - /foo - GET - ::1 info: ::1 - - [25/Apr/2022:18:08:33 +0000] "GET /foo HTTP/1.1" 404 982 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36" info: ::1 - - [25/Apr/2022:18:08:33 +0000] "GET /stylesheets/style.css HTTP/1.1" 304 - "http://localhost:3000/foo" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36"

Quanto ao registrador de arquivos, executar o comando tail novamente deve mostrar os novos registros de log:

  1. tail ~/myApp/logs/app.log

Você verá uma mensagem como a seguinte:

{"level":"error","message":"404 - Not Found - /foo - GET - ::1","timestamp":"2022-04-25T18:08:33.508Z"}

A mensagem de erro inclui todos os dados que você especificamente instruiu o winston a registrar como parte do manipulador de erros. Essas informações incluirão o status do erro (404 – Não Encontrado), a URL solicitada (localhost/foo), o método de solicitação (GET), o endereço IP que fez a solicitação e o carimbo de data/hora quando a solicitação foi feita.

Conclusão

Neste tutorial, você construiu uma aplicação web Node.js simples e integrou uma solução de registro Winston que funcionará como uma ferramenta eficaz para fornecer insights sobre o desempenho da aplicação.

Você pode fazer muito mais para construir soluções de registro robustas para suas aplicações, especialmente à medida que suas necessidades se tornam mais complexas. Para saber mais sobre os transportes do Winston, consulte a Documentação dos Transportes do Winston. Para criar seus próprios transportes, consulte Adicionando Transportes Personalizados. Para criar um endpoint HTTP para uso com o transporte HTTP principal, consulte winstond. Para usar o Winston como uma ferramenta de perfilamento, consulte Perfilamento.

Source:
https://www.digitalocean.com/community/tutorials/how-to-use-winston-to-log-node-js-applications-on-ubuntu-20-04