美文网首页
Go beego的logs源码解读

Go beego的logs源码解读

作者: 涵仔睡觉 | 来源:发表于2018-05-25 19:37 被阅读0次

    beego的日志处理支持多种引擎、多种日志级别的输出,也可以设置输出方式(包括是否输出文件名和行号、同步输出或者异步输出等)。

    本文将主要介绍一下beego是如何实现控制台和文件两个引擎的日志输出、日志消息如何构造以及如何实现的异步日志传输。

    安装与使用

    使用以下命令进行安装:

    $ go get github.com/astaxie/beego/logs
    

    安装后在源文件中引入包便可以使用了:

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

    输出引擎

    注册引擎

    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"
    )
    

    每个引擎都需要实现以下接口:

    // 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是一个map,关联了每个引擎的名称以及其对应的创建接口。每个引擎的源文件中都会有一个init() 函数,引入"github.com/astaxie/beego/logs"包时系统会自动调用该函数,该函数会将引擎的创建接口(NewConsole、newFileWriter)注册到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
    }
    

    创建日志模块BeeLogger

    logs模块创建了一个全局变量beeLogger,默认使用控制台输出日志:

    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
    }
    

    控制台引擎

    创建控制台引擎,输出设置为os.Stdout,即控制台:

    /* ------------------------ 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), // 输出设置为os.Stdout,即控制台
            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}
    }
    

    默认使用控制台引擎,日志级别包括:

    /* ------------------------ log.go -------------------------- */
    const ( 
        LevelEmergency = iota
        LevelAlert
        LevelCritical
        LevelError
        LevelWarning
        LevelNotice
        LevelInformational
        LevelDebug
    )
    

    默认使用LevelDebug级别:

    /* ------------------------ log.go -------------------------- */
    func NewLogger(channelLens ...int64) *BeeLogger {
        bl := new(BeeLogger)
        bl.level = LevelDebug
        ... ...
        return bl
    }
    

    可以通过SetLevel函数设置输出级别,那么超出所设置级别的日志将不输出:

    /* ------------------------ log.go -------------------------- */
    func SetLevel(l int) {
        beeLogger.SetLevel(l)
    }
    
    func (bl *BeeLogger) SetLevel(l int) {
        bl.level = l
    }
    

    我们可以使用以下方法记录不同级别的日志:

    logs.Debug("... ...")
    logs.Warn("... ...")
    logs.Notice("... ...")
    ... ...
    

    以Debug为例,若是设置的日志级别低于LevelDebug,则不输出日志到控制台,直接返回;否则构造日志内容,并输出日志到控制台:

    /* ------------------------ 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 { // 若是设置的日志级别低于LevelDebug,则不输出日志到控制台,直接返回
            return
        }
        bl.writeMsg(LevelDebug, format, v...)
    }
    

    构造日志内容

    通过以下设置,可以在每条日志之前加上哪个文件中哪一行调用的logs.Debug()函数:

    /* ------------------------ 程序员的源文件 -------------------------- */
    log.EnableFuncCallDepth(true)
    log.SetLogFuncCallDepth(3)
    
    /* ------------------------ log.go -------------------------- */
    func EnableFuncCallDepth(b bool) {
        beeLogger.enableFuncCallDepth = b
    }
    func SetLogFuncCallDepth(d int) {
        beeLogger.loggerFuncCallDepth = d
    }
    

    根据设置构造日志内容:

    /* ------------------------ log.go -------------------------- */
    func (bl *BeeLogger) writeMsg(logLevel int, msg string, v ...interface{}) error {
        ... ...
        when := time.Now() // 当前时间
        if bl.enableFuncCallDepth {
            _, file, line, ok := runtime.Caller(bl.loggerFuncCallDepth) // 获取文件名(包括路径)和行号
            ... ...
            _, filename := path.Split(file)  // 只取文件名
            msg = "[" + filename + ":" + strconv.Itoa(line) + "] " + msg // 在日志前加上“[文件名:行号] ”字符串
        }
    
        ... ...
        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)
            ... ...
    }
    

    日志颜色设置

    console.go文件中有一个全局变量colors,这是一个数组,每个元素是一个brush函数,用于为日志内容渲染颜色:

    /* ------------------------ 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              蓝色
    35                45              紫红色
    36                46              青蓝色
    37                47              白色
    显示方式           意义
    -------------------------
    0                终端默认设置
    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) 
            // 假设level为LevelDebug,则colors[level]得到newBrush("1;44"),
            // 也就是函数func(text string) string { return "\033[1;44m" + text + "\033[0m" }
            // 也就是日志内容渲染为高亮背景颜色为蓝色
        ... ...
        c.lg.println(when, msg)
        return nil
    }
    

    日志输出

    以下函数先将当前时间格式化成"yyyy/mm/dd hh:mm:ss"格式,然后再构造成"yyyy/mm/dd hh:mm:ss msg\n"的格式,至此日志内容构造完成,调用Write输出。

    /* ------------------------ logger.go -------------------------- */
    func (lg *logWriter) println(when time.Time, msg string) {
        lg.Lock()  
        // 竞争锁
        h, _ := formatTimeHeader(when) 
        // 将时间格式化成"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()
    }
    
    Debug、Informational、Notice、Warning、Error

    文件引擎

    创建文件引擎,输出到文件中,默认日志级别为LevelTrace,每天更换一个日志文件,日志文件保存最近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
    }
    

    设置文件引擎

    beego支持将日志写入到文件中,可以根据需要设置日志级别,设置文件路径、文件名,可以设置多少行、多少字节将日志重新写到另外一个日志文件,还可以设置每天分割一次日志文件并设置日志保存的天数:

    logs.SetLogger(logs.AdapterFile,`{"filename":"log/project.log","level":7,"maxlines":0,"maxsize":0,"daily":true,"maxdays":10}`)
    // "filename":"log/project.log" :将日志保存到当前目录下的log目录下的project.log文件中
    // "level":7 :将日志级别设为7,也就是LevelDebug
    // "maxlines":0 :设置日志文件分割条件,若文件超过maxlines,则将日志保存到下个文件中,为0表示不设置
    // "maxsize":0 :设置日志文件分割条件,若文件超过maxsize,则将日志保存到下个文件中,为0表示不设置
    // "daily":true:设置日志日否每天分割一次
    // "maxdays":10:设置保存最近几天的日志文件,超过天数的日志文件被删除,为0表示不设置
    
    
    /* ------------------------ 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()  
        // 设置之前需要加锁
        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]返回的是一个引擎的创建接口,这里为func newFileWriter() Logger
        ... ...
        lg := log()
        // 调用func newFileWriter() Logger,返回一个fileLogWriter文件引擎对象
        err := lg.Init(config)
        // 每个引擎对象都实现了Init函数
        ... ...
        return nil
    }
    
    /* ------------------------ file.go -------------------------- */
    func (w *fileLogWriter) Init(jsonConfig string) error {
        err := json.Unmarshal([]byte(jsonConfig), w)
        // jsonConfig是json格式的字符串,Unmarshal方法将其中的字段转化为对象w的字段
        ... ...
        w.suffix = filepath.Ext(w.Filename)
        // 取出后缀:.log
        w.fileNameOnly = strings.TrimSuffix(w.Filename, w.suffix)
        // 去除后缀:log/project
        if w.suffix == "" {
            w.suffix = ".log"
        }
        // 如果设置的文件后缀为空,则后缀设为".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)
        // 将设置的文件权限转化为8进制,64位的整数
        ... ...
        fd, err := os.OpenFile(w.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.FileMode(perm))
        // 以只写权限并追加写的方式打开文件(log/project.log),如果文件不存在则创建文件
        ... ...
            os.Chmod(w.Filename, os.FileMode(perm))
            // 确保文件权限为设置的值
        ... ...
        return fd, err
    }
    
    func (w *fileLogWriter) initFd() error {
        fd := w.fileWriter
        fInfo, err := fd.Stat()
        ... ...
        w.maxSizeCurSize = int(fInfo.Size())
        // 获取文件当前大小
        w.dailyOpenTime = time.Now()
        // 获取当前时间
        w.dailyOpenDate = w.dailyOpenTime.Day()
        // 获取文件打开时间
        w.maxLinesCurLines = 0
        if w.Daily {
            go w.dailyRotate(w.dailyOpenTime)
            // 创建一个线程处理日志文件按天分割工作
        }
        if fInfo.Size() > 0 {
            count, err := w.lines()
            ... ...
            w.maxLinesCurLines = count
            // 获取文件当前行数
        }
        return nil
    }
    

    日志文件分割

    beego用一个专门的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))
        // 设置定时器,定时在24小时(也就是一天)之后,会给chan变量tm发送信号
        <-tm.C
        // 定时器到期,接收到信号
        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 {
        // 如果设置了最大行数或最大字节数,是否超过了设置值
        // 如果按天分割,时间是否过了文件打开当天
        // 满足一个以上条件则返回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 {
            // 若设置了最大行数或者最大字节数,则将日志文件名称设置成格式
            // "project.yyyy-mm-dd.nnn.log",nnn从001-999,最多一天有999个日志文件
            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 {
            // 将日志文件名称设置成格式"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()
        // 关闭当前日志文件,当前日志文件一直为project.log
        err = os.Rename(w.Filename, fName)
        // 将当前日志文件重命名为前面设置的格式fName
        ... ...
        err = os.Chmod(fName, os.FileMode(rotatePerm))
        startLoggerErr := w.startLogger()
        // 再打开日志文件project.log,记录日志
        go w.deleteOldLog()
        // 开启新的线程处理文件删除工作
        ... ...
        return nil
    }
    

    日志文件保存期限

    beego允许用户设置日志文件保存天数,超高设置时间的日志文件将被删除,这个工作是由一个独立的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) {
                    // 若一个文件有project前缀以及.log后缀,而且修改日期超过日志文件保存天数,则删除
                    os.Remove(path)
                }
            }
            return
        })
    }
    

    构造日志内容并输出到日志文件

    文件引擎的日志内容构造与控制台日志内容构造差不多,以Debug为例,都是调用以下函数:

    /* ------------------------ 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 { // 若是设置的日志级别低于LevelDebug,则不输出日志到控制台,直接返回
            return
        }
        bl.writeMsg(LevelDebug, format, v...)
    }
    
    func (bl *BeeLogger) writeMsg(logLevel int, msg string, v ...interface{}) error {
        ... ...
        when := time.Now() // 当前时间
        if bl.enableFuncCallDepth {
            _, file, line, ok := runtime.Caller(bl.loggerFuncCallDepth) // 获取文件名(包括路径)和行号
            ... ...
            _, filename := path.Split(file)  // 只取文件名
            msg = "[" + filename + ":" + strconv.Itoa(line) + "] " + msg // 在日志前加上“[文件名:行号] ”字符串
        }
    
        ... ...
        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)
            ... ...
    }
    

    所有引擎都实现了Logger接口,即实现了WriteMsg方法:

    /* ------------------------ file.go -------------------------- */
    func (w *fileLogWriter) WriteMsg(when time.Time, msg string, level int) error {
        if level > w.Level { // 若日志级别超出设置的级别,则不输出
            return nil
        }
        h, d := formatTimeHeader(when) 
        // 将时间格式化成"yyyy/mm/dd hh:mm:ss "格式
        msg = string(h) + msg + "\n"
        // 将日志内容格式化为"yyyy/mm/dd hh:mm:ss msg\n"格式
        if w.Rotate {
            // 判断是否需要分割日志文件并且是否达到分割要求,若是则分割
            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()
        // 将日志写入文件之前需要竞争锁,如果有多个线程对日志文件进行写入操作,则会有竞争关系。这个问题在多线程应用中可能会成为性能瓶颈。
        _, err := w.fileWriter.Write([]byte(msg))
        // 将日志写入文件
        if err == nil {
            w.maxLinesCurLines++
            w.maxSizeCurSize += len(msg)
        }
        w.Unlock()
        return err
    }
    

    异步日志

    beego日志默认使用同步日志写入的方式。前边控制台和文件引擎在日志写入前都需要对资源进行加锁,而且每次输出日志都需要调用系统调用,非常耗时,这导致在高并发的情况下会出现性能瓶颈。

    异步日志是由一个专门的goroutine来将日志输出,调用日志输出的goroutine只需将日志内容通过信道发送给日志输出goroutine,日志输出goroutine会从信道中取出日志并输出。

    信道大小默认为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
    

    设置异步日志输出

    通过以下函数设置信道大小(2000),并触发异步日志输出:

    /* ------------------------ 程序员的源文件 -------------------------- */
    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
        // 触发异步日志输出
        if len(msgLen) > 0 && msgLen[0] > 0 {
            bl.msgChanLen = msgLen[0]
        }
        bl.msgChan = make(chan *logMsg, bl.msgChanLen)
        // 设置信道大小
        logMsgPool = &sync.Pool{
            New: func() interface{} { 
                // 若从消息池中取日志时消息池为空,则调用该函数,返回一个logMsg的空接口
                return &logMsg{}
            },
        }
        bl.wg.Add(1)
        // sync.WaitGroup只有3个方法,Add(),Done(),Wait()。 
        // 其中Done()是Add(-1)的别名。简单的来说,
        // 使用Add()添加计数,Done()减掉一个计数,计数不为0, Wait()阻塞计数为0。 
        
        go bl.startLogger()
        // 启动新的线程处理日志输出
        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
    

    构造日志内容

    以Debug为例,都是调用以下函数:

    /* ------------------------ 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 { // 若是设置的日志级别低于LevelDebug,则不输出日志到控制台,直接返回
            return
        }
        bl.writeMsg(LevelDebug, format, v...)
    }
    
    func (bl *BeeLogger) writeMsg(logLevel int, msg string, v ...interface{}) error {
        ... ...
        when := time.Now() // 当前时间
        if bl.enableFuncCallDepth {
            _, file, line, ok := runtime.Caller(bl.loggerFuncCallDepth) // 获取文件名(包括路径)和行号
            ... ...
            _, filename := path.Split(file)  // 只取文件名
            msg = "[" + filename + ":" + strconv.Itoa(line) + "] " + msg // 在日志前加上“[文件名:行号] ”字符串
        }
    
        ... ...
        msg = levelPrefix[logLevel] + msg // 在日志前加上“[D] ”字符串
        ... ...
        if bl.asynchronous { // 异步日志输出
            lm := logMsgPool.Get().(*logMsg)
            // 从logMsgPool中获取一个logMsg对象
            lm.level = logLevel
            lm.msg = msg
            lm.when = when
            // 构造日志消息
            
            bl.msgChan <- lm
            // 将日志消息通过信道传输给日志输出goroutine
        } else {
            ... ...
        }
        return nil
    }
    
    var levelPrefix = [LevelDebug + 1]string{"[M] ", "[A] ", "[C] ", "[E] ", "[W] ", "[N] ", "[I] ", "[D] "}
    

    日志输出

    异步日志输出由一个专门的线程处理:

    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
            }
        }
    }
    
    func (bl *BeeLogger) writeToLoggers(when time.Time, msg string, level int) {
        ... ...
            err := l.WriteMsg(when, msg, level)
                    // 这里将msg输出到设置的引擎,这与前边同步日志输出中控制台输出和文件输出一样
        ... ...
    }
    

    至此,关于beego logs包中的控制台和文件输出的代码学习就结束了~其他引擎的学习大致也是如此,如果有兴趣可以再看看。

    相关文章

      网友评论

          本文标题:Go beego的logs源码解读

          本文链接:https://www.haomeiwen.com/subject/pywujftx.html