testsAndMisc/python_pkg/wsg_grabber/_logs_report.py
Krzysztof kuhy Rudnicki 8ce453690f Split wsg_grabber/cli.py under the 250-line cap
The `logs` subcommand's reporting -- show_logs() and summarise() -- is
read-only analysis of the JSONL session logs and shares nothing with the
scrape/review flow, so it moves to _logs_report.py.

cli.py re-exports both through __all__ so cli.show_logs / cli.summarise stay
valid for test_cli.py and the dispatch table. The PEP 484 `X as X` alias form
does NOT survive here: ruff flags it PLC0414, autofixes it away, and then
deletes the import as unused under F401 -- which would break cli.summarise
with lint still green. Recorded in the handoff so it is not re-derived.

cli.py 282 -> 247, new _logs_report.py 76. 277 wsg_grabber tests pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 15:39:10 +02:00

77 lines
2.2 KiB
Python

"""The ``logs`` subcommand: where session logs live and what is in the newest.
Split out of :mod:`python_pkg.wsg_grabber.cli` to keep that module under the
250-line cap. This is read-only reporting over the JSONL session logs and
shares nothing with the scrape/review flow beyond the log directory itself.
"""
from __future__ import annotations
from collections import Counter
import json
import sys
from typing import TYPE_CHECKING
from python_pkg.wsg_grabber import logs
if TYPE_CHECKING:
from pathlib import Path
def _emit(message: str) -> None:
"""Write a line to stdout.
Args:
message: Line to write, without a trailing newline.
"""
sys.stdout.write(f"{message}\n")
def show_logs() -> int:
"""Print where logs live and summarise the most recent session.
Returns:
int: Exit status.
"""
directory = logs.logs_dir()
_emit(f"log directory {directory}")
sessions = sorted(directory.glob("session-*.jsonl"))
if not sessions:
_emit("no session logs yet")
return 0
newest = sessions[-1]
_emit(f"newest session {newest}")
_emit(f"sessions kept {len(sessions)}")
_emit("")
_emit(summarise(newest))
_emit("")
_emit("Useful queries:")
_emit(f" jq -c 'select(.level==\"error\")' {newest}")
_emit(f" jq -r 'select(.event==\"review.show\")|.file' {newest}")
_emit(f" jq -c 'select(.event|startswith(\"player.\"))' {newest}")
return 0
def summarise(path: Path) -> str:
"""Count events by name and level in one session log.
Args:
path: A session JSONL file.
Returns:
str: A short human-readable tally.
"""
by_event: Counter[str] = Counter()
by_level: Counter[str] = Counter()
for line in path.read_text(encoding="utf-8").splitlines():
try:
record = json.loads(line)
except ValueError:
continue
by_event[str(record.get("event"))] += 1
by_level[str(record.get("level"))] += 1
levels = " ".join(f"{name}={count}" for name, count in sorted(by_level.items()))
lines = [f"records by level: {levels or 'none'}", "events:"]
lines.extend(f" {name:<24} {count}" for name, count in by_event.most_common(15))
return "\n".join(lines)