Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions internal/api/admin_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"strconv"
"strings"
"time"

"github.com/RandomCodeSpace/otelcontext/internal/httpconst"
)

// handleGetStats handles GET /api/stats
Expand All @@ -18,7 +20,7 @@ func (s *Server) handleGetStats(w http.ResponseWriter, r *http.Request) {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.Header().Set(httpconst.HeaderContentType, httpconst.ContentTypeJSON)
_ = json.NewEncoder(w).Encode(stats)
}

Expand Down Expand Up @@ -50,7 +52,7 @@ func (s *Server) handlePurge(w http.ResponseWriter, r *http.Request) {

slog.Info("Admin purge completed", "days", days, "logs_purged", logsDeleted, "traces_purged", tracesDeleted)

w.Header().Set("Content-Type", "application/json")
w.Header().Set(httpconst.HeaderContentType, httpconst.ContentTypeJSON)
_ = json.NewEncoder(w).Encode(map[string]any{
"logs_purged": logsDeleted,
"traces_purged": tracesDeleted,
Expand All @@ -65,7 +67,7 @@ func (s *Server) handleVacuum(w http.ResponseWriter, _ *http.Request) {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.Header().Set(httpconst.HeaderContentType, httpconst.ContentTypeJSON)
_ = json.NewEncoder(w).Encode(map[string]string{"status": "vacuumed"})
}

Expand Down Expand Up @@ -110,7 +112,7 @@ func (s *Server) handleDropFTS(w http.ResponseWriter, r *http.Request) {
}
slog.Info("drop_fts completed", "elapsed_ms", elapsed.Milliseconds(), "reclaimed_bytes", reclaimed)

w.Header().Set("Content-Type", "application/json")
w.Header().Set(httpconst.HeaderContentType, httpconst.ContentTypeJSON)
_ = json.NewEncoder(w).Encode(map[string]any{
"reclaimed_bytes": reclaimed,
"elapsed_ms": elapsed.Milliseconds(),
Expand Down
7 changes: 4 additions & 3 deletions internal/api/log_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"time"

"github.com/RandomCodeSpace/otelcontext/internal/api/views"
"github.com/RandomCodeSpace/otelcontext/internal/httpconst"
"github.com/RandomCodeSpace/otelcontext/internal/realtime"
"github.com/RandomCodeSpace/otelcontext/internal/storage"
)
Expand Down Expand Up @@ -67,7 +68,7 @@ func (s *Server) handleGetLogs(w http.ResponseWriter, r *http.Request) {
return
}

w.Header().Set("Content-Type", "application/json")
w.Header().Set(httpconst.HeaderContentType, httpconst.ContentTypeJSON)
_ = json.NewEncoder(w).Encode(map[string]any{
"data": views.LogsFromModels(logs),
"total": total,
Expand Down Expand Up @@ -96,7 +97,7 @@ func (s *Server) handleGetLogContext(w http.ResponseWriter, r *http.Request) {
return
}

w.Header().Set("Content-Type", "application/json")
w.Header().Set(httpconst.HeaderContentType, httpconst.ContentTypeJSON)
_ = json.NewEncoder(w).Encode(views.LogsFromModels(logs))
}

Expand All @@ -120,7 +121,7 @@ func (s *Server) handleGetLogInsight(w http.ResponseWriter, r *http.Request) {
return
}

w.Header().Set("Content-Type", "application/json")
w.Header().Set(httpconst.HeaderContentType, httpconst.ContentTypeJSON)
_ = json.NewEncoder(w).Encode(map[string]string{"insight": string(l.AIInsight)})
}

Expand Down
15 changes: 8 additions & 7 deletions internal/api/metrics_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"time"

"github.com/RandomCodeSpace/otelcontext/internal/api/views"
"github.com/RandomCodeSpace/otelcontext/internal/httpconst"
)

// handleGetTrafficMetrics handles GET /api/metrics/traffic
Expand Down Expand Up @@ -35,7 +36,7 @@ func (s *Server) handleGetTrafficMetrics(w http.ResponseWriter, r *http.Request)
return
}

w.Header().Set("Content-Type", "application/json")
w.Header().Set(httpconst.HeaderContentType, httpconst.ContentTypeJSON)
_ = json.NewEncoder(w).Encode(points)
}

Expand Down Expand Up @@ -64,7 +65,7 @@ func (s *Server) handleGetLatencyHeatmap(w http.ResponseWriter, r *http.Request)
return
}

w.Header().Set("Content-Type", "application/json")
w.Header().Set(httpconst.HeaderContentType, httpconst.ContentTypeJSON)
_ = json.NewEncoder(w).Encode(points)
}

Expand Down Expand Up @@ -94,7 +95,7 @@ func (s *Server) handleGetDashboardStats(w http.ResponseWriter, r *http.Request)
return
}

w.Header().Set("Content-Type", "application/json")
w.Header().Set(httpconst.HeaderContentType, httpconst.ContentTypeJSON)
_ = json.NewEncoder(w).Encode(views.DashboardStatsFromModel(stats))
}

Expand All @@ -121,7 +122,7 @@ func (s *Server) handleGetServiceMapMetrics(w http.ResponseWriter, r *http.Reque
return
}

w.Header().Set("Content-Type", "application/json")
w.Header().Set(httpconst.HeaderContentType, httpconst.ContentTypeJSON)
_ = json.NewEncoder(w).Encode(views.ServiceMapMetricsFromModel(metrics))
}

Expand Down Expand Up @@ -149,7 +150,7 @@ func (s *Server) handleGetMetricBuckets(w http.ResponseWriter, r *http.Request)
return
}

w.Header().Set("Content-Type", "application/json")
w.Header().Set(httpconst.HeaderContentType, httpconst.ContentTypeJSON)
_ = json.NewEncoder(w).Encode(views.MetricBucketsFromModels(buckets))
}

Expand All @@ -164,7 +165,7 @@ func (s *Server) handleGetMetricNames(w http.ResponseWriter, r *http.Request) {
return
}

w.Header().Set("Content-Type", "application/json")
w.Header().Set(httpconst.HeaderContentType, httpconst.ContentTypeJSON)
_ = json.NewEncoder(w).Encode(names)
}

Expand All @@ -186,6 +187,6 @@ func (s *Server) handleGetServices(w http.ResponseWriter, r *http.Request) {
if services == nil {
services = []string{}
}
w.Header().Set("Content-Type", "application/json")
w.Header().Set(httpconst.HeaderContentType, httpconst.ContentTypeJSON)
_ = json.NewEncoder(w).Encode(services)
}
2 changes: 1 addition & 1 deletion internal/graphrag/drain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ func TestConcurrentMatchRaceFree(t *testing.T) {
defer wg.Done()
for i := 0; i < per; i++ {
line := fmt.Sprintf("worker %d processed request id=%d from 10.0.0.%d", wid, i, i%250)
if tpl := d.Match(line, ts); tpl == nil {
if d.Match(line, ts) == nil {
t.Errorf("nil template")
return
}
Expand Down
13 changes: 13 additions & 0 deletions internal/httpconst/httpconst.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Package httpconst centralises HTTP header names and content-type strings
// shared by the API, MCP, and OTLP-HTTP handlers so the same literal isn't
// duplicated across packages.
package httpconst

const (
// HeaderContentType is the canonical HTTP Content-Type header name.
HeaderContentType = "Content-Type"

// ContentTypeJSON is the application/json content type used by every JSON
// response on the API and MCP surface.
ContentTypeJSON = "application/json"
)
15 changes: 10 additions & 5 deletions internal/ingest/otlp_http.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ import (
// downstream a chance to drain.
const defaultRetryAfterSeconds = 1

// headerContentType is the canonical HTTP Content-Type header name. Local
// const so the OTLP-HTTP receiver compiles without pulling in a shared
// helper for a one-line literal.
const headerContentType = "Content-Type" //nolint:goconst // single literal; Sonar S1192 satisfied via const

// withTenantFromHTTP attaches a tenant ID from the X-Tenant-ID header (if any)
// to the request context before delegating to the gRPC Export methods.
// Uses the shared storage.WithTenantContext helper so ingest and read paths
Expand Down Expand Up @@ -269,7 +274,7 @@ func (h *HTTPHandler) readBody(r *http.Request) ([]byte, error) {

// unmarshal decodes the body based on Content-Type header.
func (h *HTTPHandler) unmarshal(r *http.Request, body []byte, msg proto.Message) error {
ct := r.Header.Get("Content-Type")
ct := r.Header.Get(headerContentType)
switch ct {
case contentTypeProtobuf, "":
if err := proto.Unmarshal(body, msg); err != nil {
Expand All @@ -287,9 +292,9 @@ func (h *HTTPHandler) unmarshal(r *http.Request, body []byte, msg proto.Message)

// writeResponse marshals and writes the OTLP response.
func (h *HTTPHandler) writeResponse(w http.ResponseWriter, r *http.Request, msg proto.Message) {
ct := r.Header.Get("Content-Type")
ct := r.Header.Get(headerContentType)
if ct == contentTypeJSON {
w.Header().Set("Content-Type", contentTypeJSON)
w.Header().Set(headerContentType, contentTypeJSON)
data, err := protojson.Marshal(msg)
if err != nil {
writeOTLPError(w, http.StatusInternalServerError, "failed to marshal response")
Expand All @@ -298,7 +303,7 @@ func (h *HTTPHandler) writeResponse(w http.ResponseWriter, r *http.Request, msg
w.WriteHeader(http.StatusOK)
_, _ = w.Write(data)
} else {
w.Header().Set("Content-Type", contentTypeProtobuf)
w.Header().Set(headerContentType, contentTypeProtobuf)
data, err := proto.Marshal(msg)
if err != nil {
writeOTLPError(w, http.StatusInternalServerError, "failed to marshal response")
Expand All @@ -321,7 +326,7 @@ func writeOTLPError(w http.ResponseWriter, statusCode int, msg string) {
http.Error(w, msg, statusCode)
return
}
w.Header().Set("Content-Type", contentTypeProtobuf)
w.Header().Set(headerContentType, contentTypeProtobuf)
w.WriteHeader(statusCode)
_, _ = w.Write(data)
}
2 changes: 1 addition & 1 deletion internal/ingest/otlp_http_backpressure_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ func TestHTTPBackpressure_TracesReturns429WithRetryAfter(t *testing.T) {
if rec.Code != http.StatusTooManyRequests {
t.Fatalf("want 429, got %d (body=%q)", rec.Code, rec.Body.String())
}
if got := rec.Header().Get("Retry-After"); got == "" {
if rec.Header().Get("Retry-After") == "" {
t.Fatal("Retry-After header missing on 429 response")
}
if ct := rec.Header().Get("Content-Type"); ct != contentTypeProtobuf {
Expand Down
7 changes: 4 additions & 3 deletions internal/mcp/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/RandomCodeSpace/central-ops/pkg/httputil"
"github.com/RandomCodeSpace/otelcontext/internal/graph"
"github.com/RandomCodeSpace/otelcontext/internal/graphrag"
"github.com/RandomCodeSpace/otelcontext/internal/httpconst"
"github.com/RandomCodeSpace/otelcontext/internal/storage"
"github.com/RandomCodeSpace/otelcontext/internal/telemetry"
"github.com/RandomCodeSpace/otelcontext/internal/vectordb"
Expand Down Expand Up @@ -209,7 +210,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {

// handleRPC processes JSON-RPC 2.0 requests.
func (s *Server) handleRPC(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Header().Set(httpconst.HeaderContentType, httpconst.ContentTypeJSON)

body, err := io.ReadAll(io.LimitReader(r.Body, 1<<20)) // 1 MB cap
if err != nil {
Expand Down Expand Up @@ -321,7 +322,7 @@ func (s *Server) handleRPC(w http.ResponseWriter, r *http.Request) {
case "resources/list":
result = map[string]any{
"resources": []map[string]any{
{"uri": "OtelContext://system/graph", "name": "System Graph", "mimeType": "application/json"},
{"uri": "OtelContext://system/graph", "name": "System Graph", "mimeType": httpconst.ContentTypeJSON},
{"uri": "OtelContext://metrics/prometheus", "name": "Prometheus Metrics", "mimeType": "text/plain"},
},
}
Expand Down Expand Up @@ -406,7 +407,7 @@ func writeSSE(w http.ResponseWriter, f http.Flusher, event, data string) {

// writeError writes a JSON-RPC error response.
func writeError(w http.ResponseWriter, id any, code int, msg string) {
w.Header().Set("Content-Type", "application/json")
w.Header().Set(httpconst.HeaderContentType, httpconst.ContentTypeJSON)
resp := JSONRPCResponse{
JSONRPC: "2.0",
ID: id,
Expand Down
Loading
Loading