fix(MVP-013): Add error correlation with request IDs in logs
This commit is contained in:
parent
872f11d264
commit
2c8d0ea5eb
4 changed files with 157 additions and 17 deletions
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in a new issue