安裝說明
這是一個用來處理日志的庫,它的設(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)的四個功能:
- 初始化
- 寫入消息
- 銷毀
- 刷新
// 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
}
}
}