Acabo de empezar a probar node.js hace unos días. Me di cuenta de que el Nodo se termina cada vez que tengo una excepción no controlada en mi programa. Esto es diferente al contenedor de servidor normal al que he estado expuesto, donde solo el subproceso de trabajo muere cuando se producen excepciones no controladas y el contenedor aún podría recibir la solicitud. Esto plantea algunas preguntas:
- ¿Es
process.on('uncaughtException')
la única forma efectiva de protegerse contra ella? - ¿
process.on('uncaughtException')
Capturará también la excepción no controlada durante la ejecución de procesos asincrónicos? - ¿Hay un módulo que ya está construido (como enviar un correo electrónico o escribir en un archivo) que podría aprovechar en el caso de excepciones no detectadas?
Agradecería cualquier puntero / artículo que me mostrara las mejores prácticas comunes para manejar excepciones no capturadas en node.js
try .. catch
, y verifique que esto también se haga para todas sus bibliotecassetTimeout
osetInterval
algo por el estilo enterrado en algún lugar profundo que no pueda ser captado por su código.Respuestas:
Actualización: Joyent ahora tiene su propia guía . La siguiente información es más un resumen:
Errores de "lanzamiento" de forma segura
Idealmente, nos gustaría evitar los errores no detectados tanto como sea posible, como tal, en lugar de arrojar literalmente el error, podemos "arrojar" el error de forma segura utilizando uno de los siguientes métodos, dependiendo de nuestra arquitectura de código:
Para el código sincrónico, si ocurre un error, devuelva el error:
Para el código basado en la devolución de llamada (es decir, asíncrono), el primer argumento de la devolución de llamada es
err
, si ocurre un errorerr
es el error, si no ocurre un error, entonceserr
esnull
. Cualquier otro argumento sigue elerr
argumento:Para el código lleno de eventos , donde el error puede ocurrir en cualquier lugar, en lugar de lanzar el error, active el
error
evento en su lugar :Errores de "captura" seguros
Sin embargo, a veces todavía puede haber código que arroje un error en algún lugar que pueda conducir a una excepción no detectada y a un posible bloqueo de nuestra aplicación si no la detectamos de manera segura. Dependiendo de nuestra arquitectura de código, podemos usar uno de los siguientes métodos para capturarlo:
Cuando sabemos dónde se produce el error, podemos ajustar esa sección en un dominio node.js
Si sabemos dónde está ocurriendo el error es un código síncrono, y por alguna razón no podemos usar dominios (quizás una versión antigua del nodo), podemos usar la declaración try catch:
Sin embargo, tenga cuidado de no usar
try...catch
en código asíncrono, ya que no se detectará un error asincrónicamente lanzado:Si desea trabajar
try..catch
junto con el código asincrónico, cuando ejecute el Nodo 7.4 o superior, puede usarlo deasync/await
forma nativa para escribir sus funciones asincrónicas.Otra cosa con la que debe tener cuidado
try...catch
es el riesgo de incluir su devolución de llamada de finalización dentro de latry
declaración de la siguiente manera:Este problema es muy fácil de hacer ya que su código se vuelve más complejo. Como tal, es mejor usar dominios o devolver errores para evitar (1) excepciones no detectadas en el código asincrónico (2) la ejecución de captura de prueba de captura que no desea. En los lenguajes que permiten un subproceso adecuado en lugar del estilo de máquina de eventos asíncrono de JavaScript, esto no es un problema.
Finalmente, en el caso de que ocurra un error no detectado en un lugar que no estaba envuelto en un dominio o una declaración de captura de prueba, podemos hacer que nuestra aplicación no se bloquee al usar el
uncaughtException
escucha (sin embargo, hacerlo puede poner la aplicación en un estado desconocido ):fuente
try catch
? Como me encantaría respaldar eso con evidencia. También se corrigió el ejemplo de sincronización.A continuación se presenta un resumen y una curación de muchas fuentes diferentes sobre este tema, incluidos ejemplos de código y citas de publicaciones de blog seleccionadas. La lista completa de las mejores prácticas se puede encontrar aquí.
Mejores prácticas de manejo de errores Node.JS
Número1: Use promesas para el manejo de errores asíncronos
TL; DR: manejar los errores asíncronos en el estilo de devolución de llamada es probablemente la forma más rápida de ir al infierno (también conocida como la pirámide de la fatalidad). El mejor regalo que puede darle a su código es usar una biblioteca de promesas confiable que proporcione una sintaxis de código muy compacta y familiar como try-catch
De lo contrario: el estilo de devolución de llamada Node.JS, función (err, respuesta), es una forma prometedora de código no mantenible debido a la combinación de manejo de errores con código casual, anidamiento excesivo y patrones de codificación incómodos
Ejemplo de código: bueno
ejemplo de código anti patrón - manejo de errores de estilo de devolución de llamada
Cita del blog: "Tenemos un problema con las promesas" (del blog pouchdb, en el puesto 11 de las palabras clave "Promesas de nodo")
Número2: use solo el objeto de error incorporado
TL; DR: es bastante común ver código que arroja errores como una cadena o como un tipo personalizado; esto complica la lógica de manejo de errores y la interoperabilidad entre módulos. Ya sea que rechace una promesa, arroje una excepción o emita un error: el uso del objeto de error incorporado Node.JS aumenta la uniformidad y evita la pérdida de información de error
De lo contrario: al ejecutar algún módulo, no estar seguro de qué tipo de errores se producen a cambio, hace que sea mucho más difícil razonar sobre la próxima excepción y manejarla. ¡Incluso vale la pena, el uso de tipos personalizados para describir errores puede conducir a la pérdida de información de errores críticos como el seguimiento de la pila!
Ejemplo de código: hacerlo bien
ejemplo de código anti patrón
Cita del blog: "Una cadena no es un error" (De la reflexión del blog, clasificó 6 para las palabras clave "Node.JS error object")
Número 3: Distinguir errores operacionales vs errores de programador
TL; DR: Los errores de operaciones (p. Ej., La API recibió una entrada no válida) se refieren a casos conocidos en los que el impacto del error se entiende completamente y se puede manejar cuidadosamente. Por otro lado, el error del programador (por ejemplo, al intentar leer una variable indefinida) se refiere a fallas de código desconocidas que dictan reiniciar la aplicación con gracia
De lo contrario: siempre puede reiniciar la aplicación cuando aparece un error, pero ¿por qué decepcionar a ~ 5000 usuarios en línea debido a un error menor y pronosticado (error operativo)? lo contrario tampoco es ideal: mantener la aplicación activa cuando se produce un problema desconocido (error del programador) puede generar un comportamiento imprevisto. Diferenciar los dos permite actuar con tacto y aplicar un enfoque equilibrado basado en el contexto dado
Ejemplo de código: hacerlo bien
ejemplo de código: marcar un error como operativo (confiable)
Cita del blog : "De lo contrario, arriesga el estado" (del blog debugable, clasificado 3 para las palabras clave "Node.JS excepción no detectada")
Número 4: maneja los errores de forma centralizada, a través del middleware, pero no dentro de este
TL; DR: la lógica de manejo de errores, como el correo al administrador y el registro, debe encapsularse en un objeto dedicado y centralizado que todos los puntos finales (por ejemplo, middleware Express, trabajos cron, pruebas unitarias) llaman cuando entra un error.
De lo contrario: no manejar los errores en un solo lugar conducirá a la duplicación de código y probablemente a errores que se manejen de manera incorrecta
Ejemplo de código: un flujo de error típico
Cita del blog: "A veces, los niveles más bajos no pueden hacer nada útil excepto propagar el error a su interlocutor" (Del blog Joyent, clasificado 1 para las palabras clave "Manejo de errores Node.JS")
Número5: Errores de la API de documentos usando Swagger
TL; DR: Informe a los llamadores de la API qué errores pueden aparecer a cambio para que puedan manejarlos cuidadosamente sin fallar. Esto generalmente se hace con marcos de documentación de API REST como Swagger
De lo contrario: un cliente API podría decidir bloquearse y reiniciarse solo porque recibió un error que no pudo entender. Nota: la persona que llama de su API puede ser usted (muy típico en un entorno de microservicios)
Cita del blog: "Debe informar a las personas que llaman qué errores pueden ocurrir" (del blog Joyent, en el puesto 1 por las palabras clave "Node.JS logging")
Número 6: cierra el proceso con gracia cuando un extraño llega a la ciudad
TL; DR: cuando se produce un error desconocido (un error del desarrollador, consulte la práctica recomendada número 3): existe incertidumbre acerca de la salud de la aplicación. Una práctica común sugiere reiniciar el proceso cuidadosamente usando una herramienta de 'reinicio' como Forever y PM2
De lo contrario: cuando se detecta una excepción desconocida, algún objeto puede estar en un estado defectuoso (por ejemplo, un emisor de eventos que se usa globalmente y ya no dispara eventos debido a alguna falla interna) y todas las solicitudes futuras pueden fallar o comportarse locamente
Ejemplo de código: decidir si se bloquea
Cita del blog: "Hay tres escuelas de pensamiento sobre el manejo de errores" (Del blog jsrecipes)
Número 7: use un registrador maduro para aumentar la visibilidad de los errores
TL; DR: un conjunto de herramientas de registro maduras como Winston, Bunyan o Log4J, acelerará el descubrimiento y la comprensión de errores. Así que olvídate de console.log.
De lo contrario: hojear mediante console.logs o manualmente a través de un archivo de texto desordenado sin consultar herramientas o un visor de registro decente podría mantenerlo ocupado en el trabajo hasta tarde
Ejemplo de código: Winston logger en acción
Cita del blog: "Identifiquemos algunos requisitos (para un registrador):" (Del blog strongblog)
Número 8: descubra errores y tiempo de inactividad utilizando productos APM
TL; DR: los productos de monitoreo y rendimiento (también conocido como APM) evalúan de manera proactiva su base de código o API para que puedan resaltar automáticamente los errores, bloqueos y piezas lentas que faltaba
De lo contrario: es posible que gaste un gran esfuerzo en medir el rendimiento de la API y los tiempos de inactividad, probablemente nunca se dará cuenta de cuáles son sus partes de código más lentas en el escenario del mundo real y cómo afectan esto a la experiencia de usuario
Cita del blog: "Segmentos de productos APM" (del blog Yoni Goldberg)
Lo anterior es una versión abreviada: vea aquí más prácticas recomendadas y ejemplos
fuente
Puede detectar excepciones no detectadas, pero es de uso limitado. Ver http://debuggable.com/posts/node-js-dealing-with-uncaught-exceptions:4c933d54-1428-443c-928d-4e1ecbdd56cb
monit
,forever
oupstart
se puede usar para reiniciar el proceso de nodo cuando se bloquea. Lo mejor que puede esperar es un apagado correcto (por ejemplo, guarde todos los datos en memoria en un controlador de excepciones no detectado).fuente
Error
hace que el valor de retorno sea polimórfico, lo que confunde la semántica de la función innecesariamente. Además, el buceo por 0 ya se maneja de JavaScript dandoInfinity
,-Infinity
oNaN
, los valores dondetypeof === 'number'
. Se pueden verificar con!isFinite(value)
. En general, recomendaría nunca devolver un error de una función. Mejor en términos de legibilidad de código y mantenimiento para arrojar o devolver un valor no polimórfico especial con semántica consistente.Los dominios de nodejs son la forma más actualizada de manejar errores en nodejs. Los dominios pueden capturar tanto errores / otros eventos como objetos arrojados tradicionalmente. Los dominios también proporcionan funcionalidad para manejar devoluciones de llamada con un error pasado como primer argumento a través del método de intercepción.
Al igual que con el manejo normal de errores de estilo try / catch, generalmente es mejor arrojar errores cuando ocurren y bloquear las áreas donde desea aislar los errores para que no afecten al resto del código. La forma de "bloquear" estas áreas es llamar a domain.run con una función como un bloque de código aislado.
En el código síncrono, lo anterior es suficiente: cuando ocurre un error, puede dejarlo pasar, o lo atrapa y lo maneja allí, revocando cualquier dato que necesite revertir.
Cuando el error ocurre en una devolución de llamada asincrónica, debe poder manejar completamente la reversión de datos (estado compartido, datos externos como bases de datos, etc.). O bien, debe configurar algo para indicar que ha ocurrido una excepción: donde quiera que se preocupe por ese indicador, debe esperar a que se complete la devolución de llamada.
Parte del código anterior es feo, pero puede crear patrones para que sea más bonito, por ejemplo:
ACTUALIZACIÓN (2013-09):
Arriba, uso un futuro que implica semántica de fibras , que le permite esperar futuros en línea. En realidad, esto le permite utilizar los bloques tradicionales try-catch para todo , lo que creo que es la mejor manera de hacerlo. Sin embargo, no siempre puede hacer esto (es decir, en el navegador) ...
También hay futuros que no requieren semántica de fibras (que luego funcionan con JavaScript normal y navegable). Estos pueden llamarse futuros, promesas o diferidos (de aquí en adelante me referiré a futuros). Las bibliotecas de futuros JavaScript simples permiten que los errores se propaguen entre futuros. Solo algunas de estas bibliotecas permiten manejar correctamente cualquier futuro lanzado, así que tenga cuidado.
Un ejemplo:
Esto imita un try-catch normal, a pesar de que las piezas son asíncronas. Imprimiría:
Tenga en cuenta que no imprime '3' porque se produjo una excepción que interrumpe ese flujo.
Echa un vistazo a las promesas de bluebird:
Tenga en cuenta que no he encontrado muchas otras bibliotecas que no sean estas que manejan correctamente las excepciones lanzadas. El diferido de jQuery, por ejemplo, no lo hace: el manejador de "falla" nunca obtendría la excepción arrojada como un manejador de "entonces", que en mi opinión es un factor decisivo.
fuente
Escribí sobre esto recientemente en http://snmaynard.com/2012/12/21/node-error-handling/ . Una nueva característica del nodo en la versión 0.8 son los dominios y le permite combinar todas las formas de manejo de errores en una forma de administración más fácil. Puedes leer sobre ellos en mi publicación.
También puede usar algo como Bugsnag para rastrear sus excepciones no detectadas y recibir una notificación por correo electrónico, sala de chat o hacer que se cree un ticket para una excepción no detectada (soy el cofundador de Bugsnag).
fuente
Solo me gustaría agregar que la biblioteca Step.js lo ayuda a manejar las excepciones pasándolas siempre a la función del siguiente paso. Por lo tanto, puede tener como último paso una función que verifique cualquier error en cualquiera de los pasos anteriores. Este enfoque puede simplificar enormemente su manejo de errores.
A continuación hay una cita de la página de Github:
Además, puede usar Step para controlar la ejecución de scripts para tener una sección de limpieza como último paso. Por ejemplo, si desea escribir un script de compilación en Node e informar cuánto tardó en escribir, el último paso puede hacerlo (en lugar de tratar de desenterrar la última devolución de llamada).
fuente
Una instancia en la que puede ser apropiado usar un try-catch es cuando se usa un bucle forEach. Es síncrono, pero al mismo tiempo no puede usar una declaración return en el ámbito interno. En su lugar, se puede utilizar un enfoque de prueba y captura para devolver un objeto Error en el ámbito apropiado. Considerar:
Es una combinación de los enfoques descritos por @balupton anteriormente.
fuente
Después de leer esta publicación hace algún tiempo, me preguntaba si era seguro usar dominios para el manejo de excepciones en un nivel de API / función. Quería usarlos para simplificar el código de manejo de excepciones en cada función asincrónica que escribí. Mi preocupación era que el uso de un nuevo dominio para cada función introduciría una sobrecarga significativa. Mi tarea parece indicar que hay una sobrecarga mínima y que el rendimiento es realmente mejor con dominios que con try catch en algunas situaciones.
http://www.lighthouselogic.com/#/using-a-new-domain-for-each-async-function-in-node/
fuente
Aquí se ha discutido muy bien los errores de captura, pero vale la pena recordar cerrar la sesión en algún lugar para poder verlos y arreglarlos.
Bunyan es un marco de registro popular para NodeJS: admite escribir en varios lugares de salida diferentes, lo que lo hace útil para la depuración local, siempre que evite console.log. En el controlador de errores de su dominio, puede escupir el error a un archivo de registro.
Esto puede llevar mucho tiempo si tiene muchos errores y / o servidores para verificar, por lo que podría valer la pena buscar una herramienta como Raygun (descargo de responsabilidad, trabajo en Raygun) para agrupar los errores, o usarlos juntos. Si decidió usar Raygun como herramienta, también es bastante fácil de configurar
Cruzada con el uso de una herramienta como PM2 o para siempre, su aplicación debería poder bloquearse, cerrar la sesión de lo que sucedió y reiniciar sin problemas importantes.
fuente
Si desea utilizar Servicios en Ubuntu (Upstart): Nodo como servicio en Ubuntu 11.04 con upstart, monit y forever.js
fuente