欢迎关注「Keegan小钢」公众号获取更多文章
日志需求
我们都知道日志在一个程序中有着重要的作用,撮合引擎也同样需要一个完善的日志输出功能,以方便调试和查询数据。
对一个撮合引擎来说,需要输出的日志主要有以下几类:
- 程序启动的日志,包括连接 Redis 成功的日志、Web 服务启动成功的日志;
- 接口请求和响应数据的日志;
- 启动了某引擎的日志;
- 关闭了某引擎的日志;
- 订单被添加到 orderBook 的日志;
- 成交记录的日志;
- 撤单结果的日志。
另外,撮合引擎产生的日志会非常多,所以还应该做日志分割,按日期分割是最常用的日志分割方式,所以我们也同样将不同日期的日志分割到不同日志文件保存。
实现思路
首先,我们都知道日志是有分级别的,多的比如 log4j 定义了 8 种级别的日志。不过,最常用的就 4 种级别,优先级从低到高分别为:DEBUG、INFO、WARN、ERROR。一般,不同环境会设置不同的日志级别,如 DEBUG 级别一般只在开发和测试环境才设置,生产环境则会设置为 INFO 或更高级别。当设置为高级别时,低级别的日志消息是不会打印出来的。那为了打印不同级别的日志消息,可以提供不同级别的打印函数,比如提供 log.Debug()、log.Info() 等函数。
其次,日志需要输出到文件保存,因此,就需要指定文件保存的目录、文件名和文件对象。一般,保存的文件目录和运行程序应该放在一起,所以,指定的文件目录最好是相对路径。
另外,文件还要根据日期做分割,即不同日期的日志消息要保存到不同的日志文件,那么,自然要记录下当前日志的日期。以及需要定时监控,当检测到最新日期跟当前日志的日期相比已经跨日了,说明需要进行日志分割了,那就将当前的日志文件进行备份,并创建新文件用来保存新日期的日志消息。
最后,日志消息写入文件的话,那就少不了耗时的 I/O 操作,如果用同步方式写日志,无疑会减低撮合性能,因此,最好选用异步方式写日志,可以用带缓冲的通道实现。
代码实现
我重新自定义了一个 log 包,并创建了 log.go 文件,所有代码都写在该文件中。
第一步,先定义几种日志等级,直接定义成枚举类型,如下:
type LEVEL byte
const (
DEBUG LEVEL = iota
INFO
WARN
ERROR
)
第二步,定义日志的结构体,其包含的字段比较多,如下:
type FileLogger struct {
fileDir string // 日志文件保存的目录
fileName string // 日志文件名(无需包含日期和扩展名)
prefix string // 日志消息的前缀
logLevel LEVEL // 日志等级
logFile *os.File // 日志文件
date *time.Time // 日志当前日期
lg *log.Logger // 系统日志对象
mu *sync.RWMutex // 读写锁,在进行日志分割和日志写入时需要锁住
logChan chan string // 日志消息通道,以实现异步写日志
stopTickerChan chan bool // 停止定时器的通道
}
第三步,为了能将日志应用到程序中任何地方,就需要定义一个全局的日志对象,并要对该日志对象进行初始化。初始化操作有一点复杂,我们先来看代码:
const DATE_FORMAT = "2006-01-02"
var fileLog *FileLogger
func Init(fileDir, fileName, prefix, level string) error {
CloseLogger()
f := &FileLogger{
fileDir: fileDir,
fileName: fileName,
prefix: prefix,
mu: