安装说明
这是一个用来处理日志的库,它的设计思路来自于 database/sql,目前支持的引擎有 file、console、net、smtp、es、slack,可以通过如下方式进行安装:
go get github.com/astaxie/beego/logs
使用需要导入包:
import github.com/astaxie/beego/logs
添加输出引擎(log 支持同时输出到多个引擎):
logs.SetLogger("console")
//添加输出引擎也支持第二个参数,用来表示配置信息,不同引擎,配置不同
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接口定义了每一种适配器需要实现的四个功能:
- 初始化
- 写入消息
- 销毁
- 刷新
// 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,关联了每个适配器的名称以及对应的创建方法
type newLoggerFunc func() Logger
var adapters = make(map[string]newLoggerFunc)
- 注册方法
//Register 根据名称进行日志注册,名称重复注册或 nil 会 panics
func Register(name string, log newLoggerFunc) {
......
adapters[name] = log
}
- 示例:注册file适配器
//=======================log.go==========================
type fileLogWriter struct {......}
func init() {
Register(AdapterFile, newFileWriter)
}
//创建一个 FileLogWriter 对象作为 LoggerInterface 的返回
func newFileWriter() Logger {
w := &fileLogWriter{
Daily: true,
MaxDays: 7,
......
}
return w
}
日志模块主体
- 主要结构体
//=======================log.go==========================
// BeeLogger 结构体声明
// 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
}
//结构体:日志名
type nameLogger struct {
Logger
name string
}
//结构体:日志信息
type logMsg struct {
level int
msg string
when time.Time
}
- 日志模块创建了一个全局变量beeLogger,默认使用控制台输出日志
// 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 引用使用的应用 logger
var beeLogger = NewLogger()
- 使用
通过创建NewLogger实例(也就是初始化了一个BeeLogger),然后调用SetLogger()设置使用的日志适配器。
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")
默认设置
- 默认日志等级
LevelDebug
//=======================log.go==========================
// RFC5424 log message levels.
const (
LevelEmergency = iota
LevelAlert
LevelCritical
LevelError
LevelWarning
LevelNotice
LevelInformational
LevelDebug
)
//创建 Logger 实例时,默认 LevelDebug 等级
func NewLogger(channelLens ...int64) *BeeLogger {
bl := new(BeeLogger)
bl.level = LevelDebug
......
return bl
}
- 默认适配器
console控制台
//=======================console.go=======================
// consoleWriter 实现了 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 创建一个 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}
}
提供的功能
- 设置日志等级
//=======================log.go==========================
func (bl *BeeLogger) SetLevel(l int) {
bl.level = l
}
func SetLevel(l int) {
beeLogger.SetLevel(l)
}
- 记录日志文件和行数
//=======================log.go==========================
//方法:设置在日志中记录文件名
func EnableFuncCallDepth(b bool) {
beeLogger.enableFuncCallDepth = b
}
//方法:设置在日志中记录行号
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
- 设置日志颜色
//=======================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
/**
创建一个颜色刷子
格式:\033[显示方式;前景色;背景色m
参数说明:
前景色 背景色 颜色
---------------------------------------
30 40 黑色
31 41 红色
32 42 绿色
33 43 黃色
34 44 蓝色
35 45 紫红色
36 46 青蓝色
37 47 白色
显示方式 意义
-------------------------
0 终端默认设置
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 方法中调用颜色渲染
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)
}
......
}
- 业务中记录日志
log.Trace("trace")
log.Info("info")
log.Warn("warning")
log.Debug("debug")
log.Critical("critical")
生成日志
- 生成日志内容
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 {
//未初始化进行加锁,设置默认日志适配器
if !bl.init {
bl.lock.Lock()
bl.setLogger(AdapterConsole)
bl.lock.Unlock()
}
//日志长度>0,进行格式化
if len(v) > 0 {
msg = fmt.Sprintf(msg, v...)
}
msg = bl.prefix + " " + msg
//设置当前时间
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
}
//在文件名信前增加,日志等级标志,如:【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
}
//异步操作通过日志信息池获取消息并发送给消息管道,同步直接写入
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==========================
//在控制台调用WriteMsg写入日志时触发
func (c *consoleWriter) WriteMsg(when time.Time, msg string, level int) error {
......
c.lg.writeln(when, msg)
return nil
}
文件日志适配器
- 文件适配器结构体和构造函数
type fileLogWriter struct {......}
func newFileWriter() Logger {
w := &fileLogWriter{
Daily: true, //按日分割
MaxDays: 7, //最大保留天数
Hourly: false, //按小时分割
MaxHours: 168, //保持小时数
// 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, //默认日志等级
Perm: "0660",
MaxLines: 10000000, //最大行数
MaxFiles: 999, //最大文件数
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())
//获取当前时间
w.dailyOpenTime = time.Now()
w.dailyOpenDate = w.dailyOpenTime.Day()
//获取文件打开时间
w.hourlyOpenTime = time.Now()
w.hourlyOpenDate = w.hourlyOpenTime.Hour()
w.maxLinesCurLines = 0
//启动新的线程并发处理文件分割
if w.Hourly {
go w.hourlyRotate(w.hourlyOpenTime)
} else if w.Daily {
go w.dailyRotate(w.dailyOpenTime)
}
//文件行数
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) {
// 设置定时器,定时器间隔每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))
//以定时间隔从渠道获取数据
<-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()
}
- 创建日志文件
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))
//打开文件,如果不存在则创建,操作权限:只写并追加写权限
fd, err := os.OpenFile(w.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.FileMode(perm))
if err == nil {
//确认操作权限是用户设置的
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) {
//如果执行中有 recover,延迟到方法结束返回错误
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日志默认使用同步日志写入的方式,因为写入文件前需要进行加锁,并发下有明显的性能影响,所以采用异步日志写入,由不同的 goroutine 来处理日志输出、发送日志给渠道、从渠道接收日志。
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)
......
}
- 设置渠道大小(默认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
}
//设置日志为异步
bl.asynchronous = true
if len(msgLen) > 0 && msgLen[0] > 0 {
bl.msgChanLen = msgLen[0]
}
//设置渠道大小
bl.msgChan = make(chan *logMsg, bl.msgChanLen)
//从消息池中取消息,如果为空时,返回哟1个 logMsg 空接口
logMsgPool = &sync.Pool{
New: func() interface{} {
return &logMsg{}
},
}
//同步计数+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 {
//从渠道获取数据,并写入
case bm := <-bl.msgChan:
bl.writeToLoggers(bm.when, bm.msg, bm.level)
logMsgPool.Put(bm)
//给渠道发送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
}
}
}