Files
sand-boxer/src/sandboxer/telemetry/export.py
tegwick c0a9261cdc Implement SAND-WP-0008: host telemetry and self-canary
Add profile.sandbox-canary, HostSnapshot/inventory/stale schemas, SSH
collectors, before/after provision deltas, telemetry export to State Hub
and local JSON, default `sandboxer create` self-deploy, inspect/reap-stale
CLI, runbook, and CoulombCore verification (26 tests pass).
2026-06-23 19:53:51 +02:00

64 lines
1.8 KiB
Python

"""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)