Cómo ser el primero en enterarse de los errores de tu aplicación web

Cuántas veces te pasó que te mande un mail un cliente (o peor, te llame por teléfono) para decirte que la aplicación que pusiste en producción hace más de una semana acaba de dar uno de esos errores inentendibles:

Y vos, estando en cualquier otro tema tenés que buscar en lo más recóndito de la memoria para recordar de qué se trataba y contestar algo medianamente coherente…

¿No sería genial poder decirle a tu cliente apenas atender: «Sí, ya sé, hubo un problema con la app, ya lo estoy viendo y enseguida lo tenés solucionado»?

Hay varios puntos para señalar en este escenario:

  1. Más allá de que «queda feo» que un cliente vea un mensaje en inglés cuando el resto de la aplicación está en castellano (o que vea una pantalla con fondo blanco y despojada de todo branding), esto da a un potencial atacante información que no tiene por qué tener: que tu aplicación está desarrollada usando Symfony1 (y por lo tanto PHP).
  2. Se pierde un tiempo valioso buceando en los logs de errores hasta entender qué pudo haber pasado. Asumiendo que tales logs existan, claro.

Algo que mejora tanto la seguridad de la aplicación como la experiencia del usuario es simplemente contar una página de error personalizado:

Fácilmente puede especificarse qué hacer en caso de que la respuesta a enviar no sea un código 200 usando configuración de Apache, NginX o el webserver que sea.

En este post te voy a mostrar cómo lograrlo sin tocar nada desde la infraestructura, si no todo dentro de tu propio proyecto.

Lo voy a hacer usando el framework Symfony. Si estás usando otro no va a ser tan literal el tema, pero las ideas te servirán igual:

En Symfony los errores HTTP se manejan mediante Excepciones, para cada tipo de error existe un template que se utiliza para generar la respuesta a enviar.

Todo esto en realidad no es de Symfony si no de Twig (el motor de templating que viene junto con el framework).

Dentro del código de una aplicación Symfony típica vas a encontrar un directorio vendor/symfony/symfony/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/ donde se encuentran los templates usados en caso de error.

Como en cualquier otro caso en que necesites modificar comportamientos estándar de los bundles, lo más conveniente es crear tu propia copia de los archivos originales dentro del directorio app/Resources de tu aplicación. En este caso sería el directorio app/resources/TwigBundle y dentro de él views/Exception/

Hasta aquí resolvimos el problema de lo que el usuario ve pero… ¡todavía sos el último en enterarse de lo que pasó!

Bien… ¿cómo podrías enterarte de algo que está sucediendo en el servidor sin estar pegado al log de errores 24×7?

¿Qué tal una alerta por email?

Lo primero que podrías pensar es en codear tu propio mecanismo de envío de correos ante la aparición de un error… y por ahí hasta lo lograrías. Pero, como nos gusta decir por aquí, ¿para qué reinventar la rueda?

¿Te acordás de lo que te conté sobre Monolog? Vamos a ver cómo se puede usar una de sus características más interesantes: la composición de loggers (o handlers en terminología Monolog).

De lo que se trata acá no es ni de reemplazar el logging tradicional por el mail ni de llenarte la casilla de mensajes crípticos, si no de tener lo mejor de ambos mundos (Una alerta a tiempo con la información necesaria para reaccionar y los logs completos donde deben estar).

Una forma de lograr esto es usando inteligentemente la configuración Monolog y de SwiftMailer. Estas configuraciones las encontrás en el archivo app/config/config.yml(y por supuesto en sus hijos config_dev.yml y config_prod.yml).

Del lado de Monolog debería quedar algo como:

monolog:
    handlers:
        main:
            type:         fingers_crossed
            action_level: error
            handler:      grouped
        grouped:
            type:       group
            members:    [streamed, deduplicated]
        streamed:
            type:  stream
            path:  %kernel.logs_dir%/%kernel.environment%.log
            level: debug
        deduplicated:
            type: deduplication
            handler: swift
        swift:
            type:           swift_mailer
            from_email:     app@dominio.com
            to_email:       mauro.chojrin@leewayweb.com
            subject:        "App::Error_Exception"
            level:          error
            formatter:  monolog.formatter.html
            content_type: text/html
        console:
            type:  console

Lo que estamos haciendo acá es decirle a Monolog (a través del sistema de configuración de Symfony) que cada mensaje debe ser procesado por el handler grouped que, como podrás ver, es de tipo group (Una forma muy elegante de hablar de un conjunto de loggers).

El primero de esos loggers es el clásico: archivo de texto local (Se podría hacer algo más complejo si tuvieses que sincronizar logs de diferentes servidores, pero lo dejamos para otro momento).

El segundo logger es más interesante: deduplicated (Una suerte de filtro para no llenarte la casilla con el mismo mensaje enviado 1000 veces). Este a su vez usa un handler propio: swift.

El handler swift es el que efectivamente se encarga de realizar el envío del error a la casilla que le indiques… en realidad, de lo que se encarga es de pedirle a SwiftMailer que haga todo eso.

Y ahí es donde entra en juego la configuración propia de SwiftMailer… después de todo, esto tampoco es mágico.

Lo más importante acá es a través de qué medio se va a realizar el envío, es decir qué tipo de transport vas a querer usar.

En mi ejemplo usé una cuenta de Gmail creada especialmente para esta aplicación (Total son gratis y andan muy bien :)).

La configuración al final es esta:

swiftmailer:
    host: smtp.gmail.com
    username:  '%mailer_user%'
    password:  '%mailer_password%'
    encryption: ssl
    port: 465

Al usar el username '%mailer_user%' y el password '%mailer_password%' lo que estoy diciendo es que los valores de esta configuración se obtengan del archivo app/config/parameters.yml. Como este archivo es específico para cada entorno donde la aplicación esté desplegada (léase: ¡no lo versiones!), esta configuración permite usar diferentes cuentas para desarrollo y para producción.

Lo único que debés tener en cuenta si vas a usar Gmail (En lugar de algún otro SMTP público) es que vas a tener que permitir el acceso a la cuenta a aplicaciones no seguras.

Por último, para recibir un mensaje que se vea así:

En lugar de así:

Se usan las líneas:

formatter: monolog.formatter.html

content_type: text/html

De la configuración de Monolog.

Y ahora sí, todo listo, si llega a producirse un error inesperado, el mismo sistema te va a avisar con un email bien formateado.

Y si todavía querés llevar el tema a un nivel superior, una herramienta sumamente interesante que te recomiendo usar es Honeybadger.

Con Honeybadger podés tener un panel de administración donde se centralicen todos los problemas u otros mensajes que quieras que tu aplicación envíe.

De esa forma, la gestión de los problemas puede hacerse todavía más eficiente.

Te recomiendo que le des una mirada.

mchojrin

Por mchojrin

Ayudo a desarrolladores PHP a afinar sus habilidades técnicas y avanzar en sus carreras

¿Te quedó alguna duda? Publica aca tu pregunta

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.