feat(go): add realtime pipeline observability

This commit is contained in:
lingniu
2026-07-03 15:06:27 +08:00
parent 28c81611a1
commit b8299faefa
10 changed files with 636 additions and 6 deletions

View File

@@ -3,7 +3,9 @@ package realtime
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"strconv"
"strings"
"github.com/redis/go-redis/v9"
@@ -15,6 +17,14 @@ type Handler struct {
repository *Repository
}
type OnlineIndexHandler struct {
repository *Repository
}
type PipelineDebugHandler struct {
repository *Repository
}
func NewHandler(repository *Repository) *Handler {
if repository == nil {
panic("realtime repository must not be nil")
@@ -22,6 +32,20 @@ func NewHandler(repository *Repository) *Handler {
return &Handler{repository: repository}
}
func NewOnlineIndexHandler(repository *Repository) *OnlineIndexHandler {
if repository == nil {
panic("online index repository must not be nil")
}
return &OnlineIndexHandler{repository: repository}
}
func NewPipelineDebugHandler(repository *Repository) *PipelineDebugHandler {
if repository == nil {
panic("pipeline debug repository must not be nil")
}
return &PipelineDebugHandler{repository: repository}
}
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
writeError(w, http.StatusMethodNotAllowed, "method not allowed")
@@ -52,6 +76,76 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
}
func (h *OnlineIndexHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
writeError(w, http.StatusMethodNotAllowed, "method not allowed")
return
}
if strings.Trim(r.URL.Path, "/") != "api/realtime/online" {
writeError(w, http.StatusNotFound, "route not found")
return
}
query, err := parseOnlineListQuery(r)
if err != nil {
writeError(w, http.StatusBadRequest, err.Error())
return
}
items, total, err := h.repository.ListOnline(r.Context(), query)
if err != nil {
writeError(w, http.StatusInternalServerError, err.Error())
return
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]any{
"items": items,
"total": total,
"limit": query.Limit,
"offset": query.Offset,
})
}
func (h *PipelineDebugHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
writeError(w, http.StatusMethodNotAllowed, "method not allowed")
return
}
if strings.Trim(r.URL.Path, "/") != "api/debug/pipeline" {
writeError(w, http.StatusNotFound, "route not found")
return
}
summary, err := h.repository.PipelineSummary(r.Context())
writeResult(w, summary, err)
}
func parseOnlineListQuery(r *http.Request) (OnlineListQuery, error) {
values := r.URL.Query()
limit, err := parseBoundedInt(values.Get("limit"), 100, 1, 1000, "limit")
if err != nil {
return OnlineListQuery{}, err
}
offset, err := parseBoundedInt(values.Get("offset"), 0, 0, 1_000_000, "offset")
if err != nil {
return OnlineListQuery{}, err
}
return normalizeOnlineListQuery(OnlineListQuery{
Protocol: envelope.Protocol(values.Get("protocol")),
Limit: limit,
Offset: offset,
}), nil
}
func parseBoundedInt(raw string, fallback int, min int, max int, name string) (int, error) {
raw = strings.TrimSpace(raw)
if raw == "" {
return fallback, nil
}
value, err := strconv.Atoi(raw)
if err != nil || value < min || value > max {
return 0, fmt.Errorf("%s must be between %d and %d", name, min, max)
}
return value, nil
}
func writeResult(w http.ResponseWriter, value any, err error) {
if err != nil {
if errors.Is(err, redis.Nil) {