autobrr/internal/http/logs.go
soup 0391629862
chore(license): update copyright year in headers (#1929)
* chore: update copyright year in license headers

* Revert "chore: update copyright year in license headers"

This reverts commit 3e58129c431b9a491089ce36b908f9bb6ba38ed3.

* chore: update copyright year in license headers

* fix: sort go imports

* fix: add missing license headers
2025-01-06 22:23:19 +01:00

147 lines
3.2 KiB
Go

// Copyright (c) 2021 - 2025, Ludvig Lundgren and the autobrr contributors.
// SPDX-License-Identifier: GPL-2.0-or-later
package http
import (
"io/fs"
"net/http"
"os"
"path"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/autobrr/autobrr/internal/config"
"github.com/autobrr/autobrr/internal/logger"
"github.com/dustin/go-humanize"
"github.com/go-chi/chi/v5"
"github.com/go-chi/render"
)
type logsHandler struct {
cfg *config.AppConfig
}
func newLogsHandler(cfg *config.AppConfig) *logsHandler {
return &logsHandler{cfg: cfg}
}
func (h logsHandler) Routes(r chi.Router) {
r.Get("/files", h.files)
r.Get("/files/{logFile}", h.downloadFile)
}
func (h logsHandler) files(w http.ResponseWriter, r *http.Request) {
response := LogfilesResponse{
Files: []LogFile{},
Count: 0,
}
if h.cfg.Config.LogPath == "" {
render.JSON(w, r, response)
return
}
logsDir := path.Dir(h.cfg.Config.LogPath)
// check if dir exists before walkDir
if _, err := os.Stat(logsDir); os.IsNotExist(err) {
render.JSON(w, r, response)
return
}
var walk = func(path string, d fs.DirEntry, err error) error {
if d.IsDir() {
return nil
}
if filepath.Ext(path) == ".log" {
i, err := d.Info()
if err != nil {
return err
}
response.Files = append(response.Files, LogFile{
Name: d.Name(),
SizeBytes: i.Size(),
Size: humanize.Bytes(uint64(i.Size())),
UpdatedAt: i.ModTime(),
})
}
return nil
}
if err := filepath.WalkDir(logsDir, walk); err != nil {
render.Status(r, http.StatusInternalServerError)
render.JSON(w, r, errorResponse{
Message: err.Error(),
Status: http.StatusInternalServerError,
})
return
}
response.Count = len(response.Files)
render.JSON(w, r, response)
}
func (h logsHandler) downloadFile(w http.ResponseWriter, r *http.Request) {
if h.cfg.Config.LogPath == "" {
render.Status(r, http.StatusNotFound)
return
}
logsDir := path.Dir(h.cfg.Config.LogPath)
// check if dir exists before walkDir
if _, err := os.Stat(logsDir); os.IsNotExist(err) {
render.Status(r, http.StatusNotFound)
render.JSON(w, r, errorResponse{
Message: "log directory not found or inaccessible",
Status: http.StatusNotFound,
})
return
}
logFile := chi.URLParam(r, "logFile")
if !strings.Contains(logFile, ".log") {
render.Status(r, http.StatusBadRequest)
render.JSON(w, r, errorResponse{
Message: "invalid log file. Must have .log extension",
Status: http.StatusBadRequest,
})
return
}
filePath := filepath.Join(logsDir, logFile)
w.Header().Set("Content-Disposition", "attachment; filename="+strconv.Quote(logFile))
w.Header().Set("Content-Type", "application/octet-stream")
// Sanitize the log file and directly write the output to the HTTP socket
if err := logger.SanitizeLogFile(filePath, w); err != nil {
render.Status(r, http.StatusInternalServerError)
render.JSON(w, r, errorResponse{
Message: err.Error(),
Status: http.StatusInternalServerError,
})
return
}
}
type LogFile struct {
Name string `json:"filename"`
SizeBytes int64 `json:"size_bytes"`
Size string `json:"size"`
UpdatedAt time.Time `json:"updated_at"`
}
type LogfilesResponse struct {
Files []LogFile `json:"files"`
Count int `json:"count"`
}