beego的日志處理支持多種引擎、多種日志級(jí)別的輸出,也可以設(shè)置輸出方式(包括是否輸出文件名和行號(hào)、同步輸出或者異步輸出等)。
本文將主要介紹一下beego是如何實(shí)現(xiàn)控制臺(tái)和文件兩個(gè)引擎的日志輸出、日志消息如何構(gòu)造以及如何實(shí)現(xiàn)的異步日志傳輸。
安裝與使用
使用以下命令進(jìn)行安裝:
$ go get github.com/astaxie/beego/logs
安裝后在源文件中引入包便可以使用了:
import ( "github.com/astaxie/beego/logs" )
輸出引擎
注冊(cè)引擎
beego支持的引擎有:console、file等,具體如下:
// Name for adapter with beego official support
const (
AdapterConsole = "console"
AdapterFile = "file"
AdapterMultiFile = "multifile"
AdapterMail = "smtp"
AdapterConn = "conn"
AdapterEs = "es"
AdapterJianLiao = "jianliao"
AdapterSlack = "slack"
AdapterAliLS = "alils"
)
每個(gè)引擎都需要實(shí)現(xiàn)以下接口:
// Logger defines the behavior of a log provider.
type Logger interface {
Init(config string) error
WriteMsg(when time.Time, msg string, level int) error
Destroy()
Flush()
}
全局變量adapters是一個(gè)map,關(guān)聯(lián)了每個(gè)引擎的名稱以及其對(duì)應(yīng)的創(chuàng)建接口。每個(gè)引擎的源文件中都會(huì)有一個(gè)init() 函數(shù),引入"github.com/astaxie/beego/logs"包時(shí)系統(tǒng)會(huì)自動(dòng)調(diào)用該函數(shù),該函數(shù)會(huì)將引擎的創(chuàng)建接口(NewConsole、newFileWriter)注冊(cè)到adapters中。
var adapters = make(map[string]newLoggerFunc)
type newLoggerFunc func() Logger
func Register(name string, log newLoggerFunc) {
adapters[name] = log
}
/* ------------------------ console.go -------------------------- */
func init() {
Register(AdapterConsole, NewConsole)
}
// NewConsole create ConsoleWriter returning as LoggerInterface.
func NewConsole() Logger {
... ...
return cw
}
/* ------------------------ file.go -------------------------- */
func init() {
Register(AdapterFile, newFileWriter)
}
// newFileWriter create a FileLogWriter returning as LoggerInterface.
func newFileWriter() Logger {
w := &fileLogWriter{
... ...
}
return w
}
創(chuàng)建日志模塊BeeLogger
logs模塊創(chuàng)建了一個(gè)全局變量beeLogger,默認(rèn)使用控制臺(tái)輸出日志:
var beeLogger = NewLogger()
func NewLogger(channelLens ...int64) *BeeLogger {
bl := new(BeeLogger)
... ...
bl.setLogger(AdapterConsole)
return bl
}
type BeeLogger struct {
lock sync.Mutex
level int
init bool
enableFuncCallDepth bool
loggerFuncCallDepth int
asynchronous bool
msgChanLen int64
msgChan chan *logMsg
signalChan chan string
wg sync.WaitGroup
outputs []*nameLogger
}
控制臺(tái)引擎
創(chuàng)建控制臺(tái)引擎,輸出設(shè)置為os.Stdout,即控制臺(tái):
/* ------------------------ console.go -------------------------- */
// consoleWriter implements LoggerInterface and writes messages to terminal.
type consoleWriter struct {
lg *logWriter
Level int `json:"level"`
Colorful bool `json:"color"` //this filed is useful only when system's terminal supports color
}
// NewConsole create ConsoleWriter returning as LoggerInterface.
func NewConsole() Logger {
cw := &consoleWriter{
lg: newLogWriter(os.Stdout), // 輸出設(shè)置為os.Stdout,即控制臺(tái)
Level: LevelDebug,
Colorful: runtime.GOOS != "windows",
}
return cw
}
/* ------------------------ logger.go -------------------------- */
type logWriter struct {
sync.Mutex
writer io.Writer
}
func newLogWriter(wr io.Writer) *logWriter {
return &logWriter{writer: wr}
}
默認(rèn)使用控制臺(tái)引擎,日志級(jí)別包括:
/* ------------------------ log.go -------------------------- */
const (
LevelEmergency = iota
LevelAlert
LevelCritical
LevelError
LevelWarning
LevelNotice
LevelInformational
LevelDebug
)
默認(rèn)使用LevelDebug級(jí)別:
/* ------------------------ log.go -------------------------- */
func NewLogger(channelLens ...int64) *BeeLogger {
bl := new(BeeLogger)
bl.level = LevelDebug
... ...
return bl
}
可以通過SetLevel函數(shù)設(shè)置輸出級(jí)別,那么超出所設(shè)置級(jí)別的日志將不輸出:
/* ------------------------ log.go -------------------------- */
func SetLevel(l int) {
beeLogger.SetLevel(l)
}
func (bl *BeeLogger) SetLevel(l int) {
bl.level = l
}
我們可以使用以下方法記錄不同級(jí)別的日志:
logs.Debug("... ...")
logs.Warn("... ...")
logs.Notice("... ...")
... ...
以Debug為例,若是設(shè)置的日志級(jí)別低于LevelDebug,則不輸出日志到控制臺(tái),直接返回;否則構(gòu)造日志內(nèi)容,并輸出日志到控制臺(tái):
/* ------------------------ log.go -------------------------- */
func Debug(f interface{}, v ...interface{}) {
beeLogger.Debug(formatLog(f, v...))
// formatLog(f, v...)使日志支持格式化輸出logs.Debug("xxx %v", xxx)
}
func (bl *BeeLogger) Debug(format string, v ...interface{}) {
if LevelDebug > bl.level { // 若是設(shè)置的日志級(jí)別低于LevelDebug,則不輸出日志到控制臺(tái),直接返回
return
}
bl.writeMsg(LevelDebug, format, v...)
}
構(gòu)造日志內(nèi)容
通過以下設(shè)置,可以在每條日志之前加上哪個(gè)文件中哪一行調(diào)用的logs.Debug()函數(shù):
/* ------------------------ 程序員的源文件 -------------------------- */
log.EnableFuncCallDepth(true)
log.SetLogFuncCallDepth(3)
/* ------------------------ log.go -------------------------- */
func EnableFuncCallDepth(b bool) {
beeLogger.enableFuncCallDepth = b
}
func SetLogFuncCallDepth(d int) {
beeLogger.loggerFuncCallDepth = d
}
根據(jù)設(shè)置構(gòu)造日志內(nèi)容:
/* ------------------------ log.go -------------------------- */
func (bl *BeeLogger) writeMsg(logLevel int, msg string, v ...interface{}) error {
... ...
when := time.Now() // 當(dāng)前時(shí)間
if bl.enableFuncCallDepth {
_, file, line, ok := runtime.Caller(bl.loggerFuncCallDepth) // 獲取文件名(包括路徑)和行號(hào)
... ...
_, filename := path.Split(file) // 只取文件名
msg = "[" + filename + ":" + strconv.Itoa(line) + "] " + msg // 在日志前加上“[文件名:行號(hào)] ”字符串
}
... ...
msg = levelPrefix[logLevel] + msg // 在日志前加上“[D] ”字符串
... ...
bl.writeToLoggers(when, msg, logLevel)
... ...
return nil
}
var levelPrefix = [LevelDebug + 1]string{"[M] ", "[A] ", "[C] ", "[E] ", "[W] ", "[N] ", "[I] ", "[D] "}
func (bl *BeeLogger) writeToLoggers(when time.Time, msg string, level int) {
... ...
l.WriteMsg(when, msg, level)
... ...
}
日志顏色設(shè)置
console.go文件中有一個(gè)全局變量colors,這是一個(gè)數(shù)組,每個(gè)元素是一個(gè)brush函數(shù),用于為日志內(nèi)容渲染顏色:
/* ------------------------ console.go -------------------------- */
// brush is a color join function
type brush func(string) string
// newBrush return a fix color Brush
func newBrush(color string) brush {
pre := "\033["
reset := "\033[0m"
return func(text string) string {
return pre + color + "m" + text + reset
}
}
var colors = []brush{
newBrush("1;37"), // Emergency white
newBrush("1;36"), // Alert cyan
newBrush("1;35"), // Critical magenta
newBrush("1;31"), // Error red
newBrush("1;33"), // Warning yellow
newBrush("1;32"), // Notice green
newBrush("1;34"), // Informational blue
newBrush("1;44"), // Debug Background blue
}
/*
格式:\033[顯示方式;前景色;背景色m
說明:
前景色 背景色 顏色
---------------------------------------
30 40 黑色
31 41 紅色
32 42 綠色
33 43 黃色
34 44 藍(lán)色
35 45 紫紅色
36 46 青藍(lán)色
37 47 白色
顯示方式 意義
-------------------------
0 終端默認(rèn)設(shè)置
1 高亮顯示
4 使用下劃線
5 閃爍
7 反白顯示
8 不可見
例子:
\033[1;31;40m
\033[0m
*/
為日志渲染顏色:
/* ------------------------ console.go -------------------------- */
func (c *consoleWriter) WriteMsg(when time.Time, msg string, level int) error {
... ...
msg = colors[level](msg)
// 假設(shè)level為L(zhǎng)evelDebug,則colors[level]得到newBrush("1;44"),
// 也就是函數(shù)func(text string) string { return "\033[1;44m" + text + "\033[0m" }
// 也就是日志內(nèi)容渲染為高亮背景顏色為藍(lán)色
... ...
c.lg.println(when, msg)
return nil
}
日志輸出
以下函數(shù)先將當(dāng)前時(shí)間格式化成"yyyy/mm/dd hh:mm:ss"格式,然后再構(gòu)造成"yyyy/mm/dd hh:mm:ss msg\n"的格式,至此日志內(nèi)容構(gòu)造完成,調(diào)用Write輸出。
/* ------------------------ logger.go -------------------------- */
func (lg *logWriter) println(when time.Time, msg string) {
lg.Lock()
// 競(jìng)爭(zhēng)鎖
h, _ := formatTimeHeader(when)
// 將時(shí)間格式化成"yyyy/mm/dd hh:mm:ss "格式
lg.writer.Write(append(append(h, msg...), '\n'))
// lg.writer為os.Stdout,則為os.Stdout.Write("yyyy/mm/dd hh:mm:ss msg\n")
lg.Unlock()
}
文件引擎
創(chuàng)建文件引擎,輸出到文件中,默認(rèn)日志級(jí)別為L(zhǎng)evelTrace,每天更換一個(gè)日志文件,日志文件保存最近7天的日志:
/* ------------------------ file.go -------------------------- */
type fileLogWriter struct {
sync.RWMutex // write log order by order and atomic incr maxLinesCurLines and maxSizeCurSize
// The opened file
Filename string `json:"filename"`
fileWriter *os.File
// Rotate at line
MaxLines int `json:"maxlines"`
maxLinesCurLines int
// Rotate at size
MaxSize int `json:"maxsize"`
maxSizeCurSize int
// Rotate daily
Daily bool `json:"daily"`
MaxDays int64 `json:"maxdays"`
dailyOpenDate int
dailyOpenTime time.Time
Rotate bool `json:"rotate"`
Level int `json:"level"`
Perm string `json:"perm"`
RotatePerm string `json:"rotateperm"`
fileNameOnly, suffix string // like "project.log", project is fileNameOnly and .log is suffix
}
// 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
}
設(shè)置文件引擎
beego支持將日志寫入到文件中,可以根據(jù)需要設(shè)置日志級(jí)別,設(shè)置文件路徑、文件名,可以設(shè)置多少行、多少字節(jié)將日志重新寫到另外一個(gè)日志文件,還可以設(shè)置每天分割一次日志文件并設(shè)置日志保存的天數(shù):
logs.SetLogger(logs.AdapterFile,`{"filename":"log/project.log","level":7,"maxlines":0,"maxsize":0,"daily":true,"maxdays":10}`)
// "filename":"log/project.log" :將日志保存到當(dāng)前目錄下的log目錄下的project.log文件中
// "level":7 :將日志級(jí)別設(shè)為7,也就是LevelDebug
// "maxlines":0 :設(shè)置日志文件分割條件,若文件超過maxlines,則將日志保存到下個(gè)文件中,為0表示不設(shè)置
// "maxsize":0 :設(shè)置日志文件分割條件,若文件超過maxsize,則將日志保存到下個(gè)文件中,為0表示不設(shè)置
// "daily":true:設(shè)置日志日否每天分割一次
// "maxdays":10:設(shè)置保存最近幾天的日志文件,超過天數(shù)的日志文件被刪除,為0表示不設(shè)置
/* ------------------------ log.go -------------------------- */
func SetLogger(adapter string, config ...string) error {
return beeLogger.SetLogger(adapter, config...)
}
func (bl *BeeLogger) SetLogger(adapterName string, configs ...string) error {
bl.lock.Lock()
// 設(shè)置之前需要加鎖
defer bl.lock.Unlock()
... ...
return bl.setLogger(adapterName, configs...)
}
func (bl *BeeLogger) setLogger(adapterName string, configs ...string) error {
config := append(configs, "{}")[0]
... ...
log, ok := adapters[adapterName]
// adapters[adapterName]返回的是一個(gè)引擎的創(chuàng)建接口,這里為func newFileWriter() Logger
... ...
lg := log()
// 調(diào)用func newFileWriter() Logger,返回一個(gè)fileLogWriter文件引擎對(duì)象
err := lg.Init(config)
// 每個(gè)引擎對(duì)象都實(shí)現(xiàn)了Init函數(shù)
... ...
return nil
}
/* ------------------------ file.go -------------------------- */
func (w *fileLogWriter) Init(jsonConfig string) error {
err := json.Unmarshal([]byte(jsonConfig), w)
// jsonConfig是json格式的字符串,Unmarshal方法將其中的字段轉(zhuǎn)化為對(duì)象w的字段
... ...
w.suffix = filepath.Ext(w.Filename)
// 取出后綴:.log
w.fileNameOnly = strings.TrimSuffix(w.Filename, w.suffix)
// 去除后綴:log/project
if w.suffix == "" {
w.suffix = ".log"
}
// 如果設(shè)置的文件后綴為空,則后綴設(shè)為".log"
err = w.startLogger()
return err
}
// start file logger. create log file and set to locker-inside file writer.
func (w *fileLogWriter) startLogger() error {
file, err := w.createLogFile()
// 打開文件log/project.log并返回其文件描述符
... ...
w.fileWriter = file
return w.initFd()
}
func (w *fileLogWriter) createLogFile() (*os.File, error) {
// Open the log file
perm, err := strconv.ParseInt(w.Perm, 8, 64)
// 將設(shè)置的文件權(quán)限轉(zhuǎn)化為8進(jìn)制,64位的整數(shù)
... ...
fd, err := os.OpenFile(w.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.FileMode(perm))
// 以只寫權(quán)限并追加寫的方式打開文件(log/project.log),如果文件不存在則創(chuàng)建文件
... ...
os.Chmod(w.Filename, os.FileMode(perm))
// 確保文件權(quán)限為設(shè)置的值
... ...
return fd, err
}
func (w *fileLogWriter) initFd() error {
fd := w.fileWriter
fInfo, err := fd.Stat()
... ...
w.maxSizeCurSize = int(fInfo.Size())
// 獲取文件當(dāng)前大小
w.dailyOpenTime = time.Now()
// 獲取當(dāng)前時(shí)間
w.dailyOpenDate = w.dailyOpenTime.Day()
// 獲取文件打開時(shí)間
w.maxLinesCurLines = 0
if w.Daily {
go w.dailyRotate(w.dailyOpenTime)
// 創(chuàng)建一個(gè)線程處理日志文件按天分割工作
}
if fInfo.Size() > 0 {
count, err := w.lines()
... ...
w.maxLinesCurLines = count
// 獲取文件當(dāng)前行數(shù)
}
return nil
}
日志文件分割
beego用一個(gè)專門的goroutine來處理日志文件分割:
/* ------------------------ file.go -------------------------- */
func (w *fileLogWriter) dailyRotate(openTime time.Time) {
y, m, d := openTime.Add(24 * time.Hour).Date()
nextDay := time.Date(y, m, d, 0, 0, 0, 0, openTime.Location())
tm := time.NewTimer(time.Duration(nextDay.UnixNano() - openTime.UnixNano() + 100))
// 設(shè)置定時(shí)器,定時(shí)在24小時(shí)(也就是一天)之后,會(huì)給chan變量tm發(fā)送信號(hào)
<-tm.C
// 定時(shí)器到期,接收到信號(hào)
w.Lock()
if w.needRotate(0, time.Now().Day()) {
if err := w.doRotate(time.Now()); err != nil {
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
}
}
w.Unlock()
}
func (w *fileLogWriter) needRotate(size int, day int) bool {
// 如果設(shè)置了最大行數(shù)或最大字節(jié)數(shù),是否超過了設(shè)置值
// 如果按天分割,時(shí)間是否過了文件打開當(dāng)天
// 滿足一個(gè)以上條件則返回true
return (w.MaxLines > 0 && w.maxLinesCurLines >= w.MaxLines) ||
(w.MaxSize > 0 && w.maxSizeCurSize >= w.MaxSize) ||
(w.Daily && day != w.dailyOpenDate)
}
func (w *fileLogWriter) doRotate(logTime time.Time) error {
// file exists
// Find the next available number
num := 1
fName := ""
rotatePerm, err := strconv.ParseInt(w.RotatePerm, 8, 64)
... ...
if w.MaxLines > 0 || w.MaxSize > 0 {
// 若設(shè)置了最大行數(shù)或者最大字節(jié)數(shù),則將日志文件名稱設(shè)置成格式
// "project.yyyy-mm-dd.nnn.log",nnn從001-999,最多一天有999個(gè)日志文件
for ; err == nil && num <= 999; num++ {
fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", logTime.Format("2006-01-02"), num, w.suffix)
_, err = os.Lstat(fName)
// 判斷fName是否存在,若存在則返回nil
}
} else {
// 將日志文件名稱設(shè)置成格式"project.yyyy-mm-dd.log"
fName = fmt.Sprintf("%s.%s%s", w.fileNameOnly, w.dailyOpenTime.Format("2006-01-02"), w.suffix)
_, err = os.Lstat(fName)
for ; err == nil && num <= 999; num++ {
fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", w.dailyOpenTime.Format("2006-01-02"), num, w.suffix)
_, err = os.Lstat(fName)
}
}
... ...
w.fileWriter.Close()
// 關(guān)閉當(dāng)前日志文件,當(dāng)前日志文件一直為project.log
err = os.Rename(w.Filename, fName)
// 將當(dāng)前日志文件重命名為前面設(shè)置的格式fName
... ...
err = os.Chmod(fName, os.FileMode(rotatePerm))
startLoggerErr := w.startLogger()
// 再打開日志文件project.log,記錄日志
go w.deleteOldLog()
// 開啟新的線程處理文件刪除工作
... ...
return nil
}
日志文件保存期限
beego允許用戶設(shè)置日志文件保存天數(shù),超高設(shè)置時(shí)間的日志文件將被刪除,這個(gè)工作是由一個(gè)獨(dú)立的goroutine處理的:
/* ------------------------ file.go -------------------------- */
func (w *fileLogWriter) deleteOldLog() {
dir := filepath.Dir(w.Filename)
filepath.Walk(dir, func(path string, info os.FileInfo, err error) (returnErr error) {
// 遍歷目錄dir下的所有文件
... ...
if !info.IsDir() && info.ModTime().Add(24*time.Hour*time.Duration(w.MaxDays)).Before(time.Now()) {
if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) &&
strings.HasSuffix(filepath.Base(path), w.suffix) {
// 若一個(gè)文件有project前綴以及.log后綴,而且修改日期超過日志文件保存天數(shù),則刪除
os.Remove(path)
}
}
return
})
}
構(gòu)造日志內(nèi)容并輸出到日志文件
文件引擎的日志內(nèi)容構(gòu)造與控制臺(tái)日志內(nèi)容構(gòu)造差不多,以Debug為例,都是調(diào)用以下函數(shù):
/* ------------------------ log.go -------------------------- */
func Debug(f interface{}, v ...interface{}) {
beeLogger.Debug(formatLog(f, v...))
// formatLog(f, v...)使日志支持格式化輸出logs.Debug("xxx %v", xxx)
}
func (bl *BeeLogger) Debug(format string, v ...interface{}) {
if LevelDebug > bl.level { // 若是設(shè)置的日志級(jí)別低于LevelDebug,則不輸出日志到控制臺(tái),直接返回
return
}
bl.writeMsg(LevelDebug, format, v...)
}
func (bl *BeeLogger) writeMsg(logLevel int, msg string, v ...interface{}) error {
... ...
when := time.Now() // 當(dāng)前時(shí)間
if bl.enableFuncCallDepth {
_, file, line, ok := runtime.Caller(bl.loggerFuncCallDepth) // 獲取文件名(包括路徑)和行號(hào)
... ...
_, filename := path.Split(file) // 只取文件名
msg = "[" + filename + ":" + strconv.Itoa(line) + "] " + msg // 在日志前加上“[文件名:行號(hào)] ”字符串
}
... ...
msg = levelPrefix[logLevel] + msg // 在日志前加上“[D] ”字符串
... ...
bl.writeToLoggers(when, msg, logLevel)
... ...
return nil
}
var levelPrefix = [LevelDebug + 1]string{"[M] ", "[A] ", "[C] ", "[E] ", "[W] ", "[N] ", "[I] ", "[D] "}
func (bl *BeeLogger) writeToLoggers(when time.Time, msg string, level int) {
... ...
l.WriteMsg(when, msg, level)
... ...
}
所有引擎都實(shí)現(xiàn)了Logger接口,即實(shí)現(xiàn)了WriteMsg方法:
/* ------------------------ file.go -------------------------- */
func (w *fileLogWriter) WriteMsg(when time.Time, msg string, level int) error {
if level > w.Level { // 若日志級(jí)別超出設(shè)置的級(jí)別,則不輸出
return nil
}
h, d := formatTimeHeader(when)
// 將時(shí)間格式化成"yyyy/mm/dd hh:mm:ss "格式
msg = string(h) + msg + "\n"
// 將日志內(nèi)容格式化為"yyyy/mm/dd hh:mm:ss msg\n"格式
if w.Rotate {
// 判斷是否需要分割日志文件并且是否達(dá)到分割要求,若是則分割
w.RLock()
if w.needRotate(len(msg), d) {
w.RUnlock()
w.Lock()
if w.needRotate(len(msg), d) {
if err := w.doRotate(when); err != nil {
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
}
}
w.Unlock()
} else {
w.RUnlock()
}
}
w.Lock()
// 將日志寫入文件之前需要競(jìng)爭(zhēng)鎖,如果有多個(gè)線程對(duì)日志文件進(jìn)行寫入操作,則會(huì)有競(jìng)爭(zhēng)關(guān)系。這個(gè)問題在多線程應(yīng)用中可能會(huì)成為性能瓶頸。
_, err := w.fileWriter.Write([]byte(msg))
// 將日志寫入文件
if err == nil {
w.maxLinesCurLines++
w.maxSizeCurSize += len(msg)
}
w.Unlock()
return err
}
異步日志
beego日志默認(rèn)使用同步日志寫入的方式。前邊控制臺(tái)和文件引擎在日志寫入前都需要對(duì)資源進(jìn)行加鎖,而且每次輸出日志都需要調(diào)用系統(tǒng)調(diào)用,非常耗時(shí),這導(dǎo)致在高并發(fā)的情況下會(huì)出現(xiàn)性能瓶頸。
異步日志是由一個(gè)專門的goroutine來將日志輸出,調(diào)用日志輸出的goroutine只需將日志內(nèi)容通過信道發(fā)送給日志輸出goroutine,日志輸出goroutine會(huì)從信道中取出日志并輸出。
信道大小默認(rèn)為1000:
var beeLogger = NewLogger()
func NewLogger(channelLens ...int64) *BeeLogger {
bl := new(BeeLogger)
... ...
bl.msgChanLen = append(channelLens, 0)[0]
if bl.msgChanLen <= 0 {
bl.msgChanLen = defaultAsyncMsgLen
}
bl.signalChan = make(chan string, 1)
... ...
return bl
}
const defaultAsyncMsgLen = 1e3
設(shè)置異步日志輸出
通過以下函數(shù)設(shè)置信道大小(2000),并觸發(fā)異步日志輸出:
/* ------------------------ 程序員的源文件 -------------------------- */
logs.Async(2000)
/* ------------------------ log.go -------------------------- */
func Async(msgLen ...int64) *BeeLogger {
return beeLogger.Async(msgLen...)
}
func (bl *BeeLogger) Async(msgLen ...int64) *BeeLogger {
bl.lock.Lock()
... ...
bl.asynchronous = true
// 觸發(fā)異步日志輸出
if len(msgLen) > 0 && msgLen[0] > 0 {
bl.msgChanLen = msgLen[0]
}
bl.msgChan = make(chan *logMsg, bl.msgChanLen)
// 設(shè)置信道大小
logMsgPool = &sync.Pool{
New: func() interface{} {
// 若從消息池中取日志時(shí)消息池為空,則調(diào)用該函數(shù),返回一個(gè)logMsg的空接口
return &logMsg{}
},
}
bl.wg.Add(1)
// sync.WaitGroup只有3個(gè)方法,Add(),Done(),Wait()。
// 其中Done()是Add(-1)的別名。簡(jiǎn)單的來說,
// 使用Add()添加計(jì)數(shù),Done()減掉一個(gè)計(jì)數(shù),計(jì)數(shù)不為0, Wait()阻塞計(jì)數(shù)為0。
go bl.startLogger()
// 啟動(dòng)新的線程處理日志輸出
return bl
}
// BeeLogger is default logger in beego application.
// it can contain several providers and log message into all providers.
type BeeLogger struct {
... ...
asynchronous bool
msgChanLen int64
msgChan chan *logMsg
signalChan chan string
wg sync.WaitGroup
}
type logMsg struct {
level int
msg string
when time.Time
}
var logMsgPool *sync.Pool
構(gòu)造日志內(nèi)容
以Debug為例,都是調(diào)用以下函數(shù):
/* ------------------------ log.go -------------------------- */
func Debug(f interface{}, v ...interface{}) {
beeLogger.Debug(formatLog(f, v...))
// formatLog(f, v...)使日志支持格式化輸出logs.Debug("xxx %v", xxx)
}
func (bl *BeeLogger) Debug(format string, v ...interface{}) {
if LevelDebug > bl.level { // 若是設(shè)置的日志級(jí)別低于LevelDebug,則不輸出日志到控制臺(tái),直接返回
return
}
bl.writeMsg(LevelDebug, format, v...)
}
func (bl *BeeLogger) writeMsg(logLevel int, msg string, v ...interface{}) error {
... ...
when := time.Now() // 當(dāng)前時(shí)間
if bl.enableFuncCallDepth {
_, file, line, ok := runtime.Caller(bl.loggerFuncCallDepth) // 獲取文件名(包括路徑)和行號(hào)
... ...
_, filename := path.Split(file) // 只取文件名
msg = "[" + filename + ":" + strconv.Itoa(line) + "] " + msg // 在日志前加上“[文件名:行號(hào)] ”字符串
}
... ...
msg = levelPrefix[logLevel] + msg // 在日志前加上“[D] ”字符串
... ...
if bl.asynchronous { // 異步日志輸出
lm := logMsgPool.Get().(*logMsg)
// 從logMsgPool中獲取一個(gè)logMsg對(duì)象
lm.level = logLevel
lm.msg = msg
lm.when = when
// 構(gòu)造日志消息
bl.msgChan <- lm
// 將日志消息通過信道傳輸給日志輸出goroutine
} else {
... ...
}
return nil
}
var levelPrefix = [LevelDebug + 1]string{"[M] ", "[A] ", "[C] ", "[E] ", "[W] ", "[N] ", "[I] ", "[D] "}
日志輸出
異步日志輸出由一個(gè)專門的線程處理:
func (bl *BeeLogger) startLogger() {
gameOver := false
for {
select {
case bm := <-bl.msgChan:
// 從信道中取出一個(gè)日志并輸出到設(shè)置的引擎中
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
}
}
}
func (bl *BeeLogger) writeToLoggers(when time.Time, msg string, level int) {
... ...
err := l.WriteMsg(when, msg, level)
// 這里將msg輸出到設(shè)置的引擎,這與前邊同步日志輸出中控制臺(tái)輸出和文件輸出一樣
... ...
}
至此,關(guān)于beego logs包中的控制臺(tái)和文件輸出的代碼學(xué)習(xí)就結(jié)束了~其他引擎的學(xué)習(xí)大致也是如此,如果有興趣可以再看看。