cq-test / app /services /tracing.py
NANI-Nithin's picture
Phase 3: add live session intelligence
3ee7cb0
Raw
History Blame Contribute Delete
5.14 kB
"""Logging and tracing module for pipeline transparency.
Stores JSONL event logs and complete pipeline traces for debugging,
demo narration, and possible trace publication.
"""
import json
import uuid
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Optional
# Valid event types matching the event schema
VALID_EVENT_TYPES = [
"task_revealed",
"task_completed",
"hint_used",
"task_skipped",
"photo_uploaded",
"journal_recorded",
"score_updated",
"game_finished",
]
def _ensure_log_dir(log_dir: str) -> Path:
"""Ensure the log directory exists and return its Path."""
path = Path(log_dir)
path.mkdir(parents=True, exist_ok=True)
return path
def _now_iso() -> str:
"""Return current time as ISO-8601 string."""
return datetime.now(timezone.utc).isoformat()
def log_event(
session_id: str,
event_type: str,
payload: dict,
team_id: str = "team-a",
log_dir: str = "app/logs",
) -> dict:
"""Log a gameplay event to a JSONL file.
Each event is appended as a single JSON line to ``events.jsonl`` inside
*log_dir*. Returns the full event dict that was written so callers can
pass it onward (e.g. into the scoring pipeline).
Args:
session_id: Session identifier.
event_type: One of the ``VALID_EVENT_TYPES``.
payload: Event-specific data dictionary.
team_id: Team that generated the event (default ``"team-a"``).
log_dir: Directory to store logs.
Returns:
The complete event dict that was persisted.
Raises:
ValueError: If *event_type* is not in the allowed set.
"""
if event_type not in VALID_EVENT_TYPES:
raise ValueError(
f"Invalid event_type '{event_type}'. "
f"Must be one of: {VALID_EVENT_TYPES}"
)
event = {
"event_id": str(uuid.uuid4()),
"timestamp": _now_iso(),
"session_id": session_id,
"team_id": team_id,
"event_type": event_type,
"payload": payload,
}
log_path = _ensure_log_dir(log_dir) / "events.jsonl"
with open(log_path, "a", encoding="utf-8") as fh:
fh.write(json.dumps(event, ensure_ascii=False) + "\n")
return event
def load_events(session_id: Optional[str] = None, log_dir: str = "app/logs") -> list[dict]:
"""Load events from the JSONL log file, optionally filtered by session.
Args:
session_id: If provided, only return events for this session.
log_dir: Directory containing event logs.
Returns:
List of event dicts.
"""
log_path = Path(log_dir) / "events.jsonl"
if not log_path.exists():
return []
events: list[dict] = []
with open(log_path, "r", encoding="utf-8") as fh:
for line in fh:
line = line.strip()
if not line:
continue
try:
event = json.loads(line)
except json.JSONDecodeError:
continue
if session_id and event.get("session_id") != session_id:
continue
events.append(event)
return events
def log_generation_trace(
session_id: str,
config: dict,
retrieved_examples: list[dict],
game: dict,
validation_passed: bool,
validation_failures: list[str],
repaired_game: Optional[dict] = None,
log_dir: str = "app/logs",
) -> dict:
"""Log the full generation pipeline trace for one session.
Captures config → retrieval → generation → validation → repair in a
single JSON file for debugging and publication.
Args:
session_id: Session identifier.
config: User-provided game configuration.
retrieved_examples: Examples retrieved by the grounding module.
game: Generated game JSON.
validation_passed: Whether the game passed validation.
validation_failures: Any validation failure messages.
repaired_game: Post-repair game dict, or None if no repair was needed.
log_dir: Directory to store logs.
Returns:
The complete trace dict.
"""
trace = {
"session_id": session_id,
"timestamp": _now_iso(),
"config": config,
"retrieved_examples": retrieved_examples,
"generated_game": game,
"validation_passed": validation_passed,
"validation_failures": validation_failures,
"repaired_game": repaired_game,
}
trace_path = _ensure_log_dir(log_dir) / f"trace_{session_id}.json"
with open(trace_path, "w", encoding="utf-8") as fh:
json.dump(trace, fh, indent=2, ensure_ascii=False)
return trace
def save_trace(trace_data: dict, output_path: str) -> None:
"""Save a complete pipeline trace for debugging and publication.
Args:
trace_data: Dictionary containing all pipeline data.
output_path: Where to save the trace file.
"""
out = Path(output_path)
out.parent.mkdir(parents=True, exist_ok=True)
with open(out, "w", encoding="utf-8") as fh:
json.dump(trace_data, fh, indent=2, ensure_ascii=False)