From 0890c8136f7bbc89247a8060f1e92aae5c0bac6e Mon Sep 17 00:00:00 2001 From: NanamiAdmin Date: Tue, 28 Apr 2026 15:46:31 +0800 Subject: [PATCH] feat(watchdog): add exception handling and webhook integration - Implement exception handling in TCP client to process error messages - Add webhook functionality to send notifications for exceptions - Introduce utility functions for string parsing - Update config with webhook template --- utils/handlers.go | 33 +++++++++++++++++++++++++++++++++ watchdog/processor.go | 32 ++++++++++++++++++++++++++++++++ watchdog/tcpClient.go | 15 ++++++++++++--- webhook/webhook.go | 39 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 116 insertions(+), 3 deletions(-) create mode 100644 watchdog/processor.go create mode 100644 webhook/webhook.go diff --git a/utils/handlers.go b/utils/handlers.go index e441ad2..1324e7f 100644 --- a/utils/handlers.go +++ b/utils/handlers.go @@ -6,6 +6,7 @@ import ( "net/http" "strconv" "time" + "strings" "super-frpc/database" "super-frpc/global" @@ -128,3 +129,35 @@ func LogRequest(r *http.Request, userID int) { func IntToString(i int) string { return strconv.Itoa(i) } + +func GetTextMiddle(text, left, right string) string { + start := 0 + if left != "" { + idx := strings.Index(text, left) + if idx == -1 { + return "" + } + start = idx + len(left) + } + + if right == "" { + if start > len(text) { + return "" + } + return text[start:] + } + + endIdx := strings.Index(text[start:], right) + if endIdx == -1 { + return "" + } + return text[start : start+endIdx] +} + +func GetCmdType(source string) string { + return GetTextMiddle(source, "[", "]") +} + +func GetCmdParams(source string, param string) string { + return GetTextMiddle(source, "<"+param+">", "") +} diff --git a/watchdog/processor.go b/watchdog/processor.go new file mode 100644 index 0000000..54197f7 --- /dev/null +++ b/watchdog/processor.go @@ -0,0 +1,32 @@ +package watchdog + +import ( + "fmt" + "strings" + + "super-frpc/global" + "super-frpc/webhook" +) + +func exceptionHandle(exceptionType string, serviceName string, errorMsg string) { + body := make(map[string]string) + for k, v := range global.CurrentConfig.Webhook.Body { + replaced := v + for strings.Contains(replaced, "{{ exceptionType }}") || + strings.Contains(replaced, "{{ serviceName }}") || + strings.Contains(replaced, "{{ exceptionMsg }}") { + replaced = strings.ReplaceAll(replaced, "{{ exceptionType }}", exceptionType) + replaced = strings.ReplaceAll(replaced, "{{ serviceName }}", serviceName) + replaced = strings.ReplaceAll(replaced, "{{ exceptionMsg }}", errorMsg) + } + body[k] = replaced + } + + bodyJSON := "" + for k, v := range body { + bodyJSON = fmt.Sprintf(`{"%s": "%s"}`, k, v) + break + } + + webhook.SendHook(global.CurrentConfig.Webhook.URL, global.CurrentConfig.Webhook.Method, global.CurrentConfig.Webhook.Headers, bodyJSON) +} diff --git a/watchdog/tcpClient.go b/watchdog/tcpClient.go index f0f41c5..49fee22 100644 --- a/watchdog/tcpClient.go +++ b/watchdog/tcpClient.go @@ -6,6 +6,7 @@ import ( "net" "strings" "super-frpc/postLog" + "super-frpc/utils" "sync" "time" ) @@ -108,15 +109,23 @@ func recvMsg() { if len(data) > 0 { line := strings.TrimSpace(string(data)) - if len(line) > 0 { select { - case recvChan <- line: + case recvChan <- line: default: + // drop the message } - + // Here add logic to handle the message if !isResponseMessage(line) { postLog.Debug(fmt.Sprintf("[Watchdog] TCP Socket received message: %s", line)) + cmdType := utils.GetCmdType(line) + if cmdType == "Exception" { + exceptionType := utils.GetCmdParams(line, "exceptionType") + serviceName := utils.GetCmdParams(line, "serviceName") + errorMsg := utils.GetCmdParams(line, "errorMsg") + postLog.Error(fmt.Sprintf("[Watchdog] Exception[%s]: %s, %s", serviceName, exceptionType, errorMsg)) + exceptionHandle(exceptionType, serviceName, errorMsg) + } } } } diff --git a/webhook/webhook.go b/webhook/webhook.go new file mode 100644 index 0000000..4309cb1 --- /dev/null +++ b/webhook/webhook.go @@ -0,0 +1,39 @@ +package webhook + +import ( + "fmt" + "io" + "net/http" + "strings" + + "super-frpc/postLog" +) + +// SendHook sends a webhook to the specified URL. +// Returns the error message, status code, and response message. +// If the status code is not 200, it logs the error and returns the status code and response message. +// If the status code is 200, it returns an empty error message, status code, and response message. +func SendHook(url string, method string, headers map[string]string, body string) (err string, code int, msg string) { + req, reqErr := http.NewRequest(method, url, strings.NewReader(body)) + if reqErr != nil { + return reqErr.Error(), 500, "" + } + for k, v := range headers { + req.Header.Set(k, v) + } + resp, doErr := http.DefaultClient.Do(req) + if doErr != nil { + return doErr.Error(), 500, "" + } + defer resp.Body.Close() + respMsg := "" + if resp.Body != nil { + respBody, _ := io.ReadAll(resp.Body) + respMsg = string(respBody) + } + if resp.StatusCode != http.StatusOK { + postLog.Debug(fmt.Sprintf("[SendHook] SendHook { %s, %s, %s, %s } failed, status code: %d, response: [%s]: %s", url, method, headers, body, resp.StatusCode, resp.Status, respMsg)) + return fmt.Sprintf("unexpected status code: %d, response: [%s]: %s", resp.StatusCode, resp.Status, respMsg), resp.StatusCode, respMsg + } + return "", 200, "" +}