- MongoDB storage: cwmp_exchanges collection with indexes (deviceId+timestamp, deviceId+sequenceNumber unique, TTL 24h) - ExchangeStoreService: CWMP method extraction, per-device queue for ordering, retry logic, message cap (5000/device) - Exchange API: GET/DELETE/SSE stream endpoints with Last-Event-ID reconnection - CwmpLoggerMiddleware: dual-write (MongoDB + legacy files) with fire-and-forget - Frontend: SequenceViewer (CPE↔ACS lifelines, arrows, auto-scroll, virtual scroll) + InspectorPanel (formatted XML, copy, syntax highlighting) - Tr069Trace page: integrated new viewer with col-6/col-6 layout - DeviceDetail page: collapsible exchange section when device is traced - 14 property-based tests, 40+ unit tests, 5 integration tests - Health check includes index verification - Config: feature flags, SSE timeouts, retry policy
15 KiB
Troubleshooting Guide: TR-069 Exchange Viewer
Ce guide documente les problèmes courants du système de visualisation des échanges TR-069/CWMP, leurs causes et solutions.
Table des matières
- Déconnexions SSE
- Timeouts MongoDB et récupération
- Diagnostic de perte de messages
- Vérification des index et du TTL
- Problèmes frontend (SequenceViewer)
- Feature flags et rollback
1. Déconnexions SSE
1.1 Causes courantes
| Cause | Symptôme | Fréquence |
|---|---|---|
| Timeout d'inactivité (5 min) | Déconnexion après 5 minutes sans nouvel échange | Normal — par design |
| Timeout absolu (2 h) | Déconnexion après 2 heures de connexion continue | Normal — par design |
| Proxy/reverse-proxy buffering | Messages SSE arrivent en burst au lieu de temps réel | Courant avec nginx |
| Perte réseau | Indicateur "disconnected" dans le viewer | Variable |
| SSE désactivé (feature flag) | Erreur 503 à la connexion SSE | Intentionnel |
| Device non tracé | Erreur 404 à la connexion SSE | Erreur utilisateur |
1.2 Solutions
Timeout d'inactivité / absolu
Le système est conçu pour fermer les connexions inactives. Le client se reconnecte automatiquement via le mécanisme Last-Event-ID — aucun message n'est perdu.
Pour ajuster les timeouts :
# Augmenter le timeout d'inactivité (défaut: 300000ms = 5 min)
EXCHANGE_STORE_SSE_INACTIVITY_TIMEOUT_MS=600000
# Augmenter le timeout absolu (défaut: 7200000ms = 2h)
EXCHANGE_STORE_SSE_ABSOLUTE_TIMEOUT_MS=14400000
Proxy/reverse-proxy buffering
Si un reverse-proxy (nginx, HAProxy) est en place, il peut bufferiser les réponses SSE. Le header X-Accel-Buffering: no est déjà envoyé par le serveur, mais vérifiez aussi la configuration nginx :
location /api/v1/tr069-trace/ {
proxy_buffering off;
proxy_cache off;
proxy_set_header Connection '';
proxy_http_version 1.1;
chunked_transfer_encoding off;
}
Client qui ne se reconnecte pas
Le composable useExchangeStream utilise l'EventSource natif avec retry automatique (3 secondes par défaut). Si la reconnexion ne fonctionne pas :
- Vérifiez que le device est toujours en trace active
- Vérifiez les logs serveur pour des erreurs 404 ou 503
- Vérifiez la valeur
retrydans la configuration SSE :
# Intervalle de reconnexion envoyé au client (défaut: 3000ms)
EXCHANGE_STORE_SSE_RETRY_INTERVAL_MS=3000
1.3 Diagnostic dans les logs
Cherchez les entrées [SSE] dans les logs applicatifs :
docker compose logs app | grep '\[SSE\]'
Messages attendus :
[SSE] Connection closed { deviceId: "...", reason: "inactivity" }— timeout normal[SSE] Connection closed { deviceId: "...", reason: "absolute" }— timeout absolu normal[SSE] Connection closed { deviceId: "...", reason: "client_disconnect" }— client a fermé[SSE] Error fetching missed exchanges for reconnection— problème MongoDB lors du rattrapage
2. Timeouts MongoDB et récupération
2.1 Scénarios de timeout
| Scénario | Impact | Comportement |
|---|---|---|
| Write timeout (persistance) | Message potentiellement perdu | Retry (critique) ou discard (non-critique) |
| Read timeout (API >5s) | Résultats partiels retournés | isTruncated: true dans la réponse |
| Connection lost | Toutes les opérations échouent | Les sessions CWMP continuent sans log |
2.2 Write timeout — Retry automatique
Le ExchangeStoreService gère les échecs d'écriture selon la criticité du message :
Messages critiques (Inform, GetParameterValues, SetParameterValuesResponse, Fault, etc.) :
- 3 tentatives avec backoff exponentiel : 1s, 2s, 4s
- Si toutes échouent : log error + discard (session CWMP non impactée)
Messages non-critiques (Empty, Unknown) :
- Aucun retry — discard immédiat + log warning
Vérifier les échecs de retry :
docker compose logs app | grep '\[ExchangeStore\] All retries exhausted'
docker compose logs app | grep '\[ExchangeStore\] Critical message persistence failed'
Pour ajuster la politique de retry :
# Nombre max de tentatives (défaut: 3)
EXCHANGE_STORE_RETRY_MAX_ATTEMPTS=3
# Délai de base en ms pour le backoff (défaut: 1000)
# Résultat : 1000ms, 2000ms, 4000ms
EXCHANGE_STORE_RETRY_BASE_DELAY_MS=1000
2.3 Read timeout — Résultats partiels
Si une requête API dépasse 5 secondes, le contrôleur retourne les résultats partiels avec un flag :
{
"exchanges": [...],
"metadata": {
"isTruncated": true,
"total": 4500
}
}
Causes courantes :
- Index manquant sur
(deviceId, timestamp)— voir section 4 - Trop de documents pour le device (proche du cap 5000)
- Charge MongoDB élevée (vérifier les métriques)
Pour ajuster le timeout de lecture :
# Timeout en ms (défaut: 5000)
EXCHANGE_STORE_READ_TIMEOUT_MS=5000
2.4 Vérifier la connectivité MongoDB
# Se connecter au shell MongoDB
docker compose exec mongodb mongosh
# Vérifier le statut
db.adminCommand({ serverStatus: 1 }).connections
# Vérifier la latence
db.adminCommand({ ping: 1 })
3. Diagnostic de perte de messages
3.1 Arbre de décision
Message manquant dans le viewer ?
│
├─ Le device est-il en trace ?
│ └─ Non → Activer la trace d'abord. Les messages ne sont PAS persistés hors trace.
│
├─ Le feature flag FEATURE_EXCHANGE_PERSISTENCE est-il à true ?
│ └─ Non → Activer la persistance : FEATURE_EXCHANGE_PERSISTENCE=true
│
├─ Le message apparaît-il dans MongoDB mais pas dans le viewer ?
│ └─ Oui → Problème SSE (voir section 1) ou problème frontend (voir section 5)
│
├─ Le message n'apparaît PAS dans MongoDB ?
│ ├─ Vérifier les logs de retry : grep "All retries exhausted"
│ ├─ Vérifier le cap (5000 messages) : les anciens messages sont supprimés
│ └─ Vérifier le TTL (24h) : les messages >24h sont auto-supprimés
│
└─ Message tronqué (XML incomplet) ?
└─ Le rawXml dépassait 2MB → grep "rawXml exceeds 2MB" dans les logs
3.2 Vérifier les messages en base
# Se connecter au shell MongoDB
docker compose exec mongodb mongosh
# Compter les messages pour un device
use adam_acs
db.cwmp_exchanges.countDocuments({ deviceId: "<DEVICE_ID>" })
# Voir les derniers messages
db.cwmp_exchanges.find({ deviceId: "<DEVICE_ID>" }).sort({ timestamp: -1 }).limit(5).pretty()
# Voir la séquence max (dernier message)
db.cwmp_exchange_counters.findOne({ _id: "<DEVICE_ID>" })
3.3 Vérifier les logs de persistance
# Chercher les erreurs de persistance
docker compose logs app | grep '\[ExchangeStore\]'
# Messages spécifiques
docker compose logs app | grep 'Failed to persist exchange'
docker compose logs app | grep 'Non-critical message persistence failed'
docker compose logs app | grep 'All retries exhausted'
docker compose logs app | grep 'Message cap enforced'
docker compose logs app | grep 'rawXml exceeds 2MB'
3.4 Message supprimé par le cap
Le système maintient un maximum de 5000 messages par device. Quand ce cap est dépassé, les messages les plus anciens sont supprimés.
# Vérifier si le cap a été atteint
docker compose logs app | grep 'Message cap enforced'
Pour ajuster le cap :
# Nombre max de messages par device (défaut: 5000)
EXCHANGE_STORE_MAX_MESSAGES=5000
3.5 Message supprimé par le TTL
MongoDB supprime automatiquement les documents dont createdAt dépasse 24 heures (via l'index TTL).
# Vérifier les documents les plus anciens
docker compose exec mongodb mongosh --eval "
use adam_acs;
db.cwmp_exchanges.find({}, { createdAt: 1, deviceId: 1 }).sort({ createdAt: 1 }).limit(3).pretty()
"
4. Vérification des index et du TTL
4.1 Lister les index existants
docker compose exec mongodb mongosh --eval "
use adam_acs;
db.cwmp_exchanges.getIndexes().forEach(idx => printjson(idx))
"
Index attendus :
| Nom | Champs | Options |
|---|---|---|
_id_ |
{ _id: 1 } |
— |
idx_deviceId_timestamp |
{ deviceId: 1, timestamp: 1 } |
— |
idx_deviceId_sequenceNumber |
{ deviceId: 1, sequenceNumber: 1 } |
unique: true |
idx_createdAt_ttl |
{ createdAt: 1 } |
expireAfterSeconds: 86400 |
4.2 Vérifier l'index TTL
docker compose exec mongodb mongosh --eval "
use adam_acs;
const indexes = db.cwmp_exchanges.getIndexes();
const ttlIdx = indexes.find(i => i.name === 'idx_createdAt_ttl');
if (ttlIdx) {
print('TTL index OK — expireAfterSeconds:', ttlIdx.expireAfterSeconds);
} else {
print('ERREUR: TTL index manquant !');
}
"
4.3 Recréer les index manuellement (si manquants)
Si des index sont manquants après un incident :
docker compose exec mongodb mongosh --eval "
use adam_acs;
// Index composite (deviceId + timestamp)
db.cwmp_exchanges.createIndex(
{ deviceId: 1, timestamp: 1 },
{ name: 'idx_deviceId_timestamp' }
);
// Index unique (deviceId + sequenceNumber)
db.cwmp_exchanges.createIndex(
{ deviceId: 1, sequenceNumber: 1 },
{ unique: true, name: 'idx_deviceId_sequenceNumber' }
);
// Index TTL (24h)
db.cwmp_exchanges.createIndex(
{ createdAt: 1 },
{ expireAfterSeconds: 86400, name: 'idx_createdAt_ttl' }
);
print('Tous les index recréés.');
"
4.4 Vérifier le statut du TTL monitor
Le TTL monitor de MongoDB tourne par défaut toutes les 60 secondes. Pour vérifier qu'il fonctionne :
docker compose exec mongodb mongosh --eval "
const status = db.adminCommand({ serverStatus: 1 });
print('TTL deletedDocuments:', status.metrics.ttl.deletedDocuments);
print('TTL passes:', status.metrics.ttl.passes);
"
Si passes n'augmente pas, le TTL monitor pourrait être bloqué (rare, nécessite un restart MongoDB).
4.5 Modifier le TTL
Pour changer la durée du TTL sans recréer l'index :
docker compose exec mongodb mongosh --eval "
use adam_acs;
db.runCommand({
collMod: 'cwmp_exchanges',
index: {
name: 'idx_createdAt_ttl',
expireAfterSeconds: 172800 // 48h
}
});
"
N'oubliez pas de mettre à jour la variable d'environnement pour cohérence :
EXCHANGE_STORE_TTL_SECONDS=172800
4.6 Vérifier la collection counters
La collection cwmp_exchange_counters stocke les séquences atomiques par device :
docker compose exec mongodb mongosh --eval "
use adam_acs;
db.cwmp_exchange_counters.find().forEach(doc => {
print(doc._id, '→ seq:', doc.seq);
})
"
Si un compteur est incohérent avec les données réelles :
# Recalculer le max sequenceNumber pour un device
docker compose exec mongodb mongosh --eval "
use adam_acs;
const deviceId = '<DEVICE_ID>';
const maxSeq = db.cwmp_exchanges.find({ deviceId }).sort({ sequenceNumber: -1 }).limit(1).toArray();
if (maxSeq.length > 0) {
db.cwmp_exchange_counters.updateOne(
{ _id: deviceId },
{ \$set: { seq: maxSeq[0].sequenceNumber } }
);
print('Counter reset to', maxSeq[0].sequenceNumber);
}
"
5. Problèmes frontend (SequenceViewer)
5.1 Le viewer ne se connecte pas au SSE
Causes possibles :
- Le device n'est pas en trace → activer la trace d'abord
- Le feature flag
FEATURE_SSE_ENABLEDest àfalse→ erreur 503 - Problème CORS si accès depuis un domaine différent
Diagnostic : Ouvrir la console navigateur (F12) et chercher les erreurs EventSource.
5.2 Les messages ne s'affichent pas en temps réel
Si les messages arrivent dans MongoDB mais pas dans le viewer :
- Vérifier l'indicateur de connexion : doit être "connected" (vert)
- Si "disconnected" (rouge) : voir section 1
- Si "paused" (jaune) : l'auto-scroll est désactivé car l'utilisateur a scrollé vers le haut — cliquer "Resume auto-scroll"
5.3 Performance lente avec beaucoup de messages
Le viewer utilise du virtual scrolling pour >200 messages. Si la performance se dégrade :
- Le cap client est fixé à 1000 messages en mémoire (les plus anciens sont purgés côté client)
- Si le nombre total dépasse 5000 en base, les plus anciens sont purgés côté serveur
- Utiliser "Clear exchanges" pour repartir d'un état vide
5.4 L'inspecteur XML affiche du contenu brut (non formaté)
Si le XML n'est pas mis en forme :
- Le XML est peut-être malformé → l'inspecteur affiche le contenu brut en fallback
- Vérifier si le message a un
rawXmlvide (cas "Empty")
6. Feature flags et rollback
6.1 Flags disponibles
| Variable d'environnement | Défaut | Effet si false/file |
|---|---|---|
FEATURE_EXCHANGE_PERSISTENCE |
true |
Les échanges ne sont pas écrits en MongoDB |
FEATURE_SSE_ENABLED |
true |
L'endpoint SSE retourne 503, le client utilise le REST polling (5s) |
FEATURE_EXCHANGE_API_SOURCE |
mongodb |
Si file, l'API lit depuis l'ancien système de fichiers |
6.2 Procédure de rollback rapide
En cas de problème critique :
# 1. Désactiver la persistance (les sessions CWMP continuent normalement)
FEATURE_EXCHANGE_PERSISTENCE=false
# 2. Désactiver le SSE (le frontend utilise le polling REST)
FEATURE_SSE_ENABLED=false
# 3. Revenir à la source fichier pour l'API
FEATURE_EXCHANGE_API_SOURCE=file
Redémarrer l'application après modification :
docker compose restart app
6.3 Vérifier l'état actuel des flags
Les valeurs actives sont loguées au démarrage de l'application. Vous pouvez aussi interroger le health check :
curl -s http://localhost:8080/api/v1/health/tr069-exchanges | python3 -m json.tool
Commandes utiles — Résumé rapide
# Status général
docker compose logs app --tail 20 | grep -E '\[ExchangeStore\]|\[SSE\]'
# Compter les échanges d'un device
docker compose exec mongodb mongosh --eval "use adam_acs; db.cwmp_exchanges.countDocuments({deviceId:'<ID>'})"
# Vérifier les index
docker compose exec mongodb mongosh --eval "use adam_acs; db.cwmp_exchanges.getIndexes().forEach(i=>printjson(i))"
# Vérifier le TTL monitor
docker compose exec mongodb mongosh --eval "printjson(db.adminCommand({serverStatus:1}).metrics.ttl)"
# Tester la connexion SSE manuellement
curl -N -H "Accept: text/event-stream" http://localhost:8080/api/v1/tr069-trace/<DEVICE_ID>/exchanges/stream
# Tester la reconnexion avec Last-Event-ID
curl -N -H "Accept: text/event-stream" -H "Last-Event-ID: 42" http://localhost:8080/api/v1/tr069-trace/<DEVICE_ID>/exchanges/stream
# Purger les échanges d'un device
curl -X DELETE http://localhost:8080/api/v1/tr069-trace/<DEVICE_ID>/exchanges