feat(logging): implement client log streaming and management
All checks were successful
Build Multi-Platform Binaries / build-frontend (push) Successful in 30s
Build Multi-Platform Binaries / build-binaries (amd64, darwin, server, false) (push) Successful in 1m16s
Build Multi-Platform Binaries / build-binaries (amd64, linux, client, true) (push) Successful in 1m4s
Build Multi-Platform Binaries / build-binaries (amd64, linux, server, true) (push) Successful in 2m29s
Build Multi-Platform Binaries / build-binaries (amd64, windows, client, true) (push) Successful in 54s
Build Multi-Platform Binaries / build-binaries (amd64, windows, server, true) (push) Successful in 2m35s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, client, true) (push) Successful in 55s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, server, true) (push) Successful in 2m21s
Build Multi-Platform Binaries / build-binaries (arm64, darwin, server, false) (push) Successful in 1m35s
Build Multi-Platform Binaries / build-binaries (arm64, linux, client, true) (push) Successful in 1m1s
Build Multi-Platform Binaries / build-binaries (arm64, linux, server, true) (push) Successful in 1m55s
Build Multi-Platform Binaries / build-binaries (arm64, windows, server, false) (push) Successful in 1m39s

- Added log streaming functionality for clients, allowing real-time log access via SSE.
- Introduced LogHandler to manage log streaming requests and responses.
- Implemented LogSessionManager to handle active log sessions and listeners.
- Enhanced protocol with log-related message types and structures.
- Created Logger for client-side logging, supporting various log levels and file output.
- Developed LogViewer component for the web interface to display and filter logs.
- Updated API to support log stream creation and management.
- Added support for querying logs by level and searching through log messages.
This commit is contained in:
2026-01-03 16:19:52 +08:00
parent d2ca3fa2b9
commit 2f98e1ac7d
15 changed files with 1311 additions and 12 deletions

View File

@@ -47,6 +47,7 @@ type Client struct {
runningPlugins map[string]plugin.ClientPlugin
versionStore *PluginVersionStore
pluginMu sync.RWMutex
logger *Logger // 日志收集器
}
// NewClient 创建客户端
@@ -59,12 +60,19 @@ func NewClient(serverAddr, token, id string) *Client {
home, _ := os.UserHomeDir()
dataDir := filepath.Join(home, ".gotunnel")
// 初始化日志收集器
logger, err := NewLogger(dataDir)
if err != nil {
log.Printf("[Client] Failed to initialize logger: %v", err)
}
return &Client{
ServerAddr: serverAddr,
Token: token,
ID: id,
DataDir: dataDir,
runningPlugins: make(map[string]plugin.ClientPlugin),
logger: logger,
}
}
@@ -236,6 +244,10 @@ func (c *Client) handleStream(stream net.Conn) {
c.handlePluginConfigUpdate(stream, msg)
case protocol.MsgTypeUpdateDownload:
c.handleUpdateDownload(stream, msg)
case protocol.MsgTypeLogRequest:
go c.handleLogRequest(stream, msg)
case protocol.MsgTypeLogStop:
c.handleLogStop(stream, msg)
}
}
@@ -900,3 +912,79 @@ func restartClientProcess(path, serverAddr, token, id string) {
cmd.Start()
os.Exit(0)
}
// handleLogRequest 处理日志请求
func (c *Client) handleLogRequest(stream net.Conn, msg *protocol.Message) {
if c.logger == nil {
stream.Close()
return
}
var req protocol.LogRequest
if err := msg.ParsePayload(&req); err != nil {
stream.Close()
return
}
c.logger.Printf("Log request received: session=%s, follow=%v", req.SessionID, req.Follow)
// 发送历史日志
entries := c.logger.GetRecentLogs(req.Lines, req.Level)
if len(entries) > 0 {
data := protocol.LogData{
SessionID: req.SessionID,
Entries: entries,
EOF: !req.Follow,
}
respMsg, _ := protocol.NewMessage(protocol.MsgTypeLogData, data)
if err := protocol.WriteMessage(stream, respMsg); err != nil {
stream.Close()
return
}
}
// 如果不需要持续推送,关闭流
if !req.Follow {
stream.Close()
return
}
// 订阅新日志
ch := c.logger.Subscribe(req.SessionID)
defer c.logger.Unsubscribe(req.SessionID)
defer stream.Close()
// 持续推送新日志
for entry := range ch {
// 应用级别过滤
if req.Level != "" && entry.Level != req.Level {
continue
}
data := protocol.LogData{
SessionID: req.SessionID,
Entries: []protocol.LogEntry{entry},
EOF: false,
}
respMsg, _ := protocol.NewMessage(protocol.MsgTypeLogData, data)
if err := protocol.WriteMessage(stream, respMsg); err != nil {
return
}
}
}
// handleLogStop 处理停止日志流请求
func (c *Client) handleLogStop(stream net.Conn, msg *protocol.Message) {
defer stream.Close()
if c.logger == nil {
return
}
var req protocol.LogStopRequest
if err := msg.ParsePayload(&req); err != nil {
return
}
c.logger.Unsubscribe(req.SessionID)
}