package adapter import ( "bytes" "context" "encoding/json" "net/http" "time" ) // HTTPTelemetry POSTs to the control plane, per docs/adapter-contract.md. // It must never carry subscriber identity or post body text; callers control // that by what they put in fields. type HTTPTelemetry struct { ControlURL string hc *http.Client } // NewHTTPTelemetry builds a telemetry emitter posting to // /control/v1/telemetry. func NewHTTPTelemetry(controlURL string) *HTTPTelemetry { return &HTTPTelemetry{ControlURL: controlURL, hc: &http.Client{Timeout: 10 * time.Second}} } func (t *HTTPTelemetry) Emit(ctx context.Context, event string, fields map[string]any) { if t.ControlURL == "" { return } payload := map[string]any{"event": event, "fields": fields} body, err := json.Marshal(payload) if err != nil { return } req, err := http.NewRequestWithContext(ctx, http.MethodPost, t.ControlURL+"/control/v1/telemetry", bytes.NewReader(body)) if err != nil { return } req.Header.Set("Content-Type", "application/json") // Best-effort: a dropped telemetry event must never fail the publication // it describes. resp, err := t.hc.Do(req) if err != nil { return } _ = resp.Body.Close() }