golang log4go的日志输出优化详解
前言
在go语言中,自身已经集成了一定log模块,开发者可以使用go语言自身的log包(import“log”)。也有不少对自身log的开源封装。对于一些简单的开发,自身的log模块就已经足够应付。但是对一些大型,复杂的开发,log需要分门别类的输出,或者通过网络进行输出,自身log模块将难以应对。
当前也有一些比较重量级的log模块,比如logrus,可以实现比较复杂的功能。这里介绍一个轻量级的log模块——log4go
最近又看了一些golang的日志包和相关的文章,仔细阅读了go1.9.2系统提供的log和go-log,产生了对log4go的日志输出进行优化的想法。
结构化与multiwriter
log使用multiwriter支持多个日志输出,用Mutex加锁解决多线程日志输出的冲突。log4go则采用结构化编程用channel传递LogRecord日志记录。
原来以为channel的效率比较高……其实这是一个伪命题。channel是一个全局加锁的队列,可以用来加锁,但效率比较低。因为它多了传递数据、协调顺序处理、timout等功能,并不仅仅是加锁。跟Mutex不是一回事儿。
log4go将屏幕日志输出termlog放在了结构里,这带来一个小问题。当我们用log4go调试小程序时,运行的太快,termlog的goroutine还没有运行起来,程序就退出了。结果屏幕上没有显示日志。这个问题只能通过在Close()时加延时,等待goroutine启动来解决。然后还要检查channel……
func(f*Filter)Close(){ iff.closed{ return } //sleepatmostonesecondandletgoroutinerunning //drainthelogchannelbeforeclosing fori:=10;i>0;i--{ //MustcallSleephere,otherwise,maypanicsendonclosedchannel time.Sleep(100*time.Millisecond) iflen(f.rec)<=0{ break } } //blockwritechannel f.closed=true deferf.LogWriter.Close() close(f.rec) iflen(f.rec)<=0{ return } //drainthelogchannelandwritedriect forrec:=rangef.rec{ f.LogWrite(rec) } }
log直接将格式化日志信息输出到屏幕,简单多了。
试着兼顾两者,在log4go中增加了writer,直接输出到屏幕。拟将FileLog,SocketLog作为backend,仍然放在结构里。这样,调试小程序和生产程序可以使用同一个日志库。实测效率略有降低。不知道windows下的ColorLog如何,以后再说。
在log4go中可以通过调用SetOutput(nil),使out=nil来关闭屏幕输出。
Determinecallerfunc-it'sexpensive
这句话注释在log源文件中,log4go也要调用runtime.Caller(skipint)函数来获取源文件名和行号。它是昂贵的——消耗了CPU。建议在生产环境中关闭,log.SetSkip(-1)。如果要对log4go进行封装,设置log.SetSkip(log.GetSkip()+1)。
format优化
其实,这才是文章的主题。
日志输出避免不了打印日期和时间,linux环境下还要打印微秒,说不定还要打印时区。log4go的pattlog.go就是完成这些工作的。
- 有一个1秒更新一次的cache机制。很漂亮。
- 大量使用字符串格式化函数——fmt.Sprintf。
- 返回字符串。而writer一般支持的是[]byte。多做一次转换。
- 每次都bytes.Splite讲format字符串以%字符分解成[][]byte。
在log里边自备了一个cheap的itoa函数。
//Cheapintegertofixed-widthdecimalASCII.Giveanegativewidthtoavoidzero-padding. funcitoa(buf*[]byte,iint,widint){ //Assembledecimalinreverseorder. varb[20]byte bp:=len(b)-1 fori>=10||wid>1{ wid-- q:=i/10 b[bp]=byte('0'+i-q*10) bp-- i=q } //i<10 b[bp]=byte('0'+i) *buf=append(*buf,b[bp:]...) }
用这个函数替换日期和时间的字符串格式化函数。用[]byte代替string。
优化前,log4go的benchmark。
BenchmarkFormatLogRecord-43000004480ns/op BenchmarkConsoleLog-410000001748ns/op BenchmarkConsoleNotLogged-42000000097.5ns/op BenchmarkConsoleUtilLog-43000003496ns/op BenchmarkConsoleUtilNotLog-420000000104ns/op
优化后:
BenchmarkFormatLogRecord-410000001443ns/op BenchmarkConsoleLog-42000000982ns/op BenchmarkConsoleUtilLog-45000003242ns/op BenchmarkConsoleUtilNotLog-43000000048.4ns/op
格式化日期时间所花的时间是原来的1/3。
打印无格式化信息所花的时间是原来的1/2。
BenchmarkConsoleUtilLog调用了runtime.Caller,格式化信息,且新增了输出信息到屏幕的时间。
字符串格式化——比较昂贵。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对毛票票的支持。