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:
1 parent
0ed03ac095
commit
c4154d4db9
16 files changed
+1624
-317
No files matched your search
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user