Files
gotail/backend/internal/handlers/handlers.go

167 lines
4.2 KiB
Go

package handlers
import (
"bufio"
"encoding/json"
"fmt"
"net/http"
"os"
"strconv"
"strings"
"github.com/rs/zerolog/log"
"github.com/seu-usuario/go-react-web-tail/internal/broker"
"github.com/seu-usuario/go-react-web-tail/internal/models"
)
// Handler contém os handlers HTTP da aplicação
type Handler struct {
broker *broker.Broker
logFiles []string
}
// New cria uma nova instância do Handler
func New(broker *broker.Broker, logFiles []string) *Handler {
return &Handler{
broker: broker,
logFiles: logFiles,
}
}
// HandleSSE gerencia conexões Server-Sent Events para streaming de logs
func (h *Handler) HandleSSE(w http.ResponseWriter, r *http.Request) {
flusher, ok := w.(http.Flusher)
if !ok {
http.Error(w, "Streaming unsupported", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
clientChan := h.broker.Subscribe()
defer h.broker.Unsubscribe(clientChan)
log.Info().Str("remote_addr", r.RemoteAddr).Msg("SSE client connected")
for {
select {
case entry := <-clientChan:
data, err := json.Marshal(entry)
if err != nil {
log.Error().Err(err).Msg("Failed to marshal log entry")
continue
}
fmt.Fprintf(w, "data: %s\n\n", data)
flusher.Flush()
case <-r.Context().Done():
log.Info().Str("remote_addr", r.RemoteAddr).Msg("SSE client disconnected")
return
}
}
}
// HandleFiles retorna a lista de arquivos de log monitorados
func (h *Handler) HandleFiles(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(h.logFiles); err != nil {
log.Error().Err(err).Msg("Failed to encode log files")
http.Error(w, "Internal server error", http.StatusInternalServerError)
}
}
// HandleLastLines retorna as últimas N linhas de um arquivo
func (h *Handler) HandleLastLines(w http.ResponseWriter, r *http.Request) {
file := r.URL.Query().Get("file")
nParam := r.URL.Query().Get("n")
n := 200
if nParam != "" {
if parsed, err := strconv.Atoi(nParam); err == nil && parsed > 0 {
n = parsed
}
}
// Validar que o arquivo está na lista permitida
allowed := false
for _, f := range h.logFiles {
if f == file {
allowed = true
break
}
}
if !allowed {
log.Warn().Str("file", file).Str("remote_addr", r.RemoteAddr).Msg("Attempted access to unauthorized file")
http.Error(w, "Invalid file", http.StatusBadRequest)
return
}
entries, err := h.getLastLines(file, n)
if err != nil {
log.Error().Err(err).Str("file", file).Msg("Failed to read last lines")
http.Error(w, "Failed to read file", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(entries); err != nil {
log.Error().Err(err).Msg("Failed to encode entries")
}
}
// getLastLines lê as últimas N linhas de um arquivo
func (h *Handler) getLastLines(filename string, n int) ([]models.LogEntry, error) {
f, err := os.Open(filename)
if err != nil {
return nil, err
}
defer f.Close()
scanner := bufio.NewScanner(f)
buf := make([]string, 0, n)
for scanner.Scan() {
line := scanner.Text()
if len(buf) == n {
buf = buf[1:]
}
buf = append(buf, line)
}
if err := scanner.Err(); err != nil {
return nil, err
}
entries := make([]models.LogEntry, 0, len(buf))
for _, line := range buf {
entry := models.NewLogEntry(filename, strings.TrimRight(line, "\r"))
entries = append(entries, entry)
}
return entries, nil
}
// HandleHealth retorna o status de saúde da aplicação
func (h *Handler) HandleHealth(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{
"status": "healthy",
})
}
// HandleMetrics retorna métricas da aplicação
func (h *Handler) HandleMetrics(w http.ResponseWriter, r *http.Request) {
activeClients, totalBroadcasts := h.broker.GetMetrics()
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"active_clients": activeClients,
"total_broadcasts": totalBroadcasts,
"monitored_files": len(h.logFiles),
})
}