Files
dee1ed6bf9 MUL-7236: add anonymous self-host telemetry (#8252)
* MUL-7236 add anonymous self-host telemetry

Co-authored-by: multica-agent <[email protected]>

* fix(telemetry): apply review feedback

Co-authored-by: multica-agent <[email protected]>

* fix(telemetry): address review nits

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: Sol-Boy <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-09-15 15:21:11 +08:00

86 lines
2.1 KiB
Go

package selfhosttelemetry
import (
"bytes"
"context"
"net/http"
"time"
)
const (
productionEndpoint = "https://telemetry.multica.ai/v1/telemetry/events"
httpTimeout = 5 * time.Second
)
type deliveryDisposition int
const (
deliveryRetry deliveryDisposition = iota
deliverySuccess
deliveryStopForDay
)
type eventSender interface {
Send(context.Context, []byte) deliveryDisposition
Close()
}
// HTTPClient only knows the fixed first-party V1 endpoint. The unexported test
// constructor below permits local fake servers without creating a deployment
// configuration surface that could redirect production telemetry.
type HTTPClient struct {
endpoint string
client *http.Client
}
func newHTTPClient() *HTTPClient {
transport := http.DefaultTransport.(*http.Transport).Clone()
return &HTTPClient{
endpoint: productionEndpoint,
client: &http.Client{
Transport: transport,
Timeout: httpTimeout,
// Never forward the snapshot to a redirect target. Endpoint ownership
// is part of the privacy boundary, not merely a convenience default.
CheckRedirect: func(*http.Request, []*http.Request) error {
return http.ErrUseLastResponse
},
},
}
}
func newHTTPClientForTest(endpoint string, client *http.Client) *HTTPClient {
return &HTTPClient{endpoint: endpoint, client: client}
}
func (c *HTTPClient) Send(ctx context.Context, body []byte) deliveryDisposition {
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.endpoint, bytes.NewReader(body))
if err != nil {
return deliveryStopForDay
}
req.Header.Set("Content-Type", "application/json")
resp, err := c.client.Do(req)
if err != nil {
return deliveryRetry
}
// The response body is deliberately never read or logged.
_ = resp.Body.Close()
switch {
case resp.StatusCode == http.StatusAccepted:
return deliverySuccess
case resp.StatusCode == http.StatusTooManyRequests || resp.StatusCode >= 500:
return deliveryRetry
default:
return deliveryStopForDay
}
}
func (c *HTTPClient) Close() {
if c == nil || c.client == nil {
return
}
c.client.CloseIdleConnections()
}