diff --git a/tools/rrc-log-dump.py b/tools/rrc-log-dump.py new file mode 100755 index 0000000..30c9bb8 --- /dev/null +++ b/tools/rrc-log-dump.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python3 +"""Dump the contents of rrc on-disk room history logs. + +Usage: + rrc-log-dump.py + + may be: + - a single .log file + - a hub directory under rrc_history/ + - the rrc_history/ root (or any ancestor; .log files are found recursively) +""" + +import os +import sys +import time + +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, os.path.join(SCRIPT_DIR, "..", "nomadnet", "vendor")) +import cbor + + +H_KIND = "k" +H_SRC = "s" +H_NICK = "n" +H_TEXT = "t" +H_TS = "ts" +H_MENTION = "m" + +KIND_COLOR = { + "msg": "", + "system": "\033[2m", + "notice": "\033[36m", + "error": "\033[31m", +} +RESET = "\033[0m" + + +def _fmt_ts(ts_ms): + try: + return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(int(ts_ms) // 1000)) + except Exception: + return "????-??-?? ??:??:??" + + +def _fmt_speaker(entry): + nick = entry.get(H_NICK) + src = entry.get(H_SRC) + src_hex = src.hex()[:12] if isinstance(src, (bytes, bytearray)) else None + if nick and src_hex: + return "%s (%s)" % (nick, src_hex) + if nick: + return nick + if src_hex: + return src_hex + return "" + + +def dump_file(path, use_color): + print("=== %s ===" % path) + count = 0 + truncated = False + with open(path, "rb") as f: + while True: + try: + entry = cbor.load(f) + except EOFError: + break + except Exception as ex: + truncated = True + print(" [corrupt record after entry %d: %s]" % (count, ex), file=sys.stderr) + break + if not isinstance(entry, dict): + print(" [skipping non-dict entry: %r]" % (entry,), file=sys.stderr) + continue + kind = entry.get(H_KIND) or "msg" + ts = _fmt_ts(entry.get(H_TS, 0)) + speaker = _fmt_speaker(entry) + text = entry.get(H_TEXT) or "" + mention = " *" if entry.get(H_MENTION) else " " + color = KIND_COLOR.get(kind, "") if use_color else "" + end = RESET if (color and use_color) else "" + line = "%s %-6s%s %-32s %s" % (ts, kind, mention, speaker, text) + print(color + line + end) + count += 1 + suffix = " (truncated)" if truncated else "" + print("--- %d entries%s ---" % (count, suffix)) + + +def main(argv): + if len(argv) != 2 or argv[1] in ("-h", "--help"): + print(__doc__, file=sys.stderr) + return 2 + target = argv[1] + use_color = sys.stdout.isatty() + if os.path.isfile(target): + dump_file(target, use_color) + return 0 + if os.path.isdir(target): + files = [] + for root, _dirs, names in os.walk(target): + for name in names: + if name.endswith(".log"): + files.append(os.path.join(root, name)) + files.sort() + if not files: + print("no .log files found under %s" % target, file=sys.stderr) + return 1 + for i, path in enumerate(files): + if i > 0: + print() + dump_file(path, use_color) + return 0 + print("not a file or directory: %s" % target, file=sys.stderr) + return 1 + + +if __name__ == "__main__": + sys.exit(main(sys.argv))