基于go开发日志处理包

silsuer · · 867 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。
# 基于go开发日志处理包 最近在自己开发的go语言web框架 [Bingo](https://github.com/silsuer/bingo) 中需要一个日志处理功能 , 看了看标准库的`log`包, 发现功能过于简单,所以想重新造个轮子,单独抽出来作为一个模块,辅助框架进行开发 [bingo-log] 是为了完成 [bingo](https://github.com/silsuer/bingo) 的日志功能而开发的一个第三方包,不依赖框架,可单独在其他项目中使用, Github地址: [bingo-log](https://github.com/silsuer/bingo-log) 安装和使用在 `README.md` 中已经写的很清楚了,这里不再赘述,主要记录开发流程。 ### 1. 预期效果 我希望这个包包含的功能: 1. 支持多种报错级别 2. 日志自定义配置并自动分割 3. 可异步输出日志 ### 2. 实现思路 准备使该日志包支持(`FATAL`,`ERROR`,`WARNING`,`DEBUG`,`INFO`) 5种报错级别, 写一个日志结构体作为基础,在其中设置一个接口类型的数据,将允许自定义的方法放在这个接口中,这样所有实现该接口的对象都可以作为参数传入日志结构体中 如何实现异步功能? 为了可以限制资源消耗,使用协程连接池将每个输出放入协程池中,达到异步的效果, 连接池我就不重复造轮子了,使用一个现成的github项目: [grpool](https://github.com/ivpusic/grpool) ### 开始开发 1. 构建最基础的底:日志结构体 首先声明两个常量,用来标记同步输出还是异步输出 ```go const ( LogSyncMode = iota LogPoolMode ) ``` 构建结构体 ```go type Log struct { Connector // 内嵌连接器,用来定制化功能 sync.Mutex initialized bool // 该日志对象是否初始化 mode int // 日志记录模式 同步记录 or 协程池记录 pool *grpool.Pool // 协程池 poolExpiredTime int // 协程池模式下,每个空闲协程的存活时间(秒) poolWorkerNum int // 协程池模式下,允许的最高协程数 } ``` 2. 构建连接器接口 我们希望使用连接器来设定每种输出,所以这个接口应该实现如下几种方法 ```go 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`参数就是外部传进来的参数 3. 为`Log` 结构体添加方法: - 先写如何创建一个日志对象: ```go func NewLog(mode int) *Log { l := &Log{} l.SetMode(mode) l.initialize() // 这里对结构体中的数据做初始化 return l } ``` - 然后加载连接器 ```go // 加载连接器 func (l *Log) LoadConnector(conn Connector) { l.Connector = conn // 所有实现了连接器接口的对象都可以作为参数传入 } ``` - 然后写5种报错级别: ```go // 重写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` 方法就是根据输出模式选择直接输出,还是使用协程池输出: ```go 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`: - 创建一个结构体 ```go type BaseConnector struct { sync.Mutex // 这里是因为有用到map的地方需要加锁 } ``` - 实现连接器接口: 1. 先实现GetFile接口,实际就是在当前路径下创建`bingo.log`文件,并返回文件指针: ```go // 返回一个文件句柄,用来写入数据 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 } ``` 2. 实现`Output`方法: ```go 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) } ``` 3. 实现`GetMessage` 方法,这里是将要输出的日志包装成 期望的格式: ```go // 输出格式为 [日志级别][时间][日志内容] 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" } ``` 4. 实现5种日志级别: ```go func (b BaseConnector) Info(message ...interface{}) { // 绿色输出在控制台 m := b.GetMessage(INFO, message...) fmt.Print(clcolor.Green(m)) // 输出在文件中 b.Output(m) } ``` 为了在控制台中达到以不同的颜色输出不同级别的日志,我们要在打印函数中加上颜色,具体方式在这里[给终端来点彩色(c语言和Golang版)](https://blog.csdn.net/xcl168/article/details/41819735) 我这里直接使用了一个别人写好的第三方包[xcltapestry/xclpkg](https://github.com/xcltapestry/xclpkg) 直接使用`clcolor.Green()` 即可 这样,一个基本的连接器就制作好了,我们可以随时自行扩展 ### 小结 使用方式类似于: ```go 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](https://github.com/silsuer/bingo),求 star,求 PR ~~~

有疑问加站长微信联系(非本文作者))

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:701969077

867 次点击  
加入收藏 微博
6 回复  |  直到 2018-10-23 16:20:20
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传