最近在本身开发的go语言web框架 Bingo 中须要一个日志处理功能 , 看了看标准库的log
包, 发现功能过于简单,因此想从新造个轮子,单独抽出来做为一个模块,辅助框架进行开发git
[bingo-log] 是为了完成 bingo 的日志功能而开发的一个第三方包,不依赖框架,可单独在其余项目中使用,github
Github地址: bingo-loggolang
安装和使用在 README.md
中已经写的很清楚了,这里再也不赘述,主要记录开发流程。web
我但愿这个包包含的功能:框架
准备使该日志包支持(FATAL
,ERROR
,WARNING
,DEBUG
,INFO
) 5种报错级别,异步
写一个日志结构体做为基础,在其中设置一个接口类型的数据,将容许自定义的方法放在这个接口中,这样全部实现该接口的对象均可以做为参数传入日志结构体中函数
如何实现异步功能?ui
为了能够限制资源消耗,使用协程链接池将每一个输出放入协程池中,达到异步的效果,spa
链接池我就不重复造轮子了,使用一个现成的github项目: grpool.net
首先声明两个常量,用来标记同步输出仍是异步输出
const (
LogSyncMode = iota
LogPoolMode
)
复制代码
构建结构体
type Log struct {
Connector // 内嵌链接器,用来定制化功能
sync.Mutex
initialized bool // 该日志对象是否初始化
mode int // 日志记录模式 同步记录 or 协程池记录
pool *grpool.Pool // 协程池
poolExpiredTime int // 协程池模式下,每一个空闲协程的存活时间(秒)
poolWorkerNum int // 协程池模式下,容许的最高协程数
}
复制代码
咱们但愿使用链接器来设定每种输出,因此这个接口应该实现以下几种方法
type Connector interface {
Fatal(message ...interface{})
Error(message ...interface{})
Warning(message ...interface{})
Debug(message ...interface{})
Info(message ...interface{}) // 打印
Output(message string) // 将信息输出到文件中
GetMessage(degree int, message ...interface{}) string // 将输入的信息添加抬头(例如添加打印时间等)
GetFile(config map[string]string) *os.File // 当前日志要输出到的文件位置,传入一个map 表明配置
}
复制代码
上面5种方法是5种报错级别要作的事情,主要作的事情,就是将要输出的日志,先调用 GetMessage()
将信息进行包装,包装成咱们但愿的结构,再在控制台打印输出,而后再调用Output
方法,将日志打印到日志文件中一份
而 Output()
方法中要调用 GetFile()
方法获得要输出的文件指针,咱们能够在GetFile()
方法中设置分割文件的方式,若是须要动态分割,那么其中的map
参数就是外部传进来的参数
Log
结构体添加方法:先写如何建立一个日志对象:
func NewLog(mode int) *Log {
l := &Log{}
l.SetMode(mode)
l.initialize() // 这里对结构体中的数据作初始化
return l
}
复制代码
而后加载链接器
// 加载链接器
func (l *Log) LoadConnector(conn Connector) {
l.Connector = conn // 全部实现了链接器接口的对象均可以做为参数传入
}
复制代码
而后写5种报错级别:
// 重写5种日志级别的打印函数
func (l *Log) Fatal(message string) {
// 根据模式
l.exec(l.Connector.Fatal, message)
}
func (l *Log) Error(message string) {
l.exec(l.Connector.Error, message)
}
func (l *Log) Warning(message string) {
l.exec(l.Connector.Warning, message)
}
func (l *Log) Debug(message string) {
l.exec(l.Connector.Debug, message)
}
func (l *Log) Info(message string) {
l.exec(l.Connector.Info, message)
}
复制代码
上方的 exec
方法就是根据输出模式选择直接输出,仍是使用协程池输出:
func (l *Log) exec(f func(message ...interface{}), message string) {
// 同步模式
if l.mode == LogSyncMode {
l.Lock()
defer l.Unlock()
f(message)
} else if l.mode == LogPoolMode { // 协程池异步模式
l.initialize() // 先初始化
l.Lock()
defer l.Unlock()
l.AddWaitCount(1) // 向池中添加计数器,能够计算池中有多少协程正在被使用
l.pool.JobQueue <- func() {
f(message)
defer l.pool.JobDone()
}
}
}
复制代码
从上面的代码能够看出,Log
结构体只是负责同步仍是异步执行,最重要的地方是链接器Connector
, 我实现了两种Connector
(BaseConnector
和KirinConnector
)那么咱们就实现一个基础链接器BaseConnector
:
建立一个结构体
type BaseConnector struct {
sync.Mutex // 这里是由于有用到map的地方须要加锁
}
复制代码
实现链接器接口:
bingo.log
文件,并返回文件指针:// 返回一个文件句柄,用来写入数据
func (b BaseConnector) GetFile(config map[string]string) *os.File { // 默认状况下,输出到当前路径下的bingo.log文件中
dir, err := os.Getwd()
if err != nil {
panic(err)
}
path := dir + "/bingo.log" // 真实要保存的文件位置
// 判断文件是否存在
if _, err := os.Stat(path); err != nil {
// 文件不存在,建立
f, err := os.Create(path)
//defer f.Close() // 关闭操做要放在调用位置
if err != nil {
panic(err)
}
return f
}
// 打开该文件,追加模式
f, err := os.OpenFile(path, os.O_WRONLY, os.ModeAppend)
if err != nil {
panic(err)
}
return f
}
复制代码
Output
方法:func (b BaseConnector) Output(message string) {
// 获取到要输出的文件路径
file := b.GetFile(make(map[string]string))
defer file.Close()
n, _ := file.Seek(0, os.SEEK_END) // 向文件末尾追加数据
// 写入数据
file.WriteAt([]byte(message), n)
}
复制代码
GetMessage
方法,这里是将要输出的日志包装成 指望的格式:// 输出格式为 [日志级别][时间][日志内容]
func (b BaseConnector) GetMessage(degree int, message ...interface{}) string {
var title string
switch degree {
case FATAL:
title = "[FATAL] "
case ERROR:
title = "[ERROR] "
case WARNING:
title = "[WARNING]"
case DEBUG:
title = "[DEBUG] "
case INFO:
title = "[INFO]"
default:
title = "[UNKNOWN]"
}
// 将传入的信息扩展一下
// 默认添加当前时间
return title + "[" + time.Now().Format("2006-01-02 15:04:05") + "] " + fmt.Sprint(message...) + "\n"
}
复制代码
func (b BaseConnector) Info(message ...interface{}) {
// 绿色输出在控制台
m := b.GetMessage(INFO, message...)
fmt.Print(clcolor.Green(m))
// 输出在文件中
b.Output(m)
}
复制代码
为了在控制台中达到以不一样的颜色输出不一样级别的日志,咱们要在打印函数中加上颜色,具体方式在这里给终端来点彩色(c语言和Golang版)
我这里直接使用了一个别人写好的第三方包xcltapestry/xclpkg
直接使用clcolor.Green()
便可
这样,一个基本的链接器就制做好了,咱们能够随时自行扩展
使用方式相似于:
log := bingo_log.NewLog(bingo_log.LogSyncMode)
conn := new(bingo_log.BaseConnector)
log.LoadConnector(conn)
log.Info("testing")
log.Debug("testing")
log.Warning("testing")
log.Error("testing")
log.Fatal("testing")
复制代码
接口是golang种极其强大的特性,咱们能够利用接口完成不少动态结构
最后再推荐一下本身的 WEB 框架 Bingo,求 star,求 PR ~~~