Files

6.5 KiB
Raw Permalink Blame History

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)

{"kind": "session_start", "data": {"host": "localhost", "port": 6666, "user": ""}}

MD: header # MUD log — <ts> con host, port, user poi ---.

session_end (1 per sessione)

{"kind": "session_end", "data": {"reason": "done"}}

Possibili reason: done, quit.

disconnect

{"kind": "disconnect", "data": {"reason": "local_close"}}

Emesso quando conn.close() viene chiamato explicitamente (--quit, EOF, ecc.).

text

{"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

{"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

{"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

{"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

{"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

{"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)

{"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_<num> 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

{"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)

$ 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)

# 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).