From 5366eb0d29d8f942f001791d827eb72f90915cbf Mon Sep 17 00:00:00 2001 From: kaveh <12169025+freeb5d@users.noreply.github.com> Date: Sat, 3 Oct 2026 02:51:25 +0330 Subject: [PATCH] Bound the panel syslog view with a journalctl timeout (#6689) * Bound the syslog view with a journalctl timeout * fix(syslog): bound the journal scan window and soften the timeout message Limit journalctl to the last 30 days so a rare -p level cannot scan the whole journal, and drop the guessed cause and the host-wide vacuum advice from the timeout message. * fix(syslog): drop --since from the journalctl call and test the timeout On systemd 249/252 (Ubuntu 22.04, Debian 12) journalctl seeks to --since and reads forward when both --since and -n are given, so the Syslog view showed the oldest 200 lines of the 30-day window instead of the newest. Reproduced in debian:12, ubuntu:22.04 and ubuntu:24.04 containers on a synthetic journal; only 255 kept the newest lines. The window also bought nothing: on a 340 MB journal every variant (with or without --since, rare -p level or not) answered in ~10 ms on 252 and 255. Keep the 15s deadline as the guard against a stalled journalctl and cover it with a fake journalctl on PATH; the args test pinned the broken flag and is removed. --------- Co-authored-by: Sanaei --- internal/web/service/server.go | 10 ++++++- internal/web/service/server_syslog_test.go | 33 ++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 internal/web/service/server_syslog_test.go diff --git a/internal/web/service/server.go b/internal/web/service/server.go index f5dad3f94..6c782b67c 100644 --- a/internal/web/service/server.go +++ b/internal/web/service/server.go @@ -1184,6 +1184,9 @@ func (s *ServerService) UpdateXray(version string) error { return nil } +// syslogTimeout keeps a stalled journalctl from hanging the Syslog request (#6629). +var syslogTimeout = 15 * time.Second + func (s *ServerService) GetLogs(count string, level string, syslog string) []string { c, _ := strconv.Atoi(count) var lines []string @@ -1216,10 +1219,15 @@ func (s *ServerService) GetLogs(count string, level string, syslog string) []str } // Use hardcoded command with validated parameters - cmd := exec.CommandContext(context.Background(), "journalctl", "-u", "x-ui", "--no-pager", "-n", strconv.Itoa(countInt), "-p", level) + ctx, cancel := context.WithTimeout(context.Background(), syslogTimeout) + defer cancel() + cmd := exec.CommandContext(ctx, "journalctl", "-u", "x-ui", "--no-pager", "-n", strconv.Itoa(countInt), "-p", level) var out bytes.Buffer cmd.Stdout = &out err = cmd.Run() + if errors.Is(ctx.Err(), context.DeadlineExceeded) { + return []string{"journalctl did not answer in time. Try a smaller line count or a less strict level."} + } if err != nil { return []string{"Failed to run journalctl command! Make sure systemd is available and x-ui service is registered."} } diff --git a/internal/web/service/server_syslog_test.go b/internal/web/service/server_syslog_test.go new file mode 100644 index 000000000..64766f42b --- /dev/null +++ b/internal/web/service/server_syslog_test.go @@ -0,0 +1,33 @@ +//go:build !windows + +package service + +import ( + "os" + "path/filepath" + "slices" + "testing" + "time" +) + +// A journalctl that never answers must not hang the Syslog request (#6629). +func TestGetLogsSyslogGivesUpOnAStalledJournalctl(t *testing.T) { + dir := t.TempDir() + if err := os.WriteFile(filepath.Join(dir, "journalctl"), []byte("#!/bin/sh\nexec sleep 5\n"), 0o755); err != nil { + t.Fatalf("write fake journalctl: %v", err) + } + t.Setenv("PATH", dir+string(os.PathListSeparator)+os.Getenv("PATH")) + saved := syslogTimeout + syslogTimeout = 200 * time.Millisecond + t.Cleanup(func() { syslogTimeout = saved }) + + start := time.Now() + got := (&ServerService{}).GetLogs("10", "err", "true") + want := []string{"journalctl did not answer in time. Try a smaller line count or a less strict level."} + if !slices.Equal(got, want) { + t.Fatalf("GetLogs = %q, want %q", got, want) + } + if elapsed := time.Since(start); elapsed > 3*time.Second { + t.Fatalf("GetLogs waited %v for a stalled journalctl, want about %v", elapsed, syslogTimeout) + } +}