gorm的日誌模塊源碼解析

gorm的日誌模塊源碼解析

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

這個問題是《如何爲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貌似是有隱含內容的,裏面的隱含內容有哪些呢?須要追着看下去。架構

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,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的地方,不然無能爲力。

相關文章
相關標籤/搜索