Like Prometheus, but for logs.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
loki/pkg/compactor/ui.go

114 lines
3.0 KiB

package compactor
import (
"encoding/json"
"net/http"
"sort"
"github.com/grafana/dskit/middleware"
"github.com/grafana/loki/v3/pkg/compactor/deletion"
)
func (c *Compactor) Handler() (string, http.Handler) {
mux := http.NewServeMux()
mw := middleware.Merge(
middleware.AuthenticateUser,
deletion.TenantMiddleware(c.limits),
// Automatically parse form data
middleware.Func(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
next.ServeHTTP(w, r)
})
}),
)
// API endpoints
mux.Handle("/compactor/ring", c.ring)
// Custom UI endpoints for the compactor
mux.HandleFunc("/compactor/ui/api/v1/deletes", func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
c.handleListDeleteRequests(w, r)
case http.MethodPost:
mw.Wrap(http.HandlerFunc(c.DeleteRequestsHandler.AddDeleteRequestHandler)).ServeHTTP(w, r)
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
})
return "/compactor", mux
}
type DeleteRequestResponse struct {
RequestID string `json:"request_id"`
StartTime int64 `json:"start_time"`
EndTime int64 `json:"end_time"`
Query string `json:"query"`
Status string `json:"status"`
CreatedAt int64 `json:"created_at"`
UserID string `json:"user_id"`
DeletedLines int32 `json:"deleted_lines"`
}
func (c *Compactor) handleListDeleteRequests(w http.ResponseWriter, r *http.Request) {
status := r.URL.Query().Get("status")
if status == "" {
status = string(deletion.StatusReceived)
}
ctx := r.Context()
if c.deleteRequestsStore == nil {
http.Error(w, "Retention is not enabled", http.StatusBadRequest)
return
}
requests, err := c.deleteRequestsStore.GetAllRequests(ctx)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Filter requests by status
filtered := requests[:0]
for _, req := range requests {
if req.Status == deletion.DeleteRequestStatus(status) {
filtered = append(filtered, req)
}
}
requests = filtered
// Sort by creation time descending
sort.Slice(requests, func(i, j int) bool {
return requests[i].CreatedAt > requests[j].CreatedAt
})
// Take only last 100
if len(requests) > 100 {
requests = requests[:100]
}
response := make([]DeleteRequestResponse, 0, len(requests))
for _, req := range requests {
response = append(response, DeleteRequestResponse{
RequestID: req.RequestID,
StartTime: int64(req.StartTime),
EndTime: int64(req.EndTime),
Query: req.Query,
Status: string(req.Status),
CreatedAt: int64(req.CreatedAt),
UserID: req.UserID,
DeletedLines: req.DeletedLines,
})
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(response); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}