分享
  1. 首页
  2. 文章

golang日志库glog解析

shanks · · 23500 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

glog简介

glog是著名的google开源C++日志库glog的golang版本,glog是一个轻量级的日志库,上手简单不需要配置文件并且稳定高效,但是可以自定义控制的内容就少了。 glog主要有以下几个特点:

  1. glog有四种日志等级INFO < WARING < ERROR < FATAL,不同等级的日志是打印到不同文件的,低等级的日志文件中(INFO)会包含高等级的日志信息(ERROR)
  2. 通过命令行传递参数 --log_dir指定日志文件的存放目录,默认为os.TempDir()
  3. 可以根据文件大小切割日志文件,但是不能根据日期切割日志文件
  4. 日志输出格式是固定的(Lmmdd hh:mm:ss.uuuuuu threadid file:line] msg...)不可以自定义
  5. 在程序开始时需要调用flag.Parse()解析命令行参数,在程序退出时需要调用glog.Flush() 确保将缓存区中的内容输出到文件中。

使用事例

func main() {
 //初始化命令行参数
 flag.Parse()
 
 //退出时调用,确保日志写入文件中
 defer glog.Flush()
 
 glog.Info("hello, glog")
 glog.Warning("warning glog")
 glog.Error("error glog")
 glog.Infof("info %d", 1)
 glog.Warningf("warning %d", 2)
 glog.Errorf("error %d", 3)
 }
//假设编译后的可执行程序名为demo,运行时指定log_dir参数将日志文件保存到特定的目录
// ./demo --log_dir=./log

源码分析

我们顺着事例代码中的 glog.Error("error glog") 这行代码来看下,来看下日志内容是如何输出到文件中去的。

func Error(args ...interface{}) {
 logging.print(errorLog, args...)
}
//errorLog是glog定义的日志等级标记,底层是一个int32类型的变量
type severity int32 
const (
 infoLog severity = iota
 warningLog
 errorLog
 fatalLog
 numSeverity = 4
)
// Error函数实际只是做了一层简单的封装,实际调用的是loggering对象的print函数,loggering是一个loggingT类型的全局变量
func (l *loggingT) print(s severity, args ...interface{}) {
 l.printDepth(s, 1, args...)
}
//printDepth可以指定输出日志栈的调用层次 
func (l *loggingT) printDepth(s severity, depth int, args ...interface{}) {
//header构造格式化的附加信息 Lmmdd hh:mm:ss.uuuuuu threadid file:line],glog在这个过程中做了很多优化,具体查看源码
//header函数中会从一个freeList中取buffer对象,如果不存在则会创建新的buffer对象,在使用完后调用 putBuffer将buffer放回到freeList中
 buf, file, line := l.header(s, depth)
 fmt.Fprint(buf, args...)
 if buf.Bytes()[buf.Len()-1] != '\n' {
 buf.WriteByte('\n')
 }
 l.output(s, buf, file, line, false)
}
func (l *loggingT) output(s severity, buf *buffer, file string, line int, alsoToStderr bool) {
 data := buf.Bytes()
 //glog会为每个级别的日志创建不同的日志文件,打印日志时首先要保证该级别的日志文件已存在
 if l.file[s] == nil {
 if err := l.createFiles(s); err != nil {
 os.Stderr.Write(data) 
 l.exit(err)
 }
 }
 //glog会将高级别的日志信息打印到低级别的日志文件中
 //去掉代码段中的 fallthrough,则可以实现error日志只输出到error文件中,而不会继续输出到info级别的日志文件中
 switch s {
 case fatalLog:
 l.file[fatalLog].Write(data)
 fallthrough
 case errorLog:
 l.file[errorLog].Write(data)
 fallthrough
 case warningLog:
 l.file[warningLog].Write(data)
 fallthrough
 case infoLog:
 l.file[infoLog].Write(data)
 }
 if s == fatalLog {
 //如果是FATAL日志信息,则退出程序
 os.Exit(255)
 }
 //将使用完的buffer对象放到缓冲池中
 l.putBuffer(buf)
}
//loggingT.file是一个flushSyncWriter接口类型的数组,在glog中实际的对象是syncBuffer,syncBuffer封装了底层的写文件操作,增加了缓冲区避免过于频繁的系统调用提高写日志效率
type syncBuffer struct {
 *bufio.Writer
 file *os.File
 sev severity
 nbytes uint64 // The number of bytes written to this file
}
//写入日志前会判断日志文件是否已经超过指定的最大尺寸,如果超过则创建新的日志文件
//日志内容会先写入到内存中 sb.Writer = bufio.NewWriterSize(sb.file, bufferSize)
func (sb *syncBuffer) Write(p []byte) (n int, err error) {
 if sb.nbytes+uint64(len(p)) >= MaxSize {
 if err := sb.rotateFile(time.Now()); err != nil {
 sb.logger.exit(err)
 }
 }
 n, err = sb.Writer.Write(p)
 sb.nbytes += uint64(n)
 return
}
//我们通过调用syncBuffer.Write函数将日志内容输出,但是syncBuffer缓冲区中的内容是在什么时候输出到文件中的呢
//glog的init函数中会开启一个 goroutine定时的调用 flushSyncWriter的Flush函数将内存中的日志内容刷到文件中 
func init() {
 go logging.flushDaemon()
}
func (l *loggingT) flushDaemon() {
 for _ = range time.NewTicker(flushInterval).C {
 for s := fatalLog; s >= infoLog; s-- {
 file := l.file[s]
 if file != nil {
 file.Flush() 
 file.Sync() 
 }
 }
}

vlog简介

一般的日志库会提供日志输出级别,当日志信息的级别低于输出级别时则不会输出该日志信息。我们使用其他日志库时会使用log.Debug()打印出调试信息,在测试环境下将日志库的输出级别设置为DEBUG,调试信息就会输出便于我们查看程序的具体运行情况,而在线上程序中将日志的输出级别设置为INFO调试信息就不会输出。 glog则采用另外一种方式实现这种功能,glog提供让用户自定义分级信息的功能,用户自定义分级与glog自带的日志等级(INFO ERROR)是完全分离的,在命令行参数设置中独立设置"v"或"vmodule"参数来控制。

if glog.V(1) {
 glog.Info("Starting transaction...")
}
glog.V(1).Infoln("Processed", nItems, "elements")

在测试环境下我们运行程序时指定用户自定义级别为1 (--v=1),上面的日志信息就会输出。 而在线上环境中指定自定义级别为0(--v=0),上面的日志信息则不会输出。

func init(){
 flag.Var(&logging.verbosity, "v", "log level for V logs")
}
type Verbose bool
func V(level Level) Verbose {
 if logging.verbosity.get() >= level {
 return Verbose(true)
 }
 return Verbose(false)
}
func (v Verbose) Info(args ...interface{}) {
 if v {
 logging.print(infoLog, args...)
 }
}

修改glog源码

glog有些功能与我们常用的日志库不太一样或者没有我们期望的功能,可以修改glog的源码来实现我们的需求。比如我们之前使用的日志库是有DEBUG INFO ERROR FATAL级别的,我们可以修改glog源码增加DEBUG级别,删除WARN级别,已于我们的原有系统保持一致。 具体修改内容查看github源码

设置等级控制日志的输出 实现原理:定义一个输出等级变量,提供接口给用户可以设置该变量的值,默认为INFO,在输出日志时检查日志信息的等级是否大于输出等级,如果大于则输出日志信息否则不输出

var outputSeverity severity
//outputLevel 必须为INFO ERROR等字符串,否则panic
//SetLevelString 不是线程安全的,主要是因为我都是在程序开启时在主进程中调用一次SetLevelString函数,而不会在程序运行中随意调用
func SetLevelString(outputLevel string) {
 severity, ok := severityByName(outputLevel)
 if !ok {
 panic(fmt.Errorf("unknown severity name %s", outputLevel))
 }
 outputSeverity = severity
}
func (l *loggingT) println(s severity, args ...interface{}) {
 if s < outputSeverity {
 return
 }
 buf, file, line := l.header(s, 0)
 fmt.Fprintln(buf, args...)
 l.output(s, buf, file, line, false)
}
//用户在测试环境下调用 SetLevelString("DEBUG")则调试信息能够正常输出到文件中,而在线上环境下调用SetLevelString("INFO")屏蔽调试信息

每天自动切割日志文件

实现原理:在创建日志文件时记录下创建文件的日期(MMDD),输出每条日志信息时判断当前日期与日志文件的创建日期是否一致,如果不一致则创建新的日志文件。

 func init() {
 flag.BoolVar(&logging.dailyRolling, "dailyRolling", false, " weather to handle log files daily")
 }
 
func (sb *syncBuffer) Write(p []byte) (n int, err error) {
 if logging.dailyRolling {
 if sb.createdDate != string(p[1:5]) {
 if err := sb.rotateFile(time.Now()); err != nil {
 sb.logger.exit(err)
 }
 }
 }
 //写日志信息
}
func (sb *syncBuffer) rotateFile(now time.Time) error {
 sb.createdDate = fmt.Sprintf("%02d%02d", month, day)
 //创建新的日志文件
}

有疑问加站长微信联系(非本文作者)

本文来自:shanks's blog

感谢作者:shanks

查看原文:golang日志库glog解析

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
23500 次点击
暂无回复
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏