Files
Radicade/Tools/player-log-window.py
T
2026-10-16 23:33:13 -07:00

101 lines
3.2 KiB
Python

#!/usr/bin/env python3
"""Slice the client's Player.log by wall-clock window and summarise it.
The Unity log is one flat file per launch whose lines start with
`[HH:MM:SS] [frame] [seconds] [Level] [Tag] ...`; anything that matters after a
test is usually a handful of error lines inside a one-minute window.
Usage:
python tools/player-log-window.py --from 14:21 --to 14:23 [--errors]
python tools/player-log-window.py --grep "studio|rrs" --from 14:22:30
python tools/player-log-window.py --log <path> --from ... --to ... --show
Options:
--errors dedupe and count Error/Warning/Exception lines instead of printing all
--show print every matching line (default when a --grep matches)
--tail N additionally print the last N raw lines of the file
"""
import argparse
import os
import re
import sys
DEFAULT_LOG = os.path.join(
os.environ.get("LOCALAPPDATA", ""), "..", "LocalLow",
"Against Gravity", "Rec Room", "Player.log")
LINE_RE = re.compile(r"^\[(\d\d:\d\d:\d\d(?:\.\d+)?)\]")
def pad(value, tail):
"""`14:22` means the whole minute, `14:22:30` the whole second."""
if not value:
return None
return value if value.count(":") == 2 else value + tail
def in_window(stamp, start, stop):
if start and stamp < start:
return False
if stop and stamp > stop:
return False
return True
def main(argv):
parser = argparse.ArgumentParser()
parser.add_argument("--log", default=os.path.normpath(DEFAULT_LOG))
parser.add_argument("--from", dest="start")
parser.add_argument("--to", dest="stop")
parser.add_argument("--grep")
parser.add_argument("--errors", action="store_true")
parser.add_argument("--show", action="store_true")
parser.add_argument("--tail", type=int, default=0)
args = parser.parse_args(argv[1:])
with open(args.log, "rb") as handle:
blob = handle.read().decode("utf-8", "replace")
lines = blob.split("\n")
print(f"{args.log}: {len(lines)} lines, {len(blob)} bytes")
pattern = re.compile(args.grep, re.I) if args.grep else None
selected = []
for text in lines:
match = LINE_RE.match(text)
if not match:
continue
if not in_window(match.group(1), pad(args.start, ":00"), pad(args.stop, ":59")):
continue
if pattern and not pattern.search(text):
continue
if args.errors and not re.search(r"\[(Error|Warning)\]|Exception:|Error:", text):
continue
selected.append(text.rstrip())
if args.errors or not (args.show or pattern):
counts = {}
order = []
for text in selected:
key = re.sub(r"\d+", "#", text[:220])
if key not in counts:
counts[key] = 0
order.append(key)
counts[key] += 1
for key in order:
print(f" x{counts[key]:<4} {key}")
else:
for text in selected:
print(" " + text[:300])
print(f"--- {len(selected)} selected line(s)")
if args.tail:
print(f"--- last {args.tail} lines ---")
for text in [t for t in lines if t.strip()][-args.tail:]:
print(" " + text[:300])
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv))