129 lines
3.6 KiB
Go
129 lines
3.6 KiB
Go
package api
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"net/http"
|
||
|
|
"path/filepath"
|
||
|
|
"strconv"
|
||
|
|
|
||
|
|
"github.com/havline/havline/internal/config"
|
||
|
|
"github.com/havline/havline/internal/logstore"
|
||
|
|
)
|
||
|
|
|
||
|
|
type LogsHandler struct {
|
||
|
|
cfg config.Config
|
||
|
|
}
|
||
|
|
|
||
|
|
func NewLogsHandler(cfg config.Config) *LogsHandler {
|
||
|
|
return &LogsHandler{cfg: cfg}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (h *LogsHandler) Access(w http.ResponseWriter, r *http.Request) {
|
||
|
|
limit := queryInt(r, "limit", 100)
|
||
|
|
keyword := r.URL.Query().Get("keyword")
|
||
|
|
status := queryInt(r, "status", 0)
|
||
|
|
entries, err := logstore.ReadAccess(filepath.Join(h.cfg.LogsDir(), "access.log"), limit, keyword, status)
|
||
|
|
if err != nil {
|
||
|
|
writeError(r, w, http.StatusInternalServerError, "读取访问日志失败")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
writeJSON(w, http.StatusOK, entries)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (h *LogsHandler) Error(w http.ResponseWriter, r *http.Request) {
|
||
|
|
limit := queryInt(r, "limit", 100)
|
||
|
|
keyword := r.URL.Query().Get("keyword")
|
||
|
|
lines, err := logstore.ReadError(filepath.Join(h.cfg.LogsDir(), "error.log"), limit, keyword)
|
||
|
|
if err != nil {
|
||
|
|
writeError(r, w, http.StatusInternalServerError, "读取错误日志失败")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
writeJSON(w, http.StatusOK, lines)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (h *LogsHandler) System(w http.ResponseWriter, r *http.Request) {
|
||
|
|
limit := queryInt(r, "limit", 100)
|
||
|
|
level := r.URL.Query().Get("level")
|
||
|
|
keyword := r.URL.Query().Get("keyword")
|
||
|
|
entries, err := logstore.ReadSystem(filepath.Join(h.cfg.LogsDir(), "app.log"), limit, level, keyword)
|
||
|
|
if err != nil {
|
||
|
|
writeError(r, w, http.StatusInternalServerError, "读取系统日志失败")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
writeJSON(w, http.StatusOK, entries)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (h *LogsHandler) Stream(w http.ResponseWriter, r *http.Request) {
|
||
|
|
kind := r.URL.Query().Get("type")
|
||
|
|
if kind == "" {
|
||
|
|
kind = "error"
|
||
|
|
}
|
||
|
|
var path string
|
||
|
|
switch kind {
|
||
|
|
case "access":
|
||
|
|
path = filepath.Join(h.cfg.LogsDir(), "access.log")
|
||
|
|
case "error":
|
||
|
|
path = filepath.Join(h.cfg.LogsDir(), "error.log")
|
||
|
|
default:
|
||
|
|
writeError(r, w, http.StatusBadRequest, "实时日志仅支持 Nginx 访问/错误日志")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
w.Header().Set("Content-Type", "text/event-stream")
|
||
|
|
w.Header().Set("Cache-Control", "no-cache")
|
||
|
|
w.Header().Set("Connection", "keep-alive")
|
||
|
|
|
||
|
|
flusher, ok := w.(http.Flusher)
|
||
|
|
if !ok {
|
||
|
|
writeError(r, w, http.StatusInternalServerError, "SSE 不可用")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
tail := queryInt(r, "tail", 100)
|
||
|
|
var filter logstore.LineFilter
|
||
|
|
if kind == "access" {
|
||
|
|
filter = logstore.AccessHostFilter(r.URL.Query()["host"])
|
||
|
|
}
|
||
|
|
_ = logstore.StreamFile(r.Context(), path, w, func() error {
|
||
|
|
flusher.Flush()
|
||
|
|
return nil
|
||
|
|
}, tail, filter)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (h *LogsHandler) StreamAccessForHosts(w http.ResponseWriter, r *http.Request, hosts []string) {
|
||
|
|
endpoints := make([]logstore.AccessEndpoint, 0, len(hosts))
|
||
|
|
for _, host := range hosts {
|
||
|
|
endpoints = append(endpoints, logstore.AccessEndpoint{Host: host})
|
||
|
|
}
|
||
|
|
h.StreamAccessForEndpoints(w, r, endpoints)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (h *LogsHandler) StreamAccessForEndpoints(w http.ResponseWriter, r *http.Request, endpoints []logstore.AccessEndpoint) {
|
||
|
|
w.Header().Set("Content-Type", "text/event-stream")
|
||
|
|
w.Header().Set("Cache-Control", "no-cache")
|
||
|
|
w.Header().Set("Connection", "keep-alive")
|
||
|
|
|
||
|
|
flusher, ok := w.(http.Flusher)
|
||
|
|
if !ok {
|
||
|
|
writeError(r, w, http.StatusInternalServerError, "SSE 不可用")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
path := filepath.Join(h.cfg.LogsDir(), "access.log")
|
||
|
|
tail := queryInt(r, "tail", 100)
|
||
|
|
filter := logstore.AccessEndpointFilter(endpoints)
|
||
|
|
_ = logstore.StreamFile(r.Context(), path, w, func() error {
|
||
|
|
flusher.Flush()
|
||
|
|
return nil
|
||
|
|
}, tail, filter)
|
||
|
|
}
|
||
|
|
|
||
|
|
func queryInt(r *http.Request, key string, fallback int) int {
|
||
|
|
raw := r.URL.Query().Get(key)
|
||
|
|
if raw == "" {
|
||
|
|
return fallback
|
||
|
|
}
|
||
|
|
n, err := strconv.Atoi(raw)
|
||
|
|
if err != nil {
|
||
|
|
return fallback
|
||
|
|
}
|
||
|
|
return n
|
||
|
|
}
|