Beego Logs 源碼分析 上篇

Beego Logs 使用

先大體瞭解怎麼使用,再進行剖析。git

// Test console without color
    func TestConsoleNoColor(t *testing.T) {
        log := NewLogger(100)
        log.SetLogger("console", `{"color":false}`)
        bl.Error("error")
        bl.Warning("warning")
    }
// NewLogger returns a new BeeLogger.
    // channelLen means the number of messages in 
    // chan(used where asynchronous is true).
    // if the buffering chan is full, logger adapters write to file or other way.
    func NewLogger(channelLens ...int64) *BeeLogger {
        bl := new(BeeLogger)
        bl.level = LevelDebug
        bl.loggerFuncCallDepth = 2
        bl.msgChanLen = append(channelLens, 0)[0]
        if bl.msgChanLen <= 0 {
            bl.msgChanLen = defaultAsyncMsgLen
        }
        bl.signalChan = make(chan string, 1)
        bl.setLogger(AdapterConsole)
        return bl
    }

上面有一句代碼:github

bl.msgChanLen = append(channelLens, 0)[0]

往 channelLens 切片添加一個值爲零的元素後再取頭個元素,這個技巧有如下好處:數組

  • Go 不支持可選參數,但 Go 支持可變參數,這樣作變相達到了可選參數的效果。
  • 若是 chanelLens 原來爲空的話也能拿出一個值爲零的元素出來,不用再去判斷參數是否爲空數組。

loggerFuncCallDepth 的值應設爲多少

這個變量表示函數調用的棧深度,用於記錄日誌時同時打印出當時執行語句的位置,包括文件名和行號。
雖然 NewLogger 方法裏面默認將 loggerFuncCallDepth 置爲2,可是若是你單獨使用logs包時應根據狀況設置不一樣值。舉個栗子:緩存

···
bl.Error("error")  // ----------a 語句
···

// Error Log ERROR level message.
func (bl *BeeLogger) Error(format string, v ...interface{}) {
    if LevelError > bl.level {
        return
    }
    bl.writeMsg(LevelError, format, v...) // ----------b 語句
}

func (bl *BeeLogger) writeMsg(logLevel int, msg string, v ...interface{}) error {
    ···
    if bl.enableFuncCallDepth {
        _, file, line, ok := runtime.Caller(bl.loggerFuncCallDepth) // ----------c 語句
        ···
    }
    ···
}

func Caller(skip int) (pc uintptr, file string, line int, ok bool) {
    ···
}

關於 Caller 方法的 skip 參數:安全

The argument skip is the number of stack frames to ascend, with 0 identifying the caller of Caller. (For historical reasons the meaning of skip differs between Caller and Callers.)

即,skip 爲零的時候,表示 Caller 方法自己,而咱們須要的是 a 語句的所在的行號和文件名,因此這種情境下須要提高 2 個棧幀數。服務器

工廠方法模式自定義日誌輸出引擎

如下是添加 console 輸出引擎的用法,直接調用 SetLogger 方法便可。session

func TestConsole(t *testing.T) {
    ···
    log.SetLogger("console", `{"color":false}`)
    ···
}
type newLoggerFunc func() Logger

var adapters = make(map[string]newLoggerFunc)


func (bl *BeeLogger) SetLogger(adapterName string, configs ...string) error {
    ···
    return bl.setLogger(adapterName, configs...)
}

func (bl *BeeLogger) setLogger(adapterName string, configs ...string) error {
    ···
    log, ok := adapters[adapterName]
    if !ok {
        return fmt.Errorf("logs: unknown adaptername %q (forgotten Register?)", adapterName)
    }

    lg := log() //--------- c 語句
    err := lg.Init(config)
    if err != nil {
        fmt.Fprintln(os.Stderr, "logs.BeeLogger.SetLogger: "+err.Error())
        return err
    }
    bl.outputs = append(bl.outputs, &nameLogger{name: adapterName, Logger: lg})
    return nil
}

func Register(name string, log newLoggerFunc) {
    ···
    adapters[name] = log
}
在工廠方法模式中,核心的工廠類再也不負責全部產品的建立,而是將具體建立工做交給子類去作。

上面 c 語句能夠看到,具體須要用到什麼輸出引擎,BeeLogger 不負責它們的建立,而是由這些輸出引擎本身去作。從 adapters 這個 map 結構裏找到該輸出引擎的構造方法, 而且執行這個構造方法。架構

例如 file.go 裏面定義瞭如何構造一個文件輸出引擎,並經過 init 方法註冊:併發

func init() {
    Register(AdapterFile, newFileWriter)
}

// newFileWriter create a FileLogWriter returning as LoggerInterface.
func newFileWriter() Logger {
    w := &fileLogWriter{
        Daily:      true,
        MaxDays:    7,
        Rotate:     true,
        RotatePerm: "0440",
        Level:      LevelTrace,
        Perm:       "0660",
    }
    return w
}

爲何要用到互斥鎖?

直接找到如下四處代碼段:app

func (bl *BeeLogger) Async(msgLen ...int64) *BeeLogger {
    bl.lock.Lock()
    defer bl.lock.Unlock()
    ···
}
func (bl *BeeLogger) SetLogger(adapterName string, configs ...string) error {
    bl.lock.Lock()
    defer bl.lock.Unlock()
    ···
}
func (bl *BeeLogger) DelLogger(adapterName string) error {
    bl.lock.Lock()
    defer bl.lock.Unlock()
    ···
}
func (bl *BeeLogger) writeMsg(logLevel int, msg string, v ...interface{}) error {
    if !bl.init {
        bl.lock.Lock()
        bl.setLogger(AdapterConsole)
        bl.lock.Unlock()
    }
    ···
}

能夠看出,在進行 SetLogger 、 DelLogger 這些操做時涉及到臨界資源 bl *BeeLogger 相關配置字段的更改,必須操做前加鎖保證併發安全。

臨界資源是指每次僅容許一個進程訪問的資源。

Asynchronous 選項爲何能提高性能

func (bl *BeeLogger) writeMsg(logLevel int, msg string, v ...interface{}) error {
    ···
    if bl.asynchronous {
        lm := logMsgPool.Get().(*logMsg)
        lm.level = logLevel
        lm.msg = msg
        lm.when = when
        bl.msgChan <- lm
    } else {
        bl.writeToLoggers(when, msg, logLevel)
    }
    return nil
}

若是開啓 asynchronous 選項,將日誌信息寫進 msgChan 就完事了,能夠繼續執行其餘的邏輯代碼,除非 msgChan 緩存滿了,不然不會發生阻塞,同時,還開啓一個 goroutine 監聽 msgChan,一旦 msgChan 不爲空,將日誌信息輸出:

func (bl *BeeLogger) Async(msgLen ...int64) *BeeLogger {
    ···
    go bl.startLogger()
    ···
}

// start logger chan reading.
// when chan is not empty, write logs.
func (bl *BeeLogger) startLogger() {
    gameOver := false
    for {
        select {
        case bm := <-bl.msgChan:
            bl.writeToLoggers(bm.when, bm.msg, bm.level)
            logMsgPool.Put(bm)
        case sg := <-bl.signalChan:
            // Now should only send "flush" or "close" to bl.signalChan
            bl.flush()
            if sg == "close" {
                for _, l := range bl.outputs {
                    l.Destroy()
                }
                bl.outputs = nil
                gameOver = true
            }
            bl.wg.Done()
        }
        if gameOver {
            break
        }
    }
}

從 logs package 外的 log.go 文件瞭解 beego 如何解耦

在 logs 包(package)外面還有一個 beego package 下的 log.go 文件,截取一段代碼:

// github.com/astaxie/beego/log.go

    package beego
    
    import "github.com/astaxie/beego/logs"
    
    // BeeLogger references the used application logger.
    var BeeLogger = logs.GetBeeLogger()
    
    // SetLevel sets the global log level used by the simple logger.
    func SetLevel(l int) {
        logs.SetLevel(l)
    }
// github.com/astaxie/beego/logs/log.go

// beeLogger references the used application logger.
var beeLogger = NewLogger()

// GetBeeLogger returns the default BeeLogger
func GetBeeLogger() *BeeLogger {
    return beeLogger
}

// SetLevel sets the global log level used by the simple logger.
func SetLevel(l int) {
    beeLogger.SetLevel(l)
}

beego 爲何還在外面包了一層調用 logs 包裏面的方法呢?其實 beego 自己是一個 Web 框架,那麼本質就是一個服務端程序,服務端程序須要一個日誌記錄器來記錄服務器的運行情況,那麼調用 logs 包的代碼以及其餘一些配置、初始化的邏輯,就在 log.go 中處理。

這裏其實也沒有什麼,就是一開始筆者在讀源碼的時候總是被這裏疑惑,認爲畫蛇添足。其實要實現一個功能單一的 logs 包並與其餘模塊解耦,這麼作的確不錯。

再如, beego 的 session 模塊,爲了避免與 logs 模塊耦合,因此 session 模塊也造了一個僅供本身模塊內使用的日誌記錄器 SessionLog 。代碼以下:

// Log implement the log.Logger
type Log struct {
    *log.Logger
}

// NewSessionLog set io.Writer to create a Logger for session.
func NewSessionLog(out io.Writer) *Log {
    sl := new(Log)
    sl.Logger = log.New(out, "[SESSION]", 1e9)
    return sl
}

不妨看看 Beego 官方的架構圖:

clipboard.png

beego 是基於八大獨立的模塊構建的,是一個高度解耦的框架。用戶即便不使用 beego 的 HTTP 邏輯,也依舊可使用這些獨立模塊,例如:你可使用 cache 模塊來作你的緩存邏輯;使用日誌模塊來記錄你的操做信息;使用 config 模塊來解析你各類格式的文件。因此 beego 不只能夠用於 HTTP 類的應用開發,在你的 socket 遊戲開發中也是頗有用的模塊,這也是 beego 爲何受歡迎的一個緣由。你們若是玩過樂高的話,應該知道不少高級的東西都是一塊一塊的積木搭建出來的,而設計 beego 的時候,這些模塊就是積木,高級機器人就是 beego。
相關文章
相關標籤/搜索