1. 程式人生 > >gorm的日誌模塊源碼解析

gorm的日誌模塊源碼解析

fmt 如果 delet src level github sso tail pre

gorm的日誌模塊源碼解析

如何讓gorm的日誌按照我的格式進行輸出

這個問題是《如何為gorm日誌加traceId》之後,一個群裏的朋友問我的。如何讓gorm的sql日誌不打印到控制臺,而打印到自己的日誌文件中去。正好我實現了這個功能,就記錄一下,並且再把gorm的logger這個線捋一下。

首先我寫了一個demo來實現設置我自己的Logger。其實非常簡單,只要實現print方法就行了。

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:[email protected](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接口。

輸出就是按照我logger的輸出打印出來了

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貌似是有隱含內容的,裏面的隱含內容有哪些呢?需要追著看下去。

sql的請求怎麽進入到Print中的?

我們在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,afterQueryCallback

然後再結合回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的地方,否則無能為力。

gorm的日誌模塊源碼解析