From aa84bba69b3d98c0f45e2f98f5da8ffae17f1490 Mon Sep 17 00:00:00 2001 From: hoteas <925970985@qq.com> Date: Sun, 31 May 2026 04:54:12 +0800 Subject: [PATCH] =?UTF-8?q?feat(logging):=20=E5=A2=9E=E5=BC=BA=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E8=AE=B0=E5=BD=95=E5=8A=9F=E8=83=BD=E4=B8=8E=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 redirectStderr 函数,重定向 os.Stderr 到 HoTime Logger,捕获 panic 栈和 MySQL 驱动错误日志 - 实现 mysqlLogAdapter,将 MySQL 驱动的内部错误日志桥接到 HoTime Logger,确保错误信息以 error 级别推送 - 更新 Seq 日志集成,支持 WebConnectLog 的 SeqWriter 配置,提升日志记录的灵活性与准确性 --- application.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/application.go b/application.go index dcb9055..ae669a4 100644 --- a/application.go +++ b/application.go @@ -4,6 +4,7 @@ import ( "bufio" "context" "database/sql" + "fmt" "io/ioutil" stdlog "log" "net" @@ -24,6 +25,7 @@ import ( . "code.hoteas.com/golang/hotime/common" . "code.hoteas.com/golang/hotime/db" "code.hoteas.com/golang/hotime/log" + mysql "github.com/go-sql-driver/mysql" ) type Application struct { @@ -345,14 +347,23 @@ func (that *Application) SetConfig(configPath ...string) { // 重定向 os.Stdout 和标准 log 包,捕获 fmt.Println 等绕过 HoTime Logger 的输出 redirectStdout(that.Log) + // 重定向 os.Stderr,捕获 panic 栈、MySQL driver errLog 等写 stderr 的输出 + // 注意:Logger 在此之前已创建,ConsoleWriter.Out 已绑定到原始 os.Stderr,无循环风险 + redirectStderr(that.Log) // 接入 Seq:instance = ip:port,同机多进程靠端口区分,跨机器靠 IP 区分 if seqUrl := that.Config.GetString("seqUrl"); seqUrl != "" { instance := getLocalIP() + ":" + that.Config.GetString("port") that.Log.SetSeqWriter(seqUrl, that.Config.GetString("seqApiKey"), instance) + if that.WebConnectLog != nil { + that.WebConnectLog.SetSeqWriter(seqUrl, that.Config.GetString("seqApiKey"), instance) + } that.Log.Infof("Seq 日志推送已启动: url=%s instance=%s", seqUrl, instance) } + // 把 MySQL 驱动内部 errLog(连接/包错误)接入 HoTime Logger,经 SeqWriter 推送到 Seq + _ = mysql.SetLogger(&mysqlLogAdapter{l: that.Log}) + } // SetConnectListener 连接判断,返回false继续传输至控制层,true则停止传输 @@ -672,6 +683,31 @@ func redirectStdout(l *log.Logger) { }() } +// redirectStderr 重定向 os.Stderr 到 HoTime Logger(level=error, source=stderr)。 +// 捕获 Go runtime panic 栈、MySQL driver errLog 等直写 stderr 的输出。 +// 必须在 Logger 创建之后调用:ConsoleWriter.Out 已绑定原始 os.Stderr 文件指针, +// 重定向后 ConsoleWriter 仍写真实终端,不会形成循环。 +func redirectStderr(l *log.Logger) { + r, w, err := os.Pipe() + if err != nil { + l.Warnf("[redirectStderr] create pipe failed: %v", err) + return + } + os.Stderr = w + go func() { + scanner := bufio.NewScanner(r) + for scanner.Scan() { + line := scanner.Text() + if line != "" { + l.Error().Str("source", "stderr").Msg(line) + } + } + if err := scanner.Err(); err != nil { + l.Warnf("[redirectStderr] scanner error: %v", err) + } + }() +} + // Init 初始化application func Init(config string) *Application { appIns := Application{} @@ -939,3 +975,11 @@ func setMakeCodeListener(name string, appIns *Application) { return true }) } + +// mysqlLogAdapter 将 go-sql-driver/mysql 的内部 errLog 桥接到 HoTime Logger。 +// MySQL 驱动连接/包级别错误通过此适配器以 error 级别推送到 Seq。 +type mysqlLogAdapter struct{ l *log.Logger } + +func (a *mysqlLogAdapter) Print(v ...interface{}) { + a.l.Error().Str("source", "mysql-driver").Msg(fmt.Sprint(v...)) +}