glog简介
glog是著名的google开源C++日志库glog的golang版本,glog是一个轻量级的日志库,上手简单不需要配置文件并且稳定高效,但是可以自定义控制的内容就少了。 glog主要有以下几个特点:
- glog有四种日志等级INFO < WARING < ERROR < FATAL,不同等级的日志是打印到不同文件的,低等级的日志文件中(INFO)会包含高等级的日志信息(ERROR)
- 通过命令行传递参数 --log_dir指定日志文件的存放目录,默认为os.TempDir()
- 可以根据文件大小切割日志文件,但是不能根据日期切割日志文件
- 日志输出格式是固定的(Lmmdd hh:mm:ss.uuuuuu threadid file:line] msg...)不可以自定义
- 在程序开始时需要调用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)
//创建新的日志文件
}
有疑问加站长微信联系(非本文作者)