beego源碼學(xué)習(xí)-日志模塊

安裝說明

這是一個用來處理日志的庫,它的設(shè)計思路來自于 database/sql,目前支持的引擎有 file、console、net、smtp、es、slack,可以通過如下方式進(jìn)行安裝:

go get github.com/astaxie/beego/logs

使用需要導(dǎo)入包:

import github.com/astaxie/beego/logs

添加輸出引擎(log 支持同時輸出到多個引擎):

logs.SetLogger("console")

//添加輸出引擎也支持第二個參數(shù),用來表示配置信息,不同引擎,配置不同
logs.SetLogger(logs.AdapterFile,`{"filename":"project.log","level":7,"maxlines":0,"maxsize":0,"daily":true,"maxdays":10,"color":true}`)

日志適配器注冊

  • 日志適配器的注冊入口
//=======================log.go==========================
// 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"
)
  • Logger接口定義了每一種適配器需要實現(xiàn)的四個功能:
  1. 初始化
  2. 寫入消息
  3. 銷毀
  4. 刷新
// 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
//全局變量adapters是一個map,關(guān)聯(lián)了每個適配器的名稱以及對應(yīng)的創(chuàng)建方法
type newLoggerFunc func() Logger
var adapters = make(map[string]newLoggerFunc)
  • 注冊方法
//Register 根據(jù)名稱進(jìn)行日志注冊,名稱重復(fù)注冊或 nil 會 panics
func Register(name string, log newLoggerFunc) {
    ......
    adapters[name] = log
}
  • 示例:注冊file適配器
//=======================log.go==========================
type fileLogWriter struct {......}

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

//創(chuàng)建一個 FileLogWriter 對象作為 LoggerInterface 的返回
func newFileWriter() Logger {
    w := &fileLogWriter{
        Daily:      true,
        MaxDays:    7,
        ......
    }
    return w
}

日志模塊主體

  • 主要結(jié)構(gòu)體
//=======================log.go==========================
// BeeLogger 結(jié)構(gòu)體聲明
// BeeLogger is default logger in beego application.
type BeeLogger struct {
    lock                sync.Mutex
    level               int
    init                bool
    enableFuncCallDepth bool
    loggerFuncCallDepth int
    asynchronous        bool
    prefix              string
    msgChanLen          int64
    msgChan             chan *logMsg
    signalChan          chan string
    wg                  sync.WaitGroup
    outputs             []*nameLogger
}

//結(jié)構(gòu)體:日志名
type nameLogger struct {
    Logger
    name string
}

//結(jié)構(gòu)體:日志信息
type logMsg struct {
    level int
    msg   string
    when  time.Time
}
  • 日志模塊創(chuàng)建了一個全局變量beeLogger,默認(rèn)使用控制臺輸出日志
// NewLogger 返回一個 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.setLogger(AdapterConsole)
    return bl
}

// beeLogger 引用使用的應(yīng)用 logger
var beeLogger = NewLogger()
  • 使用

通過創(chuàng)建NewLogger實例(也就是初始化了一個BeeLogger),然后調(diào)用SetLogger()設(shè)置使用的日志適配器。

import "github.com/astaxie/beego/logs"

log := NewLogger(10000)
log.SetLogger("console", "")

log.Trace("trace")
log.Info("info")
log.Warn("warning")
log.Debug("debug")
log.Critical("critical")

默認(rèn)設(shè)置

  • 默認(rèn)日志等級 LevelDebug
//=======================log.go==========================
// RFC5424 log message levels.
const (
    LevelEmergency = iota
    LevelAlert
    LevelCritical
    LevelError
    LevelWarning
    LevelNotice
    LevelInformational
    LevelDebug
)

//創(chuàng)建 Logger 實例時,默認(rèn) LevelDebug 等級
func NewLogger(channelLens ...int64) *BeeLogger {
    bl := new(BeeLogger)
    bl.level = LevelDebug
    ......
    return bl
}
  • 默認(rèn)適配器 console控制臺
//=======================console.go=======================
// consoleWriter 實現(xiàn)了 LoggerInterface 的4個方法:Init、WriteMsg、Destroy、Flush
type consoleWriter struct {
    lg       *logWriter          //繼承自log.go
    Level    int  `json:"level"`
    Colorful bool `json:"color"` //this filed is useful only when system's terminal supports color
}

// NewConsole 創(chuàng)建一個 ConsoleWriter 實例作為 LoggerInterface 的返回
func NewConsole() Logger {
    cw := &consoleWriter{
        lg:       newLogWriter(ansicolor.NewAnsiColorWriter(os.Stdout)),
        Level:    LevelDebug,
        Colorful: true,
    }
    return cw
}

//=======================log.go==========================
type logWriter struct {
    sync.Mutex
    writer io.Writer
}

func newLogWriter(wr io.Writer) *logWriter {
    return &logWriter{writer: wr}
}

提供的功能

  • 設(shè)置日志等級
//=======================log.go==========================
func (bl *BeeLogger) SetLevel(l int) {
    bl.level = l
}

func SetLevel(l int) {
    beeLogger.SetLevel(l)
}
  • 記錄日志文件和行數(shù)
//=======================log.go==========================
//方法:設(shè)置在日志中記錄文件名
func EnableFuncCallDepth(b bool) {
    beeLogger.enableFuncCallDepth = b
}
//方法:設(shè)置在日志中記錄行號
func SetLogFuncCallDepth(d int) {
    beeLogger.loggerFuncCallDepth = d
}

//示例:app.go文件 214行
//2020/11/30 17:01:22.282 [I] [app.go:214]  http server Running on http://:7001
  • 設(shè)置日志顏色
//=======================console.go==========================
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
}

// brush is a color join function
type brush func(string) string

/**
創(chuàng)建一個顏色刷子
格式:\033[顯示方式;前景色;背景色m
參數(shù)說明:
前景色            背景色           顏色
---------------------------------------
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                不可見
*/
func newBrush(color string) brush {
    pre := "\033["
    reset := "\033[0m"
    return func(text string) string {
        return pre + color + "m" + text + reset
    }
}

//在 console 的 WriteMsg 方法中調(diào)用顏色渲染
func (c *consoleWriter) WriteMsg(when time.Time, msg string, level int) error {
    ......
    if c.Colorful {
        msg = strings.Replace(msg, levelPrefix[level], colors[level](levelPrefix[level]), 1)
    }
    ......
}
  • 業(yè)務(wù)中記錄日志
log.Trace("trace")
log.Info("info")
log.Warn("warning")
log.Debug("debug")
log.Critical("critical")

生成日志

  • 生成日志內(nèi)容 writeMsg
//=======================log.go==========================
//變量:日志等級前綴
var levelPrefix = [LevelDebug + 1]string{"[M]", "[A]", "[C]", "[E]", "[W]", "[N]", "[I]", "[D]"}

//方法:寫入日志
func (bl *BeeLogger) writeMsg(logLevel int, msg string, v ...interface{}) error {
    //未初始化進(jìn)行加鎖,設(shè)置默認(rèn)日志適配器
    if !bl.init {
        bl.lock.Lock()
        bl.setLogger(AdapterConsole)
        bl.lock.Unlock()
    }
    //日志長度>0,進(jìn)行格式化
    if len(v) > 0 {
        msg = fmt.Sprintf(msg, v...)
    }

    msg = bl.prefix + " " + msg
    //設(shè)置當(dāng)前時間
    when := time.Now()
    if bl.enableFuncCallDepth {
        //獲取文件名
        _, file, line, ok := runtime.Caller(bl.loggerFuncCallDepth)
        if !ok {
            file = "???"
            line = 0
        }
        _, filename := path.Split(file)
        //獲取行號
        msg = "[" + filename + ":" + strconv.Itoa(line) + "] " + msg
    }

    //在文件名信前增加,日志等級標(biāo)志,如:【D】debug、【I】info
    if logLevel == levelLoggerImpl {
        // set to emergency to ensure all log will be print out correctly
        logLevel = LevelEmergency
    } else {
        msg = levelPrefix[logLevel] + " " + msg
    }

    //異步操作通過日志信息池獲取消息并發(fā)送給消息管道,同步直接寫入
    if bl.asynchronous {
        lm := logMsgPool.Get().(*logMsg)
        lm.level = logLevel
        lm.msg = msg
        lm.when = when
        if bl.outputs != nil {
            bl.msgChan <- lm
        } else {
            logMsgPool.Put(lm)
        }
    } else {
        bl.writeToLoggers(when, msg, logLevel)
    }
    return nil
}
  • 日志時間格式化
//=======================logger.go==========================
//日志日期格式化
func (lg *logWriter) writeln(when time.Time, msg string) {
    lg.Lock()
    //將時間格式化成"yyyy/mm/dd hh:mm:ss "格式
    h, _, _ := formatTimeHeader(when)
    lg.writer.Write(append(append(h, msg...), '\n'))
    lg.Unlock()
}

//=======================console.go==========================
//在控制臺調(diào)用WriteMsg寫入日志時觸發(fā)
func (c *consoleWriter) WriteMsg(when time.Time, msg string, level int) error {
    ......
    c.lg.writeln(when, msg)
    return nil
}

文件日志適配器

  • 文件適配器結(jié)構(gòu)體和構(gòu)造函數(shù)
type fileLogWriter struct {......}

func newFileWriter() Logger {
    w := &fileLogWriter{
        Daily:      true,       //按日分割
        MaxDays:    7,          //最大保留天數(shù)
        Hourly:     false,      //按小時分割
        MaxHours:   168,        //保持小時數(shù)
        // DoRotate means it need to write file in new file.
        // new file name like xx.2013-01-01.log (daily) or xx.001.log (by line or size)
        Rotate:     true,   
        RotatePerm: "0440",
        Level:      LevelTrace, //默認(rèn)日志等級
        Perm:       "0660",
        MaxLines:   10000000,   //最大行數(shù)
        MaxFiles:   999,        //最大文件數(shù)
        MaxSize:    1 << 28,    //文件大小
    }
    return w
}
  • 初始化
// 配置文件json
//  {
//  "filename":"logs/beego.log",
//  "maxLines":10000,
//  "maxsize":1024,
//  "daily":true,
//  "maxDays":15,
//  "rotate":true,
//      "perm":"0600"
//  }
func (w *fileLogWriter) Init(jsonConfig string) error {
    //將json格式的配置解析為 fileLogWriter 對象
    err := json.Unmarshal([]byte(jsonConfig), w)
    if err != nil {
        return err
    }
    if len(w.Filename) == 0 {
        return errors.New("jsonconfig must have filename")
    }
    //處理.log后綴
    w.suffix = filepath.Ext(w.Filename)
    w.fileNameOnly = strings.TrimSuffix(w.Filename, w.suffix)
    if w.suffix == "" {
        w.suffix = ".log"
    }
    err = w.startLogger()
    return err
}
  • 初始化文件屬性
func (w *fileLogWriter) initFd() error {
    fd := w.fileWriter
    fInfo, err := fd.Stat()
    if err != nil {
        return fmt.Errorf("get stat err: %s", err)
    }
    //獲取文件大小
    w.maxSizeCurSize = int(fInfo.Size())
    //獲取當(dāng)前時間
    w.dailyOpenTime = time.Now()
    w.dailyOpenDate = w.dailyOpenTime.Day()
    //獲取文件打開時間
    w.hourlyOpenTime = time.Now()
    w.hourlyOpenDate = w.hourlyOpenTime.Hour()
    w.maxLinesCurLines = 0
    //啟動新的線程并發(fā)處理文件分割
    if w.Hourly {
        go w.hourlyRotate(w.hourlyOpenTime)
    } else if w.Daily {
        go w.dailyRotate(w.dailyOpenTime)
    }
    //文件行數(shù)
    if fInfo.Size() > 0 && w.MaxLines > 0 {
        count, err := w.lines()
        if err != nil {
            return err
        }
        w.maxLinesCurLines = count
    }
    return nil
}
  • 文件切割
func (w *fileLogWriter) dailyRotate(openTime time.Time) {
    // 設(shè)置定時器,定時器間隔每24小時,精度納秒
    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ù)據(jù)
    <-tm.C
    //競爭鎖
    w.Lock()
    if w.needRotateDaily(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()
}
  • 創(chuàng)建日志文件
func (w *fileLogWriter) createLogFile() (*os.File, error) {
    //打開文件并格式化未int
    perm, err := strconv.ParseInt(w.Perm, 8, 64)
    if err != nil {
        return nil, err
    }

    filepath := path.Dir(w.Filename)
    os.MkdirAll(filepath, os.FileMode(perm))

    //打開文件,如果不存在則創(chuàng)建,操作權(quán)限:只寫并追加寫權(quán)限
    fd, err := os.OpenFile(w.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.FileMode(perm))
    if err == nil {
        //確認(rèn)操作權(quán)限是用戶設(shè)置的
        os.Chmod(w.Filename, os.FileMode(perm))
    }
    return fd, err
}
  • 處理過期日志文件
func (w *fileLogWriter) deleteOldLog() {
    //處理文件路徑
    dir := filepath.Dir(w.Filename)
    absolutePath, err := filepath.EvalSymlinks(w.Filename)
    if err == nil {
        dir = filepath.Dir(absolutePath)
    }
    //遍歷路徑下的文件
    filepath.Walk(dir, func(path string, info os.FileInfo, err error) (returnErr error) {
        //如果執(zhí)行中有 recover,延遲到方法結(jié)束返回錯誤
        defer func() {
            if r := recover(); r != nil {
                fmt.Fprintf(os.Stderr, "Unable to delete old log '%s', error: %v\n", path, r)
            }
        }()

        if info == nil {
            return
        }
        if w.Hourly {
            //如果不是目錄 && 超過最大保留期限 && 文件名一致 && 文件后綴一致,那么就刪除文件
            if !info.IsDir() && info.ModTime().Add(1 * time.Hour * time.Duration(w.MaxHours)).Before(time.Now()) {
                if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) &&
                strings.HasSuffix(filepath.Base(path), w.suffix) {
                    os.Remove(path)
                }
            }
        } else if w.Daily {
            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) {
                    os.Remove(path)
                }
            }
        }
        return
    })
}

異步日志

beego日志默認(rèn)使用同步日志寫入的方式,因為寫入文件前需要進(jìn)行加鎖,并發(fā)下有明顯的性能影響,所以采用異步日志寫入,由不同的 goroutine 來處理日志輸出、發(fā)送日志給渠道、從渠道接收日志。

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)
    ......
}
  • 設(shè)置渠道大小(默認(rèn)1000)
func Async(msgLen ...int64) *BeeLogger {
    return beeLogger.Async(msgLen...)
}
func (bl *BeeLogger) Async(msgLen ...int64) *BeeLogger {
    bl.lock.Lock()
    defer bl.lock.Unlock()
    if bl.asynchronous {
        return bl
    }
    //設(shè)置日志為異步
    bl.asynchronous = true
    if len(msgLen) > 0 && msgLen[0] > 0 {
        bl.msgChanLen = msgLen[0]
    }
    //設(shè)置渠道大小
    bl.msgChan = make(chan *logMsg, bl.msgChanLen)
    //從消息池中取消息,如果為空時,返回喲1個 logMsg 空接口
    logMsgPool = &sync.Pool{
        New: func() interface{} {
            return &logMsg{}
        },
    }
    //同步計數(shù)+1
    bl.wg.Add(1)
    //啟動新線程開始日志處理
    go bl.startLogger()
    return bl
}
  • 獨立線程處理異步日志輸出
// start logger chan reading.
// when chan is not empty, write logs.
func (bl *BeeLogger) startLogger() {
    gameOver := false
    for {
        select {
        //從渠道獲取數(shù)據(jù),并寫入
        case bm := <-bl.msgChan:
            bl.writeToLoggers(bm.when, bm.msg, bm.level)
            logMsgPool.Put(bm)
        //給渠道發(fā)送close信息
        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
        }
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,703評論 2 380

推薦閱讀更多精彩內(nèi)容