feat(gomsg): pymsg/libmsg 协议的 Go 实现,waf logclient 抽取接入

- 新增 gomsg/:帧协议(4B 大端长度 + UTF-8,64MiB 上限)、MsgClient/MsgServer、
  RpcClient/RpcServer(_route 分发、错误脱敏、panic 恢复、json.Number 保精度)、
  AsyncQueue[T]、LogClient(集中式日志,级别过滤 + fail-safe),线协议与 Python/C++ 字节级兼容
- waf/internal/logclient 改为 gomsg 薄适配层(336→20 行),main/engine/proxy 零改动
- gomsg 26 项单测全绿(-race);waf 编译通过
This commit is contained in:
qing committed 2026-08-23 11:21:40 +00:00
1 parent 0ed03ac095
commit c4154d4db9
16 files changed
+1624 -317

No files matched your search

+173
View File
@@ -0,0 +1,173 @@
package gomsg
import (
"encoding/json"
"fmt"
"os"
"time"
)
// 集中式日志客户端:通过 pymsg/TCP 调用 db2(或 db)execute 路由,
// 逐条 INSERT 到 Logs 表。对应各服务目录下的 log_client.py 与 C++ liblogc。
//
// 设计要点(与 waf internal/logclient / Python log_client.py 对齐):
// - 异步非阻塞:AsyncQueue 批量推送,不拖慢业务
// - fail-safe:db 服务不可用时静默丢弃,绝不影响业务
// - 缓冲批量:满 50 条或超时 2 秒自动 flush
const defaultDBAddr = "127.0.0.1:8005"
// 日志级别数值(越小越详细)。
const (
levelDebug = 0
levelInfo = 1
levelWarn = 2
levelError = 3
)
var levelMap = map[string]int{
"debug": levelDebug, "DEBUG": levelDebug,
"info": levelInfo, "INFO": levelInfo,
"warn": levelWarn, "warning": levelWarn, "WARN": levelWarn, "WARNING": levelWarn,
"error": levelError, "ERROR": levelError,
}
// Entry 单条日志。
type Entry struct {
Service string
Level string
Message string
TraceID string
Extra map[string]any
}
// LogClient 异步批量日志推送客户端。
type LogClient struct {
queue *AsyncQueue[Entry]
client *MsgClient
minLevel int
}
// NewLogClient 创建日志客户端并启动后台批量推送。
// dbAddr 为空时依次取环境变量 LOG_DB_ADDR、默认 127.0.0.1:8005;
// 默认最低级别 info,可用 SetLevel 调整(环境变量 LOG_LEVEL 同样生效)。
func NewLogClient(dbAddr string) *LogClient {
if dbAddr == "" {
dbAddr = os.Getenv("LOG_DB_ADDR")
}
if dbAddr == "" {
dbAddr = defaultDBAddr
}
lc := &LogClient{
client: NewMsgClient(dbAddr, 5*time.Second),
minLevel: levelInfo,
}
lc.queue = NewAsyncQueue[Entry](lc.sendBatch, AsyncQueueConfig{})
if lv := os.Getenv("LOG_LEVEL"); lv != "" {
lc.SetLevel(lv)
}
return lc
}
// SetLevel 设置最低日志级别(debug|info|warn|error,大小写不敏感)。
func (c *LogClient) SetLevel(level string) {
if n, ok := levelMap[level]; ok {
c.minLevel = n
}
}
// Push 推送一条日志(非阻塞,队列满或低于最低级别时静默丢弃)。
func (c *LogClient) Push(e Entry) bool {
n, ok := levelMap[e.Level]
if !ok || n < c.minLevel {
return false
}
return c.queue.Push(e)
}
// Debug 推送 DEBUG 级别日志。
func (c *LogClient) Debug(service, message string, extra map[string]any) bool {
return c.Push(Entry{Service: service, Level: "DEBUG", Message: message, Extra: extra})
}
// Info 推送 INFO 级别日志。
func (c *LogClient) Info(service, message string, extra map[string]any) bool {
return c.Push(Entry{Service: service, Level: "INFO", Message: message, Extra: extra})
}
// Warning 推送 WARNING 级别日志。
func (c *LogClient) Warning(service, message string, extra map[string]any) bool {
return c.Push(Entry{Service: service, Level: "WARNING", Message: message, Extra: extra})
}
// Error 推送 ERROR 级别日志。
func (c *LogClient) Error(service, message string, extra map[string]any) bool {
return c.Push(Entry{Service: service, Level: "ERROR", Message: message, Extra: extra})
}
// Flush 停止队列并 flush 剩余日志(用于进程退出前)。
func (c *LogClient) Flush() {
c.queue.Shutdown()
}
// dbV2Request execute 路由的 INSERT 请求体(与 Python/C++ 各 log 客户端一致)。
type dbV2Request struct {
Route string `json:"_route"`
Action string `json:"action"`
Table string `json:"table"`
Values map[string]any `json:"values"`
}
// dbV2Response execute 路由的通用响应。
type dbV2Response struct {
Success bool `json:"success"`
Error string `json:"error,omitempty"`
}
func (c *LogClient) sendBatch(batch []Entry) {
now := time.Now().UTC().Format(time.RFC3339)
for _, e := range batch {
c.sendOne(e, now)
}
}
// sendOne 通过 pymsg TCP 协议发送单条日志到 db2(fail-safe:任何错误静默丢弃)。
func (c *LogClient) sendOne(e Entry, ts string) {
extraJSON := ""
if e.Extra != nil {
b, err := json.Marshal(e.Extra)
if err != nil {
return
}
extraJSON = string(b)
}
values := map[string]any{
"ts": ts,
"service": e.Service,
"level": e.Level,
"message": e.Message,
}
if e.TraceID != "" {
values["trace_id"] = e.TraceID
}
if extraJSON != "" {
values["extra"] = extraJSON
}
req := dbV2Request{Route: "execute", Action: "insert", Table: "Logs", Values: values}
data, err := json.Marshal(req)
if err != nil {
return
}
resp, err := c.client.Call(data)
if err != nil {
return // fail-safe
}
var r dbV2Response
if json.Unmarshal(resp, &r) != nil || !r.Success {
fmt.Fprintf(os.Stderr, "[gomsg:logclient] INSERT failed: %s\n", r.Error)
}
}