|
|
@@ -9,7 +9,9 @@ import (
|
|
|
"net/http"
|
|
|
"os"
|
|
|
"os/exec"
|
|
|
+ "strconv"
|
|
|
"strings"
|
|
|
+ "sync"
|
|
|
"time"
|
|
|
|
|
|
"hnyfkj.com.cn/rtu/linux/baseapp"
|
|
|
@@ -141,6 +143,8 @@ func (ui *WebUI) rootHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
http.NotFound(w, r)
|
|
|
}
|
|
|
|
|
|
+var logCmds sync.Map /* pid -> *exec.Cmd, 用于管理日志流进程, 以便在前端关闭日志流时, 后端可以
|
|
|
+ 关闭对应的日志流进程, 解决frpc代理后, sse连接断开时, 日志流进程无法关闭的问题, 占用PID资源 */
|
|
|
var logFiles = map[string]string{
|
|
|
"yfkj-camera-capture.service": "/opt/yfkj/camera-capture.service/log/camera-capture.log",
|
|
|
}
|
|
|
@@ -200,12 +204,19 @@ func serviceLogHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
+ pid := cmd.Process.Pid
|
|
|
+ logCmds.Store(pid, cmd)
|
|
|
+
|
|
|
id := time.Now().UnixNano()
|
|
|
if true {
|
|
|
baseapp.Logger.Tracef("[服务日志流启动] id=%d unit=%s", id, unit)
|
|
|
}
|
|
|
|
|
|
+ fmt.Fprintf(w, "event: pid\ndata: %d\n\n", pid)
|
|
|
+ flusher.Flush() // 发送PID给前端, 让前端在关闭日志流时, 可以通知后端关闭对应的日志流进程
|
|
|
+
|
|
|
defer func() {
|
|
|
+ logCmds.Delete(pid)
|
|
|
if cmd.Process != nil {
|
|
|
err := cmd.Process.Kill()
|
|
|
baseapp.Logger.Tracef("[journalctl kill] pid=%d err=%v", cmd.Process.Pid, err)
|
|
|
@@ -242,7 +253,7 @@ func serviceLogHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
if !ok {
|
|
|
return
|
|
|
}
|
|
|
- if _, err := fmt.Fprintf(w, "data: %s\n\n", line); err != nil {
|
|
|
+ if _, err := fmt.Fprintf(w, "data: %s\n\n", line); err != nil { // 实时推送日志
|
|
|
return
|
|
|
}
|
|
|
flusher.Flush()
|
|
|
@@ -250,6 +261,48 @@ func serviceLogHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+func serviceLogCloseHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
+ pid, err := strconv.Atoi(r.URL.Query().Get("pid"))
|
|
|
+ if err != nil || pid <= 0 {
|
|
|
+ http.Error(w, "invalid pid", http.StatusBadRequest)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ v, ok := logCmds.Load(pid)
|
|
|
+ if !ok {
|
|
|
+ http.Error(w, "pid not found", http.StatusNotFound)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ cmd := v.(*exec.Cmd)
|
|
|
+ if cmd.Process == nil {
|
|
|
+ logCmds.Delete(pid)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ data, err := os.ReadFile(fmt.Sprintf("/proc/%d/cmdline", pid))
|
|
|
+ if err != nil {
|
|
|
+ http.Error(w, "read cmdline failed", http.StatusInternalServerError)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ cmdline := strings.ReplaceAll(string(data), "\x00", " ")
|
|
|
+ if !strings.Contains(cmdline, "journalctl") && !strings.Contains(cmdline, "tail") {
|
|
|
+ http.Error(w, "not log process", http.StatusBadRequest) // 只允许关闭日志流进程, 防止误杀其它进程
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ logCmds.Delete(pid)
|
|
|
+ err = cmd.Process.Kill()
|
|
|
+ baseapp.Logger.Tracef("[服务日志流关闭] pid=%d cmd=%s err=%v", pid, cmdline, err)
|
|
|
+ if err != nil {
|
|
|
+ http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ w.WriteHeader(http.StatusOK)
|
|
|
+}
|
|
|
+
|
|
|
func runShellAndWrite(w http.ResponseWriter, cmd string) {
|
|
|
baseapp.Logger.Tracef("[执行命令] %s", cmd)
|
|
|
|