# Diseño de Logging > Nota: este documento describía originalmente un plan pendiente. Ya está > implementado tal como se describe abajo; se conserva el nombre del > archivo por los enlaces existentes desde `index.md`. ## Objetivo Cada línea de log queda etiquetada automáticamente con el `request_id` de la petición HTTP y con el empleado conectado que la originó (`webUser`, `emp_id`, `full_name`), en formato JSON, sin tener que pasar esos datos a mano en cada llamada a `logger.info/warning/error`. ## Piezas ### `context.py` Dos `ContextVar` por request (aisladas entre corrutinas concurrentes de FastAPI): - `request_id_ctx` — expuesta via `get_request_id()` / `set_request_id()`. - `current_user_ctx` — dict `{"webUser", "emp_id", "full_name"}`, expuesta via `get_current_user()` / `set_current_user(webUser, full_name="", emp_id="-")`. Por defecto ambas quedan en `"-"` cuando no hay usuario autenticado (por ejemplo, en el propio `POST /api/Login` antes de validar credenciales). ### `main.py` - `correlation_id_middleware` llama a `set_request_id()` con el header `X-Request-ID` entrante o un UUID generado si no viene. - `session_middleware`, tras validar el token con `validate_and_refresh_session()`, llama a `set_current_user(webUser, get_employee_name(webUser), get_emp_id(webUser))` para que el resto de la petición (incluyendo las llamadas a `forward_to_sapb1`) quede etiquetada con ese usuario. - `routers/auth.py` también llama a `set_current_user(...)` en un login exitoso, porque `/api/Login` no pasa por el `session_middleware` (todavía no hay token) y de otro modo su propio log quedaría sin usuario. - `LOG_ENVIRONMENT` (variable de entorno, default `"production"`) decide el formato de salida en `setup_logging()`. ### `logger_config.py` — `JsonFormatter` En modo `"production"` (default), cada línea de log —tanto en consola como en `logs/backend.log`— es un JSON con esta forma: ```json {"timestamp": "2026-08-14T15:32:10.123456+00:00", "level": "INFO", "request_id": "3f2a...", "webUser": "jgarcia", "emp_id": "11", "full_name": "Juan García", "http_method": "GET", "endpoint": "Items", "status_code": 200, "message": "[200] GET Items", "module": "sap", "function": "forward_to_sapb1", "line": 141} ``` Campos siempre presentes: `timestamp`, `level`, `request_id`, `webUser`, `emp_id`, `full_name`, `message`, `module`, `function`, `line`. Campos opcionales (`http_method`, `endpoint`, `status_code`) se agregan solo si el `logger.info/warning/error(...)` que generó la línea los pasó vía `extra={...}` — este es el patrón que usan `core/sap.py`, `core/api_gateway.py` y `routers/reports.py` para dejar trazabilidad de cada llamada saliente. Si el log viene de una excepción (`exc_info`), se agrega también `exception` con el traceback formateado. En cualquier otro valor de `LOG_ENVIRONMENT` (por ejemplo `"development"`), la consola usa un formato de texto plano legible (`%(asctime)s [%(levelname)s] %(module)s.%(funcName)s:%(lineno)d - %(message)s`) en vez de JSON — pensado para desarrollo local, no para buscar en el archivo. El archivo `logs/backend.log` (rotativo, 5 MB x 3 backups vía `RotatingFileHandler`) siempre se escribe en JSON sin importar el entorno. ## Qué NO se loguea `routers/auth.py` nunca escribe `Password` / `U_WEB_PASS` en los logs, ni en éxito ni en fallo de login — solo el `webUser`, el `EmployeeID` y si el intento fue exitoso o el motivo del rechazo (`"Usuario no registrado"`, `"Usuario inactivo"`, contraseña incorrecta). Al auditar un cambio en el flujo de login, verificar que esta regla se mantenga. ## Verificación 1. Levantar el backend (local o Docker). 2. Loguearse con un usuario de prueba y hacer un par de llamadas reales (GET a algún catálogo, POST/PATCH a algo). 3. Revisar `logs/backend.log`: cada línea debe traer `webUser`, `emp_id`, `full_name`, y las líneas de proxy SAP también `http_method`, `endpoint`, `status_code`. 4. `grep` de la contraseña de prueba sobre todo `logs/backend.log` (y la salida de consola) para confirmar que **no aparece en ningún lado**.