"""Telemetry export sinks.""" from __future__ import annotations import json import os from pathlib import Path from typing import Protocol import httpx from sandboxer.lifecycle.state_hub import hub_url from sandboxer.telemetry.models import IntrospectionReport class TelemetrySink(Protocol): """Future export target (artifact-store, Prometheus, ClickHouse).""" def publish(self, report: IntrospectionReport) -> None: ... def telemetry_dir() -> Path: base = Path(os.environ.get("XDG_DATA_HOME", Path.home() / ".local" / "share")) path = base / "sandboxer" / "telemetry" path.mkdir(parents=True, exist_ok=True) return path def export_local_artifact(report: IntrospectionReport) -> Path: path = telemetry_dir() / f"{report.sandbox_id}.json" path.write_text(json.dumps(report.model_dump(mode="json"), indent=2, default=str)) return path def export_state_hub(report: IntrospectionReport) -> dict | None: if os.environ.get("SANDBOXER_NO_STATE_HUB", "").lower() in ("1", "true", "yes"): return None payload = { "event_type": "note", "summary": ( f"Telemetry {report.sandbox_id}: load Δ " f"{report.provision_delta.load_1m_delta if report.provision_delta else 0}, " f"stale={len(report.stale_candidates)}" ), "author": "sandboxer", "detail": report.model_dump(mode="json"), } try: response = httpx.post(f"{hub_url()}/progress/", json=payload, timeout=10.0) response.raise_for_status() return response.json() except httpx.HTTPError: return None def export_telemetry(report: IntrospectionReport) -> Path: path = export_local_artifact(report) export_state_hub(report) return path class NoopTelemetrySink: def publish(self, report: IntrospectionReport) -> None: export_telemetry(report)