Lab guiado: Manejadores de error, introspección y logging en Flask¶
- Unidad: UT4 — Interfaz web con Flask
- Sesión: 3 — Observabilidad y calidad de la interfaz web (parte 2 de 2, continúa de la sesión 2).
- Agrupamiento: Individual o en parejas conductor/navegante (igual que los labs anteriores).
- Recursos:
- Ficheros de trabajo:
expendedora/presentation/app.pyexpendedora/README.md,expendedora/CHANGELOG.md,expendedora/docs/EJECUCION.md,expendedora/.gitignore(en el paso final de documentación)
Punto de partida¶
Parte del zip que entregaste al final del lab a2. Antes de empezar:
- Activa el entorno virtual del proyecto (
source .venv/bin/activateen Linux/Mac, osource .venv/Scripts/activateen Git Bash de Windows). - Sitúate en la carpeta padre de
expendedora/. - Arranca el servidor web:
python -m expendedora.presentation.app. - Comprueba que todas las rutas del a2 funcionan (
/productos,/seleccionar/A1,/comprar, etc.). Si alguna falla, revuélvete al a2 antes de seguir.
Trabajo en parejas
Si decides trabajar en pareja, sigue el procedimiento conductor/navegante del lab a1.
Antes de empezar: repasemos los conceptos
Manejadores globales de error
Hasta ahora cada route captura sus propias excepciones de dominio con try/except. Flask permite registrar funciones que se disparan ante errores HTTP concretos (404, 500…), capturen lo que capturen los routes. Sirve de red de seguridad para lo que se escapa.
Pista recurso
Flask mantiene internamente un mapa de todas las rutas registradas (app.url_map). Podemos consultarlo en tiempo de ejecución para, por ejemplo, ofrecer una página de ayuda que siempre esté al día.
Hook
Un hook (gancho) es un punto del flujo de un programa en el que puedes "enganchar" código propio para que se ejecute automáticamente, sin modificar el flujo original. @app.before_request es el hook que Flask llama antes de cada petición, sea la que sea.
Logging
El módulo logging de la librería estándar de Python permite registrar mensajes en un fichero, con timestamp y nivel (INFO, WARNING, ERROR…). Útil para auditar a posteriori qué ha pasado sin depender de lo que quede en la consola desde la que se lanza el servidor.
Paso 1 — Manejador de errores global app.py¶
Conceptos
Flask permite registrar funciones que se ejecutan cuando ocurre un error HTTP concreto. @app.errorhandler(404) registra la función que maneja todos los errores 404 de la app; @app.errorhandler(500) hace lo mismo para errores del servidor.
Objetivo¶
Añadir manejadores globales para los errores 404 y 500, de modo que aunque Flask genere el error sin pasar por un route, el usuario vea una página coherente.
Tarea¶
Pista
Los manejadores globales no sustituyen a los errores que ya gestionas dentro de una route.
Si una route captura una excepción con except ... as e y devuelve return str(e), 404, Flask no llama al manejador global de 404: la propia route ya ha construido la respuesta.
El manejador @app.errorhandler(404) se ejecuta cuando Flask genera un 404 automáticamente, por ejemplo al visitar una URL que no existe o cuando un converter rechaza un valor de la URL antes de entrar en la route.
Para errores no capturados dentro de una route, Flask generará normalmente un 500, que puede gestionarse con @app.errorhandler(500).
También atrapa los 404 por converter rechazado
El manejador @app.errorhandler(404) se dispara también cuando un converter rechaza el valor de la URL — los casos que viste en el Paso 2 del lab a2 (/reponer/A1/-1, /reponer/A1/tres, /insertar/1…). Flask genera el 404 antes de llamar a tu route, pero el manejador lo captura igual. Pruébalo: visita /reponer/A1/-1 y comprueba que ahora sale tu página personalizada en lugar de la página genérica de Flask.
Pregunta¶
Accede a una URL que no existe, por ejemplo
http://localhost:5000/noexiste. ¿Qué muestra ahora? ¿Qué mostraba antes de añadir el manejador? Prueba también con/reponer/A1/-1(converter rechazando un valor).
Respuesta
Antes: la página de error por defecto de Flask (HTML genérico). Ahora: tu manejador personalizado. En ambos casos el código HTTP es 404 — solo cambia la presentación del error. El manejador trata igual los dos tipos de 404: URL inexistente y converter rechazado.
Comprobación — forzar un error 500 reproducible¶
Para verificar que el manejador 500 funciona, añade temporalmente este route de prueba, visítalo, comprueba que el manejador lo captura, y bórralo cuando termines:
Con debug=True verás el traceback técnico; en producción (sin debug) se mostraría tu manejador 500.
Reflexión¶
Respuestas
- Los manejadores de error son routes especiales que capturan códigos HTTP globales.
- Permiten dar una experiencia coherente al usuario en cualquier error (incluso en los que no tienen route propio).
- No sustituyen al try/except en los routes — son una red de seguridad adicional para los errores que se "escapan".
Paso 2 — Route de ayuda con todas las rutas app.py¶
Conceptos
Flask mantiene un mapa de todas las rutas registradas accesible desde app.url_map. Esto es útil para depurar, para documentar la API, y para ofrecer al usuario una página índice con todo lo que la app puede hacer.
Objetivo¶
Crear un route /ayuda que liste todas las rutas registradas.
Tarea¶
Pista
app.url_map.iter_rules() devuelve todas las reglas registradas. Incluye una regla especial, static, que Flask usa internamente para servir ficheros estáticos (CSS, imágenes…); la filtramos porque no es una ruta de nuestra app.
Comprobación¶
Visita http://localhost:5000/ayuda. Debe aparecer una lista completa con todas las rutas que has creado.
Pregunta¶
¿Por qué
/ayudamuestra rutas como/producto/<codigo>con los placeholders<codigo>literales en vez de URLs concretas?
Respuesta
Porque url_map contiene el patrón de cada ruta, no las URLs concretas. Para generar una URL concreta haría falta pasar valores con url_for, pero iter_rules solo conoce las plantillas.
Reflexión¶
Respuestas
app.url_mapes la "tabla de routing" interna de Flask, accesible en tiempo de ejecución.- Una página
/ayudagenerada automáticamente siempre está al día: si añades un route, aparece solo. - En labs posteriores, cuando uses templates, esta información aparecerá más bonita con Jinja2.
Paso 3 — Logging de peticiones app.py¶
Conceptos
Registrar cada petición en un fichero permite auditar la actividad de la app sin depender de lo que aparezca en el traceback del navegador. El módulo logging de la librería estándar de Python ya lo tienes; Flask lo integra de forma natural.
Objetivo¶
Configurar un log de peticiones que escriba en un fichero expendedora.log.
Tarea¶
Al inicio de app.py, después de los imports y antes de crear la app, añade la configuración:
Y después de crear app = Flask(__name__), añade el "gancho" que se ejecuta antes de cada petición:
Comprobación¶
- Visita varias URLs en el navegador.
- En otra terminal, ejecuta
tail -f expendedora.log(o abre el fichero con tu editor). - Cada petición debe aparecer con timestamp, nivel y método + ruta.
Pregunta¶
El log se escribe en
expendedora.log. ¿Conviene incluir ese fichero en el zip de entrega? ¿Y en un.gitignoresi fuera un repositorio Git?
Respuesta
- En la entrega: no. Es ruido generado por las pruebas que tú mismo hiciste; no es parte del código ni del resultado del lab.
- En
.gitignore: sí. Es un fichero generado en ejecución. Los ficheros generados (logs, bases de datos de desarrollo, caches) no se versionan; solo se versiona el código que los genera.
Reflexión¶
Respuestas
@app.before_requestes un "hook" que Flask llama antes de ejecutar cualquier route.- El log es una herramienta transversal: no pertenece a ningún route concreto pero los observa a todos.
- En producción, el nivel del log y el destino (fichero, syslog, servicio externo) se ajustan.
logging.INFOes un nivel moderadamente verboso, útil para desarrollo.
Paso 4 — Comparación final menu.py vs app.py app.py¶
Conceptos
Ahora que la interfaz web cubre toda la funcionalidad del menú y tiene observabilidad propia, es momento de consolidar la equivalencia entre ambas capas de presentación y verificar que la arquitectura se ha respetado.
Objetivo¶
Documentar explícitamente la equivalencia entre menu.py y app.py para confirmar que la web cubre exactamente las mismas operaciones que la consola.
Tarea¶
Añade esta tabla como comentario al inicio de app.py:
Pregunta¶
¿Qué línea de
servicios.pyejecuta el route/comprar? Localízala exactamente.
Respuesta
return self._maquina.comprar() dentro del método comprar de ServicioExpendedora. Es exactamente la misma línea que ejecuta opcion_comprar(servicio) desde menu.py. La capa de aplicación no ha cambiado — solo le hemos construido una nueva puerta de entrada.
Comprobación¶
Abre servicios.py, maquina.py y menu.py. Confirma visualmente que:
maquina.pyno se ha tocado.servicios.pysolo ha crecido en un método (saldo), y es delegación pura.menu.pyno se ha tocado.
Arranca el menú en una terminal (python -m expendedora.presentation.menu) y el servidor web en otra (python -m expendedora.presentation.app). Agrega un producto por la web, refresca la lista en el menú: el producto aparece. Ambas presentaciones comparten el mismo repositorio SQLite.
Reflexión¶
Respuestas
app.pyymenu.pyson dos presentaciones distintas del mismo sistema.- Coexisten: la expendedora funciona por consola y por web simultáneamente sobre la misma base de datos.
- La arquitectura por capas ha cumplido su promesa: cambiar la presentación no rompe nada.
Paso 5 — Actualizar la documentación del proyecto¶
Conceptos
Un proyecto bien mantenido tiene su documentación sincronizada con el código. Igual que en el lab a1, terminamos el trabajo actualizando los ficheros que han cambiado por culpa de este lab.
Objetivo¶
Dejar README.md, CHANGELOG.md, docs/EJECUCION.md y .gitignore en sintonía con la nueva capa de observabilidad.
Tarea¶
CHANGELOG.md¶
Añade una entrada nueva al inicio del fichero:
## [0.7.0] - (Fase 07: observabilidad y manejadores globales)
### Added
- `presentation/app.py`: manejadores globales `@app.errorhandler(404)` y `@app.errorhandler(500)`.
- `presentation/app.py`: route `/ayuda` que lista todas las rutas registradas mediante `app.url_map`.
- `presentation/app.py`: logging de peticiones con `@app.before_request`; se genera `expendedora.log` al arrancar.
- `presentation/app.py`: tabla de equivalencia `menu.py` ↔ `app.py` como comentario inicial.
- `.gitignore`: entrada `*.log` para no versionar ficheros de log.
README.md¶
- En la sección Uso (interfaz web), añade
/ayudaa la lista de rutas disponibles:
- Añade al final de esa misma sección una nota:
Al arrancar el servidor se crea automáticamente `expendedora.log` en la carpeta desde la que lances el comando. Contiene una línea por cada petición HTTP recibida.
docs/EJECUCION.md¶
Añade al final de la sección Ejecutar interfaz web:
El fichero `expendedora.log` se genera junto al comando que lanza el servidor (nivel INFO, formato timestamp + método + ruta). No se incluye en la entrega — es ruido generado por tus pruebas.
.gitignore¶
Si existe, añade la línea *.log. Si no existe, créalo con este contenido:
Comprobación¶
Abre README.md y comprueba que alguien que no conozca el proyecto puede saber, solo leyéndolo, que ahora tiene manejadores globales, route de ayuda y logging. Arranca el servidor, visita una URL inexistente, comprueba que el expendedora.log ha registrado la petición.
Checklist de entrega¶
- Manejadores globales
@app.errorhandler(404)y@app.errorhandler(500)enapp.py. - Route
/ayudacon lista de rutas registradas medianteapp.url_map. - Logging configurado con
logging.basicConfigy hook@app.before_request.expendedora.logse genera al recibir peticiones. - Tabla de equivalencia
menu.py↔app.pyescrita como comentario al inicio deapp.py. -
CHANGELOG.mdcon entrada[0.7.0]. -
README.mdactualizado con/ayuday la nota sobreexpendedora.log. -
docs/EJECUCION.mdactualizado con la nota de log. -
.gitignoreincluye*.log. - Ningún cambio en
domain/,application/,infrastructure/nipresentation/menu.py. Toda la observabilidad vive enpresentation/app.py. - Entregado un zip con todo el proyecto — será la base del siguiente lab.
Problemas comunes¶
NameError: name 'request' is not defined
Falta from flask import request al inicio de app.py. Revisa el Paso 3.
expendedora.log no se crea
logging.basicConfig tiene que llamarse antes de que Flask arranque (por eso va al inicio del fichero, no dentro de una función). Asegúrate de que el orden es: import logging → logging.basicConfig(...) → app = Flask(__name__) → hooks y routes.
El manejador 500 no se muestra (veo el traceback técnico)
Con debug=True Flask muestra su traceback interactivo en lugar de tu manejador. Es normal y útil en desarrollo. Para ver el manejador real, cambia temporalmente app.run(debug=True) por app.run(debug=False), vuelve a probar /error, y luego restaura debug=True.
/ayuda no aparece en su propia lista
Si tu /ayuda no aparece en la propia lista, asegúrate de que el @app.route('/ayuda') está registrado antes de leer app.url_map — es decir, que la declaración del decorador se ha ejecutado ya. Si pones el route en un fichero aparte que no se importa, la regla no se añade al mapa.