基于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 ~~~


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

查看所有标签

猜你喜欢:

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

算法之道

算法之道

邹恒明 / 机械工业出版社 / 2010-2 / 39.00元

《算法之道》追求的目标是算法背后的逻辑,是一本启示书,而不是一本包罗万象的算法大全。因此,《算法之道》甄选了那些最能够展现算法思想、战略和精华,并能够有效训练算法思维的内容。《算法之道》将算法的讨论分为五大部分:算法基础篇、算法设计篇、算法分析篇、经典算法篇、难解与无解篇。每一个部分分别讨论算法的一大方面:基础、设计、分析、经典和难解问题。 《算法之道》既可以作为大学本科或研究生的算法教材或......一起来看看 《算法之道》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

随机密码生成器
随机密码生成器

多种字符组合密码

URL 编码/解码
URL 编码/解码

URL 编码/解码