基于go开发日志处理包

栏目: 编程工具 · 发布时间: 6年前

内容简介:最近在自己开发的go语言web框架[bingo-log] 是为了完成Github地址:

基于 go 开发日志处理包

最近在自己开发的go语言web框架 Bingo 中需要一个日志处理功能 , 看了看标准库的 log 包, 发现功能过于简单,所以想重新造个轮子,单独抽出来作为一个模块,辅助框架进行开发

[bingo-log] 是为了完成 bingo 的日志功能而开发的一个第三方包,不依赖框架,可单独在其他项目中使用,

Github地址: bingo-log

安装和使用在 README.md 中已经写的很清楚了,这里不再赘述,主要记录开发流程。

1. 预期效果

我希望这个包包含的功能:

  1. 支持多种报错级别
  2. 日志自定义配置并自动分割
  3. 可异步输出日志

2. 实现思路

准备使该日志包支持( FATAL , ERROR , WARNING , DEBUG , INFO ) 5种报错级别,

写一个日志结构体作为基础,在其中设置一个接口类型的数据,将允许自定义的方法放在这个接口中,这样所有实现该接口的对象都可以作为参数传入日志结构体中

如何实现异步功能?

为了可以限制资源消耗,使用协程连接池将每个输出放入协程池中,达到异步的效果,

连接池我就不重复造轮子了,使用一个现成的github项目: grpool

开始开发

  1. 构建最基础的底:日志结构体

首先声明两个常量,用来标记同步输出还是异步输出

const (
	LogSyncMode = iota
	LogPoolMode
)

构建结构体

type Log struct {
	Connector                    // 内嵌连接器,用来定制化功能
	sync.Mutex
	initialized     bool         // 该日志对象是否初始化
	mode            int          // 日志记录模式  同步记录 or 协程池记录
	pool            *grpool.Pool // 协程池
	poolExpiredTime int          // 协程池模式下,每个空闲协程的存活时间(秒)
	poolWorkerNum   int          // 协程池模式下,允许的最高协程数
}
  1. 构建连接器接口

我们希望使用连接器来设定每种输出,所以这个接口应该实现如下几种方法

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 参数就是外部传进来的参数

  1. 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 , 我实现了两种 ConnectorBaseConnectorKirinConnector )那么我们就实现一个基础连接器 BaseConnector :

  • 创建一个结构体

      type BaseConnector struct {
      	sync.Mutex  // 这里是因为有用到map的地方需要加锁
      }
    
  • 实现连接器接口:

    1. 先实现GetFile接口,实际就是在当前路径下创建 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
        }
    
    1. 实现 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)
         }
    
    
    1. 实现 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"
             }
    
    1. 实现5种日志级别:
          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 ~~~


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Build Your Own Web Site the Right Way Using HTML & CSS

Build Your Own Web Site the Right Way Using HTML & CSS

Ian Lloyd / SitePoint / 2006-05-02 / USD 29.95

Build Your Own Website The Right Way Using HTML & CSS teaches web development from scratch, without assuming any previous knowledge of HTML, CSS or web development techniques. This book introduces you......一起来看看 《Build Your Own Web Site the Right Way Using HTML & CSS》 这本书的介绍吧!

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具