A seguir, é apresentado um resumo e uma curadoria de várias fontes diferentes sobre este tópico, incluindo exemplos de código e citações de postagens de blog selecionadas. A lista completa de práticas recomendadas pode ser encontrada aqui
Práticas recomendadas para o tratamento de erros do Node.JS
Número1: use promessas para tratamento de erros assíncronos
TL; DR: o tratamento de erros assíncronos no estilo de retorno de chamada é provavelmente o caminho mais rápido para o inferno (também conhecido como pirâmide do destino). O melhor presente que você pode dar ao seu código é usar uma biblioteca de promessas respeitável, que fornece uma sintaxe de código muito compacta e familiar, como try-catch
Caso contrário: o estilo de retorno de chamada Node.JS, função (err, resposta), é uma maneira promissora de código não-sustentável, devido à mistura de tratamento de erros com código casual, aninhamento excessivo e padrões de codificação desajeitados
Exemplo de código - bom
doWork()
.then(doWork)
.then(doError)
.then(doWork)
.catch(errorHandler)
.then(verify);
exemplo de código anti-padrão - tratamento de erro no estilo de retorno de chamada
getData(someParameter, function(err, result){
if(err != null)
//do something like calling the given callback function and pass the error
getMoreData(a, function(err, result){
if(err != null)
//do something like calling the given callback function and pass the error
getMoreData(b, function(c){
getMoreData(d, function(e){
...
});
});
});
});
});
Citação do blog: "Temos um problema com promessas"
(no blog pouchdb, classificado em 11 pelas palavras-chave "Promessas do nó")
"... E, de fato, os retornos de chamada fazem algo ainda mais sinistro: eles nos privam da pilha, o que geralmente é um dado adquirido nas linguagens de programação. Escrever código sem uma pilha é como dirigir um carro sem um pedal de freio: você não perceba o quanto você precisa disso, até chegar lá e ele não está lá.O objetivo das promessas é devolver-nos os fundamentos da linguagem que perdemos quando assíncronas: retorno, arremesso e pilha. precisa saber como usar as promessas corretamente para tirar proveito delas " .
Número2: use apenas o objeto de erro interno
TL; DR: É bastante comum ver código que gera erros como string ou como um tipo personalizado - isso complica a lógica de manipulação de erros e a interoperabilidade entre os módulos. Se você rejeita uma promessa, lança uma exceção ou emite um erro - o uso do objeto Erro interno do Node.JS aumenta a uniformidade e evita a perda de informações de erro.
Caso contrário: Ao executar algum módulo, a incerteza de que tipo de erro ocorre em retorno - dificulta muito o raciocínio e a manipulação da exceção que se aproxima. Mesmo vale a pena, usar tipos personalizados para descrever erros pode levar à perda de informações críticas sobre erros, como o rastreamento de pilha!
Exemplo de código - fazendo certo
//throwing an Error from typical function, whether sync or async
if(!productToAdd)
throw new Error("How can I add new product when no value provided?");
//'throwing' an Error from EventEmitter
const myEmitter = new MyEmitter();
myEmitter.emit('error', new Error('whoops!'));
//'throwing' an Error from a Promise
return new promise(function (resolve, reject) {
DAL.getProduct(productToAdd.id).then((existingProduct) =>{
if(existingProduct != null)
return reject(new Error("Why fooling us and trying to add an existing product?"));
exemplo de código anti-padrão
//throwing a String lacks any stack trace information and other important properties
if(!productToAdd)
throw ("How can I add new product when no value provided?");
Citação do blog: "Uma string não é um erro"
(no blog devthought, classificou 6 para as palavras-chave “Node.JS error object”)
"... passar uma string em vez de um erro resulta em menor interoperabilidade entre os módulos. Ele quebra contratos com APIs que podem estar executando instâncias de verificações de erros ou que desejam saber mais sobre o erro . Objetos de erro, como veremos, têm muito propriedades interessantes nos modernos mecanismos JavaScript, além de conter a mensagem passada ao construtor .. "
Número 3: Distinguir erros operacionais x programadores
TL; DR: erros de operações (por exemplo, a API recebeu uma entrada inválida) referem-se a casos conhecidos em que o impacto do erro é totalmente compreendido e pode ser tratado com cuidado. Por outro lado, o erro do programador (por exemplo, tentar ler variáveis indefinidas) refere-se a falhas de código desconhecidas que determinam reiniciar o aplicativo normalmente.
Caso contrário: você sempre poderá reiniciar o aplicativo quando um erro aparecer, mas por que decepcionar ~ 5000 usuários on-line devido a um erro pequeno e previsto (erro operacional)? o contrário também não é o ideal - manter o aplicativo ativo quando ocorreu um problema desconhecido (erro do programador) pode levar a um comportamento imprevisível. A diferenciação dos dois permite agir com tato e aplicar uma abordagem equilibrada com base no contexto fornecido
Exemplo de código - fazendo certo
//throwing an Error from typical function, whether sync or async
if(!productToAdd)
throw new Error("How can I add new product when no value provided?");
//'throwing' an Error from EventEmitter
const myEmitter = new MyEmitter();
myEmitter.emit('error', new Error('whoops!'));
//'throwing' an Error from a Promise
return new promise(function (resolve, reject) {
DAL.getProduct(productToAdd.id).then((existingProduct) =>{
if(existingProduct != null)
return reject(new Error("Why fooling us and trying to add an existing product?"));
exemplo de código - marcando um erro como operacional (confiável)
//marking an error object as operational
var myError = new Error("How can I add new product when no value provided?");
myError.isOperational = true;
//or if you're using some centralized error factory (see other examples at the bullet "Use only the built-in Error object")
function appError(commonType, description, isOperational) {
Error.call(this);
Error.captureStackTrace(this);
this.commonType = commonType;
this.description = description;
this.isOperational = isOperational;
};
throw new appError(errorManagement.commonErrors.InvalidInput, "Describe here what happened", true);
//error handling code within middleware
process.on('uncaughtException', function(error) {
if(!error.isOperational)
process.exit(1);
});
Citação do blog : "Caso contrário, você corre o risco do estado" (No blog depurável, classifique 3 para as palavras-chave "Exceção não capturada do Node.JS")
" ... Pela própria natureza de como o throw funciona no JavaScript, quase nunca existe uma maneira de" pegar de onde você parou "com segurança, sem vazar referências ou criar outro tipo de estado frágil indefinido. A maneira mais segura de responder a um erro gerado é encerrar o processo.Claro , em um servidor Web normal, você pode ter muitas conexões abertas, e não é razoável encerrá-las abruptamente porque um erro foi acionado por outra pessoa. envie uma resposta de erro à solicitação que acionou o erro, deixando os outros concluírem no horário normal e pare de ouvir novas solicitações nesse trabalhador "
Número4: manipular erros centralmente, através, mas não no middleware
TL; DR: A lógica de manipulação de erros, como correio para administrador e log, deve ser encapsulada em um objeto dedicado e centralizado que todos os terminais (por exemplo, middleware Express, tarefas cron, teste de unidade) chamam quando ocorre um erro.
Caso contrário: não manipular erros em um único local levará à duplicação de código e provavelmente a erros tratados incorretamente
Exemplo de código - um fluxo de erro típico
//DAL layer, we don't handle errors here
DB.addDocument(newCustomer, (error, result) => {
if (error)
throw new Error("Great error explanation comes here", other useful parameters)
});
//API route code, we catch both sync and async errors and forward to the middleware
try {
customerService.addNew(req.body).then(function (result) {
res.status(200).json(result);
}).catch((error) => {
next(error)
});
}
catch (error) {
next(error);
}
//Error handling middleware, we delegate the handling to the centrzlied error handler
app.use(function (err, req, res, next) {
errorHandler.handleError(err).then((isOperationalError) => {
if (!isOperationalError)
next(err);
});
});
Citação do blog: "Às vezes, níveis mais baixos não podem fazer nada de útil, exceto propagar o erro ao chamador" (no blog Joyent, classificou 1 para as palavras-chave "Tratamento de erros do Node.JS")
"... Você pode acabar lidando com o mesmo erro em vários níveis da pilha. Isso acontece quando os níveis mais baixos não podem fazer nada de útil, exceto propagar o erro ao chamador, que propaga o erro ao chamador e assim por diante. Frequentemente, somente o chamador de nível superior sabe qual é a resposta apropriada, seja para repetir a operação, relatar um erro ao usuário ou outra coisa, mas isso não significa que você deve tentar relatar todos os erros em um único nível superior retorno de chamada, porque esse retorno de chamada em si não pode saber em que contexto ocorreu o erro "
Número5: documentar erros da API usando o Swagger
TL; DR: informe aos chamadores da API quais erros podem resultar em retorno, para que eles possam lidar com eles com cuidado sem travar. Isso geralmente é feito com estruturas de documentação da API REST como o Swagger
Caso contrário: um cliente de API pode decidir travar e reiniciar apenas porque recebeu de volta um erro que não conseguia entender. Nota: o chamador da sua API pode ser você (muito típico em um ambiente de microsserviços)
Citação do blog: "Você precisa informar aos chamadores quais erros podem ocorrer" (no blog Joyent, classificado em 1 pelas palavras-chave “Registro no Node.JS”)
... Falamos sobre como lidar com erros, mas quando você está escrevendo uma nova função, como você entrega erros para o código que chamou sua função? … Se você não sabe quais erros podem acontecer ou não sabe o que eles significam, seu programa não pode estar correto, exceto por acidente. Portanto, se você estiver escrevendo uma nova função, precisará informar aos chamadores quais erros podem acontecer e o que eles significam
Número6: Encerre o processo normalmente quando um estranho chegar à cidade
TL; DR: Quando ocorre um erro desconhecido (um erro de desenvolvedor, consulte a prática recomendada número 3) - há incerteza sobre a integridade do aplicativo. Uma prática comum sugere reiniciar o processo com cuidado usando uma ferramenta 'restarter' como Forever e PM2
Caso contrário: Quando uma exceção desconhecida é capturada, algum objeto pode estar com um estado defeituoso (por exemplo, um emissor de evento que é usado globalmente e não dispara mais eventos devido a alguma falha interna) e todas as solicitações futuras podem falhar ou se comportar loucamente
Exemplo de código - decidindo se deve travar
//deciding whether to crash when an uncaught exception arrives
//Assuming developers mark known operational errors with error.isOperational=true, read best practice #3
process.on('uncaughtException', function(error) {
errorManagement.handler.handleError(error);
if(!errorManagement.handler.isTrustedError(error))
process.exit(1)
});
//centralized error handler encapsulates error-handling related logic
function errorHandler(){
this.handleError = function (error) {
return logger.logError(err).then(sendMailToAdminIfCritical).then(saveInOpsQueueIfCritical).then(determineIfOperationalError);
}
this.isTrustedError = function(error)
{
return error.isOperational;
}
Citação do blog: "Existem três pensamentos sobre o tratamento de erros" (do blog jsrecipes)
… Existem basicamente três escolas de pensamento sobre o tratamento de erros: 1. Deixe o aplicativo travar e reinicie-o. 2. Manipule todos os erros possíveis e nunca trava. 3. Abordagem equilibrada entre os dois
Número7: Use um criador de logs maduro para aumentar a visibilidade dos erros
TL; DR: Um conjunto de ferramentas de registro maduras, como Winston, Bunyan ou Log4J, acelerará a descoberta e o entendimento de erros. Então esqueça o console.log.
Caso contrário: percorrer o console.logs ou manualmente o arquivo de texto confuso, sem consultar as ferramentas ou um visualizador de logs decente, poderá mantê-lo ocupado no trabalho até tarde
Exemplo de código - Winston logger em ação
//your centralized logger object
var logger = new winston.Logger({
level: 'info',
transports: [
new (winston.transports.Console)(),
new (winston.transports.File)({ filename: 'somefile.log' })
]
});
//custom code somewhere using the logger
logger.log('info', 'Test Log Message with some parameter %s', 'some parameter', { anything: 'This is metadata' });
Citação do blog: "Vamos identificar alguns requisitos (para um criador de logs):" (Do blog forte do blog)
... Vamos identificar alguns requisitos (para um registrador): 1. Carimbo de data e hora em cada linha de registro. Essa é bastante autoexplicativa - você deve saber quando cada entrada de log ocorreu. 2. O formato de registro deve ser facilmente digerível tanto por humanos quanto por máquinas. 3. Permite vários fluxos de destino configuráveis. Por exemplo, você pode estar gravando logs de rastreamento em um arquivo, mas quando um erro for encontrado, escreva no mesmo arquivo, depois no arquivo de erro e envie um email ao mesmo tempo ...
Número8: Descubra erros e tempo de inatividade usando produtos APM
TL; DR: os produtos de monitoramento e desempenho (também conhecidos como APM) medem proativamente sua base de código ou API para que possam destacar automaticamente magicamente erros, falhas e partes lentas que estavam faltando
Caso contrário: você pode se esforçar bastante para medir o desempenho e os tempos de inatividade da API, provavelmente nunca saberá quais são as partes de código mais lentas no cenário do mundo real e como isso afeta o UX
Citação do blog: "segmentos de produtos APM" (do blog Yoni Goldberg)
"… Os produtos APM constituem três segmentos principais: 1. Monitoramento de site ou API - serviços externos que monitoram constantemente o tempo de atividade e o desempenho via solicitações HTTP. Podem ser configurados em alguns minutos. A seguir, são apresentados alguns candidatos selecionados: Pingdom, Uptime Robot e New Relic
2 Instrumentação de código - família de produtos que exige a incorporação de um agente no aplicativo para beneficiar a detecção lenta de código, estatísticas de exceções, monitoramento de desempenho e muito mais A seguir, são apresentados alguns candidatos selecionados: New Relic, App Dynamics
3. Painel de inteligência operacional -essas linhas de produtos estão focadas em facilitar a equipe de operações com métricas e conteúdo com curadoria que ajuda a manter-se atualizado sobre o desempenho do aplicativo. Isso geralmente envolve a agregação de várias fontes de informações (logs de aplicativos, logs de banco de dados, log de servidores, etc.) e trabalho inicial de design de painéis. A seguir estão alguns candidatos selecionados: Datadog, Splunk "
A descrição acima é uma versão reduzida - veja aqui mais práticas recomendadas e exemplos