先大體瞭解怎麼使用,再進行剖析。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 切片添加一個值爲零的元素後再取頭個元素,這個技巧有如下好處:數組
這個變量表示函數調用的棧深度,用於記錄日誌時同時打印出當時執行語句的位置,包括文件名和行號。
雖然 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 相關配置字段的更改,必須操做前加鎖保證併發安全。
臨界資源是指每次僅容許一個進程訪問的資源。
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)外面還有一個 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 官方的架構圖:
beego 是基於八大獨立的模塊構建的,是一個高度解耦的框架。用戶即便不使用 beego 的 HTTP 邏輯,也依舊可使用這些獨立模塊,例如:你可使用 cache 模塊來作你的緩存邏輯;使用日誌模塊來記錄你的操做信息;使用 config 模塊來解析你各類格式的文件。因此 beego 不只能夠用於 HTTP 類的應用開發,在你的 socket 遊戲開發中也是頗有用的模塊,這也是 beego 爲何受歡迎的一個緣由。你們若是玩過樂高的話,應該知道不少高級的東西都是一塊一塊的積木搭建出來的,而設計 beego 的時候,這些模塊就是積木,高級機器人就是 beego。