wireguard-ui/handler/logs_tail.go

117 lines
3.0 KiB
Go

package handler
import (
"bufio"
"bytes"
"context"
"fmt"
"os"
"os/exec"
"strings"
"time"
"github.com/ngoduykhanh/wireguard-ui/util"
)
// LogsTailEnvVarName is the env var for an optional log file shown on the Logs page.
const LogsTailEnvVarName = "WGUI_LOG_TAIL_PATH"
// SystemLogSection is one rendered block in Logs page.
type SystemLogSection struct {
Title string
Cmd string
Lines []string
}
func tailLines(lines []string, maxLines int) []string {
if maxLines <= 0 || len(lines) <= maxLines {
return lines
}
return lines[len(lines)-maxLines:]
}
func runCommandTail(maxLines int, name string, args ...string) []string {
ctx, cancel := context.WithTimeout(context.Background(), 4*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, name, args...)
out, err := cmd.CombinedOutput()
if err != nil && len(out) == 0 {
return []string{fmt.Sprintf("No disponible: %v", err)}
}
sc := bufio.NewScanner(bytes.NewReader(out))
sc.Split(bufio.ScanLines)
var lines []string
for sc.Scan() {
line := strings.TrimRight(sc.Text(), "\r")
if strings.TrimSpace(line) == "" {
continue
}
lines = append(lines, line)
}
if len(lines) == 0 && err != nil {
lines = []string{fmt.Sprintf("No disponible: %v", err)}
}
return tailLines(lines, maxLines)
}
// ReadSystemLogSections builds practical sections for wg and app logs.
func ReadSystemLogSections(iface string) []SystemLogSection {
if strings.TrimSpace(iface) == "" {
iface = "wg0"
}
wgUnit := "wg-quick@" + iface
return []SystemLogSection{
{
Title: "systemctl status " + wgUnit,
Cmd: "systemctl status " + wgUnit + " --no-pager",
Lines: runCommandTail(140, "systemctl", "status", wgUnit, "--no-pager"),
},
{
Title: "journalctl " + wgUnit,
Cmd: "journalctl -u " + wgUnit + " -n 180 --no-pager --output=short-iso",
Lines: runCommandTail(180, "journalctl", "-u", wgUnit, "-n", "180", "--no-pager", "--output=short-iso"),
},
{
Title: "journalctl wireguard-ui",
Cmd: "journalctl -u wireguard-ui -n 180 --no-pager --output=short-iso",
Lines: runCommandTail(180, "journalctl", "-u", "wireguard-ui", "-n", "180", "--no-pager", "--output=short-iso"),
},
}
}
// ReadLogTailLines reads up to maxLines lines from the end of a file path from LogsTailEnvVarName.
// Lines are trimmed; empty slice if file missing/unreadable or path unset.
func ReadLogTailLines(maxLines int) []string {
p := util.LookupEnvOrString(LogsTailEnvVarName, "")
if p == "" || maxLines <= 0 {
return nil
}
b, err := os.ReadFile(p)
if err != nil || len(b) == 0 {
return nil
}
// Prefer last chunk for large logs
maxBytes := 256 * 1024
start := 0
if len(b) > maxBytes {
start = len(b) - maxBytes
// Align to newline
for start < len(b) && b[start] != '\n' {
start++
}
if start >= len(b) {
start = 0
}
}
s := bufio.NewScanner(bytes.NewReader(b[start:]))
s.Split(bufio.ScanLines)
var lines []string
for s.Scan() {
lines = append(lines, s.Text())
}
if len(lines) <= maxLines {
return lines
}
return lines[len(lines)-maxLines:]
}