Add backend with buffered zstd-compressed report storage
Introduce the Go backend (netwatch-server) with an HTTP API that accepts telemetry reports and persists them as zstd-compressed JSONL files. Reports are buffered in memory and flushed to disk when the buffer reaches 10 MiB or every 60 seconds.
This commit is contained in:
82
backend/internal/handlers/report.go
Normal file
82
backend/internal/handlers/report.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const maxReportBodyBytes = 1 << 20 // 1 MiB
|
||||
|
||||
type reportSample struct {
|
||||
T int64 `json:"t"`
|
||||
Latency *int `json:"latency"`
|
||||
Error *string `json:"error"`
|
||||
}
|
||||
|
||||
type reportHost struct {
|
||||
History []reportSample `json:"history"`
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
type report struct {
|
||||
ClientID string `json:"clientId"`
|
||||
Geo json.RawMessage `json:"geo"`
|
||||
Hosts []reportHost `json:"hosts"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
}
|
||||
|
||||
// HandleReport returns a handler that accepts telemetry
|
||||
// reports from NetWatch clients.
|
||||
func (s *Handlers) HandleReport() http.HandlerFunc {
|
||||
type response struct {
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
r.Body = http.MaxBytesReader(
|
||||
w, r.Body, maxReportBodyBytes,
|
||||
)
|
||||
|
||||
var rpt report
|
||||
|
||||
err := json.NewDecoder(r.Body).Decode(&rpt)
|
||||
if err != nil {
|
||||
s.log.Error("failed to decode report",
|
||||
"error", err,
|
||||
)
|
||||
s.respondJSON(w, r,
|
||||
&response{Status: "error"},
|
||||
http.StatusBadRequest,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
totalSamples := 0
|
||||
for _, h := range rpt.Hosts {
|
||||
totalSamples += len(h.History)
|
||||
}
|
||||
|
||||
s.log.Info("report received",
|
||||
"client_id", rpt.ClientID,
|
||||
"timestamp", rpt.Timestamp,
|
||||
"host_count", len(rpt.Hosts),
|
||||
"total_samples", totalSamples,
|
||||
"geo", string(rpt.Geo),
|
||||
)
|
||||
|
||||
bufErr := s.buf.Append(rpt)
|
||||
if bufErr != nil {
|
||||
s.log.Error("failed to buffer report",
|
||||
"error", bufErr,
|
||||
)
|
||||
}
|
||||
|
||||
s.respondJSON(w, r,
|
||||
&response{Status: "ok"},
|
||||
http.StatusOK,
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user