這個問題是《如何爲gorm日誌加traceId》以後,一個羣裏的朋友問個人。如何讓gorm的sql日誌不打印到控制檯,而打印到本身的日誌文件中去。正好我實現了這個功能,就記錄一下,而且再把gorm的logger這個線捋一下。mysql
首先我寫了一個demo來實現設置我本身的Logger。其實很是簡單,只要實現print方法就好了。git
package main import ( "fmt" "github.com/jinzhu/gorm" _ "github.com/jinzhu/gorm/dialects/mysql" "log" ) type T struct { Id int `gorm:"id"` A int `gorm:"a"` B int `gorm:"b"` } func (T) TableName() string { return "t" } type MyLogger struct { } func (logger *MyLogger) Print(values ...interface{}) { var ( level = values[0] source = values[1] ) if level == "sql" { sql := values[3].(string) log.Println(sql, level, source) } else { log.Println(values) } } func main() { db, _ := gorm.Open("mysql", "root:123456@(192.168.33.10:3306)/mysqldemo?charset=utf8&parseTime=True&loc=Local") defer db.Close() logger := &MyLogger{} db.LogMode(true) db.SetLogger(logger) first := T{} err := db.Find(&first, "id=1").Error if err != nil { panic(err) } fmt.Println(first) }
這裏的mylogger就是實現了gorm.logger接口。github
輸出就是按照我logger的輸出打印出來了sql
2019/04/02 09:11:16 SELECT * FROM `t` WHERE (id=1) sql /Users/yejianfeng/Documents/gopath/src/gorm-log/main.go:50 {1 1 1}
可是這裏有個有點奇怪地方,就是這個Print方法裏面的values貌似是有隱含內容的,裏面的隱含內容有哪些呢?須要追着看下去。架構
咱們在db.Find以前只調用過gorm.Open,db.LogMode,db.SetLogger。後面兩個函數的邏輯又是極其簡單,咱們看到Open裏面。函數
重點在這裏:架構設計
db = &DB{ db: dbSQL, logger: defaultLogger, callbacks: DefaultCallback, dialect: newDialect(dialect, dbSQL), }
這裏的 callbacks 默認是 DefaultCallback。設計
var DefaultCallback = &Callback{} type Callback struct { creates []*func(scope *Scope) updates []*func(scope *Scope) deletes []*func(scope *Scope) queries []*func(scope *Scope) rowQueries []*func(scope *Scope) processors []*CallbackProcessor }
咱們這裏看到的DefaultCallback是空的,可是實際上,它並非空的,在callback_query.go這個文件中有個隱藏的init()函數日誌
func init() { DefaultCallback.Query().Register("gorm:query", queryCallback) DefaultCallback.Query().Register("gorm:preload", preloadCallback) DefaultCallback.Query().Register("gorm:after_query", afterQueryCallback) }
這個init的函數往DefaultCallback.queries裏面註冊了三個毀掉函數,queryCallback,preloadCallback,afterQueryCallbackcode
而後再結合回db.Find的方法
func (s *DB) Find(out interface{}, where ...interface{}) *DB { return s.NewScope(out).inlineCondition(where...).callCallbacks(s.parent.callbacks.queries).db }
咱們看到最終執行的 callCallbacks(s.parent.callbacks.queries) 就是將這三個方法 queryCallback,preloadCallback,afterQueryCallback 逐一調用。
很明顯,這三個方法中,和咱們有關係的就是queryCallback方法。
func queryCallback(scope *Scope) { ... defer scope.trace(NowFunc()) ... }
這裏有個赤裸裸的scope.trace方法
func (scope *Scope) trace(t time.Time) { if len(scope.SQL) > 0 { scope.db.slog(scope.SQL, t, scope.SQLVars...) } } func (s *DB) slog(sql string, t time.Time, vars ...interface{}) { if s.logMode == detailedLogMode { s.print("sql", fileWithLineNum(), NowFunc().Sub(t), sql, vars, s.RowsAffected) } } func (s *DB) print(v ...interface{}) { s.logger.Print(v...) }
找到了,這裏是使用scope.db.slog->db.print->db.logger.Print
這個db.logger就是前面使用SetLogger設置爲MyLogger的地方了。
欣賞下這裏的print這行:
s.print("sql", fileWithLineNum(), NowFunc().Sub(t), sql, vars, s.RowsAffected)
第一個參數爲 level,表示這個是個什麼請求,第二個參數爲打印sql的代碼行號,如/Users/yejianfeng/Documents/gopath/src/gorm-log/main.go:50
, 第三個參數是執行時間戳,第四個參數是sql語句,第五個參數是若是有預處理,請求參數,第六個參數是這個sql影響的行數。
好了,這個邏輯圈畫完了。對照咱們前面的MyLogger的Print,咱們要取出什麼就記錄什麼就好了。
type MyLogger struct { } func (logger *MyLogger) Print(values ...interface{}) { var ( level = values[0] source = values[1] ) if level == "sql" { sql := values[3].(string) log.Println(sql, level, source) } else { log.Println(values) } }
從gorm的log也能大概窺探出gorm的代碼架構設計了。它的幾個結構是核心,DB, Scope, 在Scope中,會註冊各類回調方法,creates,updates, querys等,在諸如Find等函數觸發了回調調用的時候,纔去和真是的DB進行交互。至於日誌,就埋藏在這些回調函數之中。
因此《如何爲gorm日誌加traceId》中若是須要在gorm中增長一個traceId,作不到的緣由就是這個gorm.logger沒有實現SetContext方法,而且在打印的時候沒有將Context輸出到Print的參數中。因此除非修改源碼中調用db.slog的地方,不然無能爲力。