# Schema eventi del `EventLogger` `EventLogger` espone due sink paralleli attivabili via `__init__`: - **JSONL** (`--log-json PATH`) — uno `{"ts":..., "kind":..., "data":{...}}` per riga. - **Markdown** (`--log-md PATH`) — heading `## HH:MM:SS.mmm — kind` seguito da un body specifico del `kind`. Entrambi `flush()` dopo ogni evento (sub‑os buffer vuoto) → adatto a `tail -f`. --- ## Timestamp - **JSONL**: ISO‑8601 UTC con precisione millisecondo + `Z` finale (es. `"2026-07-05T12:34:56.789Z"`). - **MD**: solo l'ora, `HH:MM:SS.mmm`. Entrambi calcolati da `datetime.now(timezone.utc)` non da `time.time()` (locale‑indipendente). --- ## Tipi di evento ### `session_start` (1 per sessione) ```json {"kind": "session_start", "data": {"host": "localhost", "port": 6666, "user": ""}} ``` MD: header `# MUD log — ` con `host`, `port`, `user` poi `---`. ### `session_end` (1 per sessione) ```json {"kind": "session_end", "data": {"reason": "done"}} ``` Possibili `reason`: `done`, `quit`. ### `disconnect` ```json {"kind": "disconnect", "data": {"reason": "local_close"}} ``` Emesso quando `conn.close()` viene chiamato explicitamente (`--quit`, EOF, ecc.). ### `text` ```json {"kind": "text", "data": {"raw": "Inn Hall\n\x1b[36mCobbles.\x1b[0m\n[ Exits: n ]\n"}} ``` Chunk ANSI grezzo dal server, subito dopo la IAC strip. Nel MD: blocco ` ``` ` con testo privato di ANSI via `strip_ansi`. > Attenzione: ANSI con byte non validi vengono preservati nel `raw` > JSONL ma al massimo appaiono come "?" nel markdown. La redazione > non si applica al text (se il server ti invia una password in > chiaro la vedrai). ### `room_enter` ```json {"kind": "room_enter", "data": {"id": "vnum12345", "name": "Inn Hall", "exits": ["n", "e"]}} ``` `id`: - `vnum{int(num)}` se GMCP `Room.Info.num`/`vnum`/`id` era numerico. - altrimenti `name.lower()` (case‑insensitive identity). MD: lista bullet `id` / `name` / `exits`. ### `gmcp` ```json {"kind": "gmcp", "data": {"module": "Char.Vitals", "payload": {"hp": 100, "max_hp": 120}}} ``` `payload` è il JSON decodificato (potrebbe essere `null` se il modulo veniva emesso senza body). MD: blocco ` ```json ` con `payload` ordinato per chiave + indentazione. ### `vitals` ```json {"kind": "vitals", "data": {"hp": 100, "max_hp": 120, "sp": 50, "max_sp": 60, "mv": 80, "max_mv": 100}} ``` Solo i campi effettivamente presenti nel payload GMCP. MD: `HP 100/120 — SP 50/60 — MV 80/100`. ### `line_sent` ```json {"kind": "line_sent", "data": {"text": "north", "redacted": false}} {"kind": "line_sent", "data": {"text": "***", "redacted": true}} ``` `redacted=true` ⇒ la `text` diventa `"***"` (la stringa originale viene scartata). MD: `> … *(redacted)*` se redatto, altrimenti `> …`. ### `local_cmd` ```json {"kind": "local_cmd", "data": {"cmd": "map", "args": [], "redacted": false}} {"kind": "local_cmd", "data": {"cmd": "password", "args": ["***"], "redacted": true}} ``` Se `redacted=true`, `args` è collassato a una lista di `"***"` di pari lunghezza all'originale (anche password multi‑parola). ### `subneg` (forward hook diagnostico) ```json {"kind": "subneg", "data": {"opt": "NAWS", "bytes": 4}} {"kind": "subneg", "data": {"opt": "TTYPE", "bytes": 0}} ``` Emesso da `cli.py` per ogni subnegoziazione telnet **non‑GMCP** ricevuta. `opt` è il nome umano (`ECHO`/`SUPPRESS_GO_AHEAD`/ `TTYPE`/`NAWS`/`GMCP` o `opt_` se sconosciuto) da `_OPT_NAMES`, `bytes` è la lunghezza del payload. Utile in `tail -f` per diagnosticare la negoziazione delle opzioni senza dover attivare `--log DEBUG`. MD: `- **opt**: NAWS` + `- **bytes**: 4`. ### `login` ```json {"kind": "login", "data": {"phase": "user_sent", "value": "alice", "redact": false}} {"kind": "login", "data": {"phase": "password_sent", "value": "***", "redact": true}} {"kind": "login", "data": {"phase": "password_prompt", "value": "", "redact": true}} ``` `phase ∈ {user_sent, password_prompt, password_sent}`. Default `redact=True` ⇒ `value` registrato come `"***"`. Passa `redact=False` esplicitamente per i nomi account (devono essere tracciabili per leggere i log). MD: `**{phase}** → \`{value}\`` (con back‑tick escape). --- ## Esempio di sessione (estratti) ```bash $ jq -c '.' < session.jsonl {"ts":"2026-07-05T12:34:56.789Z","kind":"session_start","data":{"host":"localhost","port":6666,"user":"zork"}} {"ts":"2026-07-05T12:34:57.012Z","kind":"text","data":{"raw":"Welcome to Dead Souls.\n"}} {"ts":"2026-07-05T12:34:57.234Z","kind":"text","data":{"raw":"What name do you wish?\n"}} {"ts":"2026-07-05T12:34:57.235Z","kind":"line_sent","data":{"text":"zork","redacted":false}} {"ts":"2026-07-05T12:34:57.236Z","kind":"login","data":{"phase":"user_sent","value":"zork","redact":false}} {"ts":"2026-07-05T12:34:58.001Z","kind":"text","data":{"raw":"Password:\n"}} {"ts":"2026-07-05T12:34:58.002Z","kind":"login","data":{"phase":"password_prompt","value":"","redact":true}} {"ts":"2026-07-05T12:35:00.500Z","kind":"line_sent","data":{"text":"***","redacted":true}} {"ts":"2026-07-05T12:35:00.501Z","kind":"login","data":{"phase":"password_sent","value":"***","redact":true}} {"ts":"2026-07-05T12:35:01.123Z","kind":"gmcp","data":{"module":"Room.Info","payload":{"num":12345,"name":"Town Square","exits":{"n":12346}}}} {"ts":"2026-07-05T12:35:01.124Z","kind":"room_enter","data":{"id":"vnum12345","name":"Town Square","exits":["n"]}} {"ts":"2026-07-05T12:35:30.000Z","kind":"session_end","data":{"reason":"quit"}} ``` --- ## Helpers di analisi (`jq`) ```bash # solo stanze visitate jq -c 'select(.kind=="room_enter")' session.jsonl # vitals (ultimo snapshot) jq -c 'select(.kind=="vitals")' session.jsonl | tail -1 # righe inviate (escludendo redacted) jq -c 'select(.kind=="line_sent" and .data.redacted==false)' session.jsonl # tutti i comandi locali slash jq -c 'select(.kind=="local_cmd")' session.jsonl ``` --- ## Idempotenza `EventLogger.close()` può essere chiamato più volte senza effetti collaterali. Nel `finally` di `cli._run`, viene chiamato esattamente una volta anche se l'utente fa `Ctrl‑C` due volte. --- ## Note di privacy - Il client **non** logga la password in chiaro. Tutti i punti di emissione relativi a credenziali sono protetti da `redacted=True`. - `text` chunk può contenere **qualsiasi cosa** il server ti invii. Se il MUD fa echo della tua password (raro, perché WILL ECHO è attivo), apparirà nel log. - `gmcp.payload` è decodificato; se un modulo MUD contiene segreti, li vedrai nel log. - Per ambienti condivisi si consiglia `--log-json` su FS cifrato e/o log ruotato con uno scheduler esterno (es. `logrotate`).