diff --git a/VEZA_MVP_STABILITY_TODOLIST.json b/VEZA_MVP_STABILITY_TODOLIST.json index 94abfc19e..ecb9c7b53 100644 --- a/VEZA_MVP_STABILITY_TODOLIST.json +++ b/VEZA_MVP_STABILITY_TODOLIST.json @@ -774,7 +774,7 @@ "description": "Backend returns request_id but frontend doesn't log it. Add for debugging.", "owner": "frontend", "estimated_hours": 2, - "status": "todo", + "status": "completed", "priority": 13, "dependencies": [], "files_to_modify": [ @@ -900,12 +900,12 @@ ] }, "progress_tracking": { - "completed": 12, + "completed": 13, "in_progress": 0, - "todo": 3, + "todo": 2, "blocked": 0, - "last_updated": "2025-01-28T01:00:00Z", - "completion_percentage": 80 + "last_updated": "2025-01-28T02:00:00Z", + "completion_percentage": 87 }, "validation_checklist": { "description": "Run these checks after all tasks complete to verify MVP stability", diff --git a/VEZA_MVP_TODOLIST_TRACKING.md b/VEZA_MVP_TODOLIST_TRACKING.md index 021152345..54917a4b5 100644 --- a/VEZA_MVP_TODOLIST_TRACKING.md +++ b/VEZA_MVP_TODOLIST_TRACKING.md @@ -10,10 +10,10 @@ | Métrique | Valeur | |----------|--------| -| **Tâches complétées** | 12 / 15 | +| **Tâches complétées** | 13 / 15 | | **Phase actuelle** | PHASE-3 (Reliability & Polish) | -| **Progression globale** | ████████████░ 80% | -| **Dernière mise à jour** | 2025-01-28 01:00 | +| **Progression globale** | █████████████░ 87% | +| **Dernière mise à jour** | 2025-01-28 02:00 | ### Progression par Phase @@ -21,7 +21,7 @@ |-------|--------|-------------| | PHASE-1 — Bloquants Critiques | ✅ Terminé | 5/5 | | PHASE-2 — Alignement API | ✅ Terminé | 5/5 | -| PHASE-3 — Fiabilité & Polish | 🔄 En cours | 2/5 | +| PHASE-3 — Fiabilité & Polish | 🔄 En cours | 3/5 | | PHASE-3 — Fiabilité | ⚪ En attente | 0/5 | --- @@ -646,14 +646,32 @@ code: z.number() | **Source** | INT-000013 | | **Owner** | Frontend | | **Effort** | ~2h | -| **Statut** | ⬜ À faire | +| **Statut** | ✅ Terminé | **Problème** : `request_id` du backend non logué côté frontend. -**Fichier** : -- [ ] `apps/web/src/services/api/client.ts` +**Fichiers modifiés** : +- [x] `apps/web/src/services/api/client.ts` → Ajouté logging du request_id dans tous les cas d'erreur +- [x] `apps/web/src/utils/apiErrorHandler.ts` → Ajouté paramètre optionnel pour inclure request_id dans les messages utilisateur -**Action** : Extraire et logger `request_id` des réponses d'erreur. +**Changements effectués** : +- Ajouté logging du `request_id` dans l'interceptor de réponse pour : + - Erreurs générales (avec console.error) + - Erreurs 429 (rate limiting, avec console.warn) + - Erreurs 502/503 (avec console.warn pour retries, console.error après échec) +- Modifié `formatErrorMessage()` pour accepter un paramètre `includeRequestId` : + - Si `true` et en mode développement, inclut le request_id dans le message + - Permet de corréler les erreurs frontend avec les logs backend + +**Validation** : +- `npx tsc --noEmit` → ✅ Aucune erreur TypeScript +- Tous les logs d'erreur incluent maintenant le request_id si disponible +- Format des logs : `[API Error] message (Code: X, Request ID: Y)` + +**Critères d'acceptation** : +- [x] Logs d'erreur incluent request_id +- [x] Peut corréler les erreurs frontend avec les logs backend +- [x] Request_id optionnellement affiché dans les messages utilisateur (mode dev) --- @@ -1088,10 +1106,40 @@ Frontend : **Temps passé** : 2h -**Prochaine tâche** : MVP-013 (Add Error Correlation with Request IDs) +**Prochaine tâche** : MVP-014 (Validate CORS Credentials Configuration) **Notes** : Les erreurs transitoires (502/503) sont maintenant automatiquement retentées avec exponential backoff, améliorant la robustesse de l'application face aux problèmes temporaires de réseau ou de services externes. Le header Retry-After est respecté si présent, permettant au backend de contrôler le timing des retries. +---- + +## 2025-01-28 (suite 2) + +**Tâches travaillées** : MVP-013 +**Statut** : +- MVP-013 : ✅ Terminé + +**Changements effectués** : +- Modifié `apps/web/src/services/api/client.ts` : + - Ajouté logging du `request_id` dans tous les cas d'erreur : + - Erreurs générales : `console.error` avec request_id, code, message, timestamp, details, context, url, method + - Erreurs 429 (rate limiting) : `console.warn` avec request_id et retry_after + - Erreurs 502/503 : `console.warn` pour chaque retry avec request_id, `console.error` après échec final +- Modifié `apps/web/src/utils/apiErrorHandler.ts` : + - `formatErrorMessage()` accepte maintenant un paramètre optionnel `includeRequestId` + - Si `true` et en mode développement, inclut le request_id dans le message formaté + - Permet de corréler les erreurs affichées à l'utilisateur avec les logs backend + +**Validation** : +- `npx tsc --noEmit` → ✅ Aucune erreur TypeScript +- Tous les logs d'erreur incluent maintenant le request_id si disponible +- Format des logs : `[API Error] message (Code: X, Request ID: Y)` avec contexte complet + +**Temps passé** : 1h30 + +**Prochaine tâche** : MVP-014 (Validate CORS Credentials Configuration) + +**Notes** : Les erreurs API incluent maintenant le request_id dans les logs, permettant de corréler facilement les erreurs frontend avec les logs backend pour le debugging. Le request_id peut également être affiché dans les messages utilisateur en mode développement. + --- ## 📚 Commandes Utiles diff --git a/apps/web/src/services/api/client.ts b/apps/web/src/services/api/client.ts index 2b8522bd5..15ebbec4e 100644 --- a/apps/web/src/services/api/client.ts +++ b/apps/web/src/services/api/client.ts @@ -210,6 +210,20 @@ apiClient.interceptors.response.use( const apiError = parseApiError(error); const retryAfter = apiError.retry_after || 5; // Default 5 secondes + // Log error with request_id for correlation + if (apiError.request_id) { + console.warn( + `[API Rate Limit] ${apiError.message} (Request ID: ${apiError.request_id}, Retry after: ${retryAfter}s)`, + { + code: apiError.code, + request_id: apiError.request_id, + retry_after: retryAfter, + url: originalRequest?.url, + method: originalRequest?.method, + }, + ); + } + // Si la requête n'a pas encore été retentée, attendre et réessayer if (originalRequest && !originalRequest._retry && retryAfter > 0) { originalRequest._retry = true; @@ -239,6 +253,23 @@ apiClient.interceptors.response.use( // Calculate delay (respect Retry-After header if present, otherwise exponential backoff) const delay = getRetryDelay(error, retryCount, 1000); + // Log retry attempt with request_id if available + const apiError = parseApiError(error); + if (apiError.request_id) { + console.warn( + `[API Retry] ${status} error, retrying (${retryCount + 1}/${maxRetries}) - Request ID: ${apiError.request_id}`, + { + status, + retry_count: retryCount + 1, + max_retries: maxRetries, + delay_ms: delay, + request_id: apiError.request_id, + url: originalRequest?.url, + method: originalRequest?.method, + }, + ); + } + // Wait before retrying return sleep(delay).then(() => { // Retry the request @@ -248,11 +279,58 @@ apiClient.interceptors.response.use( // If already retried maxRetries times, reject immediately const apiError = parseApiError(error); + + // Log final error with request_id after all retries failed + if (apiError.request_id) { + console.error( + `[API Error] ${status} error after ${maxRetries} retries - Request ID: ${apiError.request_id}`, + { + code: apiError.code, + message: apiError.message, + request_id: apiError.request_id, + timestamp: apiError.timestamp, + url: originalRequest?.url, + method: originalRequest?.method, + }, + ); + } + return Promise.reject(apiError); } // Parser l'erreur en ApiError standardisé pour les autres codes const apiError = parseApiError(error); + + // Log error with request_id for correlation with backend logs + if (apiError.request_id) { + console.error( + `[API Error] ${apiError.message} (Code: ${apiError.code}, Request ID: ${apiError.request_id})`, + { + code: apiError.code, + message: apiError.message, + request_id: apiError.request_id, + timestamp: apiError.timestamp, + details: apiError.details, + context: apiError.context, + url: originalRequest?.url, + method: originalRequest?.method, + }, + ); + } else { + // Log without request_id if not available + console.error( + `[API Error] ${apiError.message} (Code: ${apiError.code})`, + { + code: apiError.code, + message: apiError.message, + timestamp: apiError.timestamp, + details: apiError.details, + url: originalRequest?.url, + method: originalRequest?.method, + }, + ); + } + return Promise.reject(apiError); }, ); diff --git a/apps/web/src/utils/apiErrorHandler.ts b/apps/web/src/utils/apiErrorHandler.ts index 18b4343bf..9b5d29145 100644 --- a/apps/web/src/utils/apiErrorHandler.ts +++ b/apps/web/src/utils/apiErrorHandler.ts @@ -156,18 +156,32 @@ function normalizeApiError(error: any): ApiError { /** * Formate un message d'erreur pour l'affichage dans l'UI * @param error - ApiError + * @param includeRequestId - Si true, inclut le request_id dans le message (pour debugging) * @returns Message formaté pour l'utilisateur */ -export function formatErrorMessage(error: ApiError): string { +export function formatErrorMessage( + error: ApiError, + includeRequestId: boolean = false, +): string { + let message = error.message; + // Si l'erreur a des détails de validation, les inclure if (error.details && Array.isArray(error.details) && error.details.length > 0) { const detailsMessages = error.details .map((detail) => `${detail.field}: ${detail.message}`) .join(', '); - return `${error.message} (${detailsMessages})`; + message = `${error.message} (${detailsMessages})`; } - return error.message; + // Optionnellement inclure le request_id pour le debugging (en mode développement) + if (includeRequestId && error.request_id) { + const isDev = import.meta.env.DEV; + if (isDev) { + message = `${message} [Request ID: ${error.request_id}]`; + } + } + + return message; } /**