Files
kandev/apps/backend/internal/system/logs/handler.go
T

90 lines
2.2 KiB
Go

package logs
import (
"errors"
"fmt"
"io"
"net/http"
"os"
"strconv"
"github.com/gin-gonic/gin"
)
// Default and maximum line counts for the tail endpoint.
const (
defaultTailLines = 1000
maxTailLines = 10_000
)
// HandleList renders GET /api/v1/system/logs: the directory listing.
func HandleList(s *Service) gin.HandlerFunc {
return func(c *gin.Context) {
files, err := s.List()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"files": files})
}
}
// HandleTail renders GET /api/v1/system/logs/tail?n=<lines>. n defaults to
// 1000 and is capped at 10000. Invalid values silently fall back to the
// default — the endpoint is read-only and lenient.
func HandleTail(s *Service) gin.HandlerFunc {
return func(c *gin.Context) {
n := resolveTailN(c.Query("n"))
lines, err := s.Tail(n)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"lines": lines})
}
}
// HandleDownload streams a single log file. The :name path parameter is
// validated to be a bare filename within the configured log directory;
// anything else (path traversal, separators, missing file) returns 4xx.
func HandleDownload(s *Service) gin.HandlerFunc {
return func(c *gin.Context) {
name := c.Param("name")
f, size, err := s.Open(name)
if err != nil {
status := http.StatusBadRequest
if errors.Is(err, os.ErrNotExist) {
status = http.StatusNotFound
}
c.JSON(status, gin.H{"error": err.Error()})
return
}
defer func() { _ = f.Close() }()
c.Header("Content-Type", "application/octet-stream")
c.Header("Content-Length", strconv.FormatInt(size, 10))
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%q", name))
c.Status(http.StatusOK)
_, _ = io.Copy(c.Writer, f)
}
}
// resolveTailN parses ?n=… , falling back to the default for empty or
// unparseable values, and clamping to [0, maxTailLines].
func resolveTailN(raw string) int {
if raw == "" {
return defaultTailLines
}
v, err := strconv.Atoi(raw)
if err != nil {
return defaultTailLines
}
if v < 0 {
return 0
}
if v > maxTailLines {
return maxTailLines
}
return v
}