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()
}
文件引擎
创建文件引擎,输出到文件中,默认日志级别为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包中的控制台和文件输出的代码学习就结束了~其他引擎的学习大致也是如此,如果有兴趣可以再看看。