Introducción
Ir a producción cambiará su flujo de trabajo en desarrollo, añadirá estrés a su vida y le quitará tiempo de desarrollo para mantener las tareas. Es como tener un bebé.
Pero no quiero desanimarte a salir a producción, tu jefe me odiará, en cambio, quiero darte algunos consejos que aprendí a través de estos años de dolor experiencia saliendo a la producción.
Espero que encuentres este post útil, si crees que te robé tu tiempo, por favor, envíame un tweet a @santypk4 con tus sentimientos.
Tabla de contenidos
Manejo de errores en Node.js 🚧
Cuándo fue la última vez que un usuario final te informó un error?
Tal vez cuando el error fue fatal y necesitaba usar su sistema para continuar trabajando.
Pero la mayoría de las veces los errores ocurren, y nosotros, los desarrolladores detrás, nunca lo notamos.
Los usuarios generalmente no quieren reportar un error, implica llenar formularios feos, dar demasiados detalles, y generalmente nunca obtienen una respuesta.
La última vez que me sucedió esto, estaba probando este nuevo producto SaaS que me trae automáticamente contenido relacionado con mi nicho, para publicar en Twitter, todo basado en una IA de última generación.
La landing page me compró instantáneamente
Bueno, para mi suerte, la aplicación estaba completamente rota, el formulario de registro nunca completó la solicitud porque algún campo específico estaba mal validado. Así que contacté a los desarrolladores por twitter, pero nunca me respondieron.
Hasta el último día del mes, cuando querían cobrarme por una suscripción a su SaaS que nunca funcionó.
Todavía estoy interesado en un servicio como este
No seas como esos tipos y registra tus errores antes de que el usuario lo note.
Debes tener una forma estable, confiable y centralizada de manejar tus errores.**
Usamos ejemplo de proyecto real de la arquitectura de 3 capas del artículo anterior, imaginemos que su motor de búsqueda de usuarios comienza a fallar.
Lo importante aquí no es manejar los errores de las capas subyacentes sino lanzarlos a la capa controladora.
import UsefulError from '../utils/usefulError';
class UserService {
constructor(
private userSearchEngineService,
private userThirdpartyService,
private userDatabaseModel,
private logger,
)
GetAll() {
try {
return this.userDatabaseModel.find();
} catch(e) {
throw new Error(`The database is dead!`, 503)
}
}
SearchUserByLocation(lat, long) {
try {
this.logger.silly('performing search...')
return this.userSearchEngineService.searchByLocation(lat, long);
} catch(e) {
throw new Error(`The user search engine doesn't work!`, 503)
}
}
// No está relacionado con algo que me haya sucedido
GetUsersFromThatThirdPartyServiceThatTheFounderMadeUsAssociateAndNeverWorkAndSeemLikeOurFault() {
try {
return this.userThirdpartyService.find();
} catch(e) {
this.logger.silly('We should call Pablo')
throw new Error(`The thirdparty api doesn't work!`, 500)
}
}
}
Vamos a crear una class error personalizado, para que podamos añadirle más propiedades.
class UsefulError extends Error {
constructor(name, httpStatusCode = 500, context, ...params) {
// Pasar el resto de los argumentos (incluyendo los específicos del vendedor) al constructor principal.
super(...params);
// Mantiene el rastro adecuado del lugar donde fue lanzado nuestro error (sólo disponible en V8)
if (Error.captureStackTrace) {
Error.captureStackTrace(this, UsefulError );
}
this.name = 'name';
this.httpStatusCode = httpStatusCode;
this.context = context;
this.date = new Date();
}
}
No confunda sus errores, sea honesto, haga saber a sus usuarios por qué falla la solicitud, para que puedan realizar otra acción, o intentar algo diferente.
Un buen mensaje de error será como:
El motor de búsqueda de usuarios no funciona por ahora pero puedes seguir viendo tu perfil.
import Logger from '../logger';
import UserService from '../services/user';
export default (app) => {
app.get('/user/search-location', (req, res, next) => {
try {
const { lat, lng } = req.query;
Logger.silly('Invoking user service to search by location')
const users = UserService.SearchUserByLocation(lat, lng);
return res.json(users).status(200);
} catch(e) {
Logger.warn('We fail!')
return next(e);
}
})
}
La capa de controladores lo pasa al siguiente middleware express, nuestro gestor de errores centralizado.
import Logger from '../logger';
export default (err, req, res, next) => {
Logger.error('Error %o', err);
return res.json(err).status(err.httpStatusCode || 500);
}
Lo cual me lleva a...
La importancia de los registros 📝
Alguna vez has tenido un servidor que llenó con console.logs para todo?
✋ He estado allí.
Pero entonces, alguna vez has tenido un servidor que no registra nada?
✋ Yo también he estado allí, y fue peor que registrar todo.
Lo que hago ahora es una mezcla entre ambos enfoques.
Registro TODO pero no todo se muestra en el registro de salida 😉
Creo firmemente que es necesario registrar cuando una acción está a punto de realizarse, cuando la acción se ha realizado, el resultado y el error si se ha producido.
Aunque, esos registros tienen diferentes niveles.
Cuando tu aplicación esté en producción, y necesites más información, sólo tienes que cambiar el nivel de registro a través de una variable de entorno.
Puedes ver un ejemplo de una migración de console.log a winston eneste PR to the bulleproof node.js project.
Lo mejor, Winston te permite establecer la capa de "transporte", que será donde se mostrarán tus errores.
Los hice aparecer en la consola, pero puedes instalar fácilmente un plugin que informe a Sentry o Rollbar o lo que sea mejor para ti.
Bendito patrón de adaptación.
Conclusión 🏗️
Usa una biblioteca de loggers como Winston, y separa tus logs por nivel en lugar de usar console.log que inundan los archivos de logs.
Considera la posibilidad de usar un manejo de errores centralizado, puede ser un middleware para express, simplemente reenvía tus errores todos a la misma ubicación central en tu servidor.
Espero que hayan disfrutado de este pequeño post, y si están interesados en más consejos de node.js, y tal vez unaArquitectura del proyecto Bullet Proof node.js, te recomiendo encarecidamente que leas ese artículo.