diff --git a/RECAP.md b/RECAP.md index 9736cf6..51cf895 100644 --- a/RECAP.md +++ b/RECAP.md @@ -164,10 +164,10 @@ non. Patrick a piloté à l'écran, l'agent a observé. Une capture par étape d `flutter run` **n'imprime pas** les traces `dart:developer log()` — elles partent au VM Service, ni sur stdout ni dans logcat. Tout ce que `NymeaService._log()` journalise était -donc invisible. Le pont `scratchpad/vm_logs.dart` s'abonne au flux `Logging` du VM Service -et rend la trace. **Sans lui, aucun des constats ci-dessous n'était atteignable.** À -récupérer au prochain passage (attention : horodater sur `event.timestamp`, pas à la -réception — le VM Service livre l'historique bufférisé d'un coup). +donc invisible. Le pont **`tools/devlog/vm_logs.dart`** s'abonne au flux `Logging` du VM +Service et rend la trace. **Sans lui, aucun des constats ci-dessous n'était atteignable.** +Mode d'emploi en tête du fichier, piège d'horodatage compris (lire `event.timestamp`, pas +l'heure de réception : le VM Service livre l'historique bufférisé d'un coup). ### Critères diff --git a/tools/devlog/vm_logs.dart b/tools/devlog/vm_logs.dart new file mode 100644 index 0000000..83078ab --- /dev/null +++ b/tools/devlog/vm_logs.dart @@ -0,0 +1,58 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +// Capte les traces `dart:developer log()` de l'app en cours d'exécution. +// +// `flutter run` ne les imprime PAS sur stdout (elles partent au VM Service, pas à +// stdout ni à logcat). Sans ce pont, tout ce que `NymeaService._log()` journalise est +// invisible depuis le poste — donc les traces de locale, de télémétrie et d'écriture +// aussi. +// +// Usage : dart vm_logs.dart ws://127.0.0.1:PORT/TOKEN=/ws +Future main(List args) async { + final uri = args.first; + final ws = await WebSocket.connect(uri); + var id = 0; + void call(String method, [Map params = const {}]) { + ws.add(jsonEncode({ + 'jsonrpc': '2.0', + 'id': (++id).toString(), + 'method': method, + 'params': params, + })); + } + + ws.listen((raw) { + final m = jsonDecode(raw as String) as Map; + if (m['method'] != 'streamNotify') return; + final ev = (m['params'] as Map)['event'] as Map; + final kind = ev['kind']; + // L'heure de l'ENREGISTREMENT, pas celle de la réception : le VM Service livre + // l'historique bufférisé d'un coup au moment du streamListen, et horodater à la + // réception écraserait toute la chronologie sous une seule seconde. + final micros = (ev['timestamp'] as num?)?.toInt(); + final ts = micros == null + ? '--:--:--' + : DateTime.fromMillisecondsSinceEpoch(micros) + .toIso8601String() + .substring(11, 23); + if (kind == 'Logging') { + final rec = ev['logRecord'] as Map; + final name = (rec['loggerName'] as Map?)?['valueAsString'] ?? ''; + final msg = (rec['message'] as Map?)?['valueAsString'] ?? ''; + print('$ts [$name] $msg'); + } else if (kind == 'WriteEvent') { + final bytes = ev['bytes'] as String?; + if (bytes != null) { + stdout.write('$ts [stdout] ${utf8.decode(base64.decode(bytes))}'); + } + } + }); + + call('streamListen', {'streamId': 'Logging'}); + call('streamListen', {'streamId': 'Stdout'}); + call('streamListen', {'streamId': 'Stderr'}); + print('--- branché sur $uri ---'); + await Future.delayed(const Duration(hours: 2)); +}