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