Files
blackbird/gomsg/logclient.go
T
qing c4154d4db9 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 编译通过
2026-08-23 11:21:40 +00:00

174 lines
4.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
}
}