feat: add telemetry foundation
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
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
|
||||
Reference in New Issue
Block a user