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...)) +}