• 企业400电话
  • 微网小程序
  • AI电话机器人
  • 电商代运营
  • 全 部 栏 目

    企业400电话 网络优化推广 AI电话机器人 呼叫中心 网站建设 商标✡知产 微网小程序 电商运营 彩铃•短信 增值拓展业务
    golang log4go的日志输出优化详解

    前言

    在go语言中,自身已经集成了一定log模块,开发者可以使用go语言自身的log包(import “log”) 。也有不少对自身log的开源封装。对于一些简单的开发,自身的log模块就已经足够应付。但是对一些大型,复杂的开发,log需要分门别类的输出,或者通过网络进行输出,自身log模块将难以应对。

    当前也有一些比较重量级的log模块,比如logrus,可以实现比较复杂的功能。这里介绍一个轻量级的log模块——log4go

    最近又看了一些golang的日志包和相关的文章,仔细阅读了go 1.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() {
     if f.closed {
      return
     }
     // sleep at most one second and let go routine running
     // drain the log channel before closing
     for i := 10; i > 0; i-- {
      // Must call Sleep here, otherwise, may panic send on closed channel
      time.Sleep(100 * time.Millisecond)
      if len(f.rec) = 0 {
       break
      }
     }
     // block write channel
     f.closed = true
     defer f.LogWriter.Close()
     close(f.rec)
     if len(f.rec) = 0 {
      return
     }
     // drain the log channel and write driect
     for rec := range f.rec {
      f.LogWrite(rec)
     }
    }

    log直接将格式化日志信息输出到屏幕,简单多了。

    试着兼顾两者,在 log4go 中增加了 writer,直接输出到屏幕。拟将FileLog,SocketLog作为backend,仍然放在结构里。这样,调试小程序和生产程序可以使用同一个日志库。实测效率略有降低。不知道 windows 下的 ColorLog 如何,以后再说。

    在log4go中可以通过调用 SetOutput(nil) ,使out = nil 来关闭屏幕输出。

    Determine caller func - it's expensive

    这句话注释在 log 源文件中,log4go也要调用runtime.Caller(skip int)函数来获取源文件名和行号。它是昂贵的——消耗了CPU。建议在生产环境中关闭,log.SetSkip(-1) 。如果要对log4go进行封装,设置 log.SetSkip(log.GetSkip()+1)

    format优化

    其实,这才是文章的主题。

    日志输出避免不了打印日期和时间,linux 环境下还要打印微秒,说不定还要打印时区。log4go的pattlog.go就是完成这些工作的。

    在log里边自备了一个cheap的itoa函数。

    // Cheap integer to fixed-width decimal ASCII. Give a negative width to avoid zero-padding.
    func itoa(buf *[]byte, i int, wid int) {
     // Assemble decimal in reverse order.
     var b [20]byte
     bp := len(b) - 1
     for i >= 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-4    300000    4480 ns/op
    BenchmarkConsoleLog-4     1000000    1748 ns/op
    BenchmarkConsoleNotLogged-4    20000000    97.5 ns/op
    BenchmarkConsoleUtilLog-4     300000    3496 ns/op
    BenchmarkConsoleUtilNotLog-4   20000000    104 ns/op

    优化后:

    BenchmarkFormatLogRecord-4  1000000    1443 ns/op
    BenchmarkConsoleLog-4   2000000    982 ns/op
    BenchmarkConsoleUtilLog-4   500000    3242 ns/op
    BenchmarkConsoleUtilNotLog-4 30000000    48.4 ns/op

    格式化日期时间所花的时间是原来的1/3。

    打印无格式化信息所花的时间是原来的1/2。

    BenchmarkConsoleUtilLog调用了runtime.Caller,格式化信息,且新增了输出信息到屏幕的时间。

    字符串格式化——比较昂贵。

    总结

    以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对脚本之家的支持。

    您可能感兴趣的文章:
    • Golang报“import cycle not allowed”错误的2种解决方法
    • Golang常见错误之值拷贝和for循环中的单一变量详解
    • Golang巧用defer进行错误处理的方法
    • golang新手们容易犯的3个错误总结
    • Golang中重复错误处理的优化方法
    上一篇:golang对自定义类型进行排序的解决方法
    下一篇:golang如何实现mapreduce单进程版本详解
  • 相关文章
  • 

    © 2016-2020 巨人网络通讯 版权所有

    《增值电信业务经营许可证》 苏ICP备15040257号-8

    golang log4go的日志输出优化详解 golang,log4go,的,日志,输出,