Go beego的logs源码解读

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包中的控制台和文件输出的代码学习就结束了~其他引擎的学习大致也是如此,如果有兴趣可以再看看。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,596评论 18 139
  • 1.ios高性能编程 (1).内层 最小的内层平均值和峰值(2).耗电量 高效的算法和数据结构(3).初始化时...
    欧辰_OSR阅读 29,300评论 8 265
  • 在应用程序中添加日志记录总的来说基于三个目的:监视代码中变量的变化情况,周期性的记录到文件中供其他应用进行统计分析...
    时待吾阅读 4,957评论 0 6
  • 在应用程序中添加日志记录总的来说基于三个目的:监视代码中变量的变化情况,周期性的记录到文件中供其他应用进行统计分析...
    时待吾阅读 4,941评论 1 13
  • 看了这本书,我突然觉得对不起我老公了,因为我比我老公大八岁,是典型的老牛吃嫩草。结婚前还可以依靠姿色抓住老公的心,...
    很哈阅读 237评论 1 0