79 lines
2.2 KiB
Python
79 lines
2.2 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
from dataclasses import asdict, dataclass, field
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
|
|
|
|
@dataclass
|
|
class TelemetryEvent:
|
|
"""A single structured telemetry event."""
|
|
timestamp: str
|
|
event_type: str
|
|
target: str
|
|
details: dict = field(default_factory=dict)
|
|
|
|
def to_dict(self) -> dict:
|
|
return asdict(self)
|
|
|
|
|
|
def _session_log_path(root: Path) -> Path:
|
|
"""Return the path to the session-log.jsonl file under .rdb/."""
|
|
rdb_dir = root / ".rdb"
|
|
rdb_dir.mkdir(parents=True, exist_ok=True)
|
|
return rdb_dir / "session-log.jsonl"
|
|
|
|
|
|
def record_event(root: Path, event_type: str, target: str, details: dict | None = None) -> TelemetryEvent:
|
|
"""Record a telemetry event to the session log.
|
|
|
|
Creates the .rdb/session-log.jsonl file if it does not exist.
|
|
Each call appends one JSON line to the file.
|
|
|
|
Args:
|
|
root: Project root directory.
|
|
event_type: Category of event, e.g. 'command', 'task', 'guardrail'.
|
|
target: The entity the event relates to, e.g. 'rdb prompt'.
|
|
details: Optional extra key-value pairs for context.
|
|
|
|
Returns:
|
|
The TelemetryEvent that was recorded.
|
|
"""
|
|
event = TelemetryEvent(
|
|
timestamp=datetime.now(timezone.utc).isoformat(),
|
|
event_type=event_type,
|
|
target=target,
|
|
details=details or {},
|
|
)
|
|
|
|
log_path = _session_log_path(root)
|
|
with open(log_path, "a", encoding="utf-8") as f:
|
|
f.write(json.dumps(event.to_dict()) + "\n")
|
|
|
|
return event
|
|
|
|
|
|
def read_events(root: Path) -> list[TelemetryEvent]:
|
|
"""Read all events from the session log.
|
|
|
|
Returns an empty list if the log does not yet exist or is empty.
|
|
Malformed lines are silently skipped.
|
|
"""
|
|
log_path = _session_log_path(root)
|
|
if not log_path.exists():
|
|
return []
|
|
|
|
events: list[TelemetryEvent] = []
|
|
for line in log_path.read_text(encoding="utf-8").splitlines():
|
|
line = line.strip()
|
|
if not line:
|
|
continue
|
|
try:
|
|
data = json.loads(line)
|
|
events.append(TelemetryEvent(**data))
|
|
except (json.JSONDecodeError, TypeError):
|
|
pass
|
|
|
|
return events
|