golang日志库glog解析

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 = iotawarningLogerrorLogfatalLognumSeverity = 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)fallthroughcase errorLog:l.file[errorLog].Write(data)fallthroughcase warningLog:l.file[warningLog].Write(data)fallthroughcase 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.Writerfile   *os.Filesev    severitynbytes 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)//创建新的日志文件
}


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部