Go Tips: 定时更新日志的输出位置
Go语言自带的log package非常简单和易用,只要在启动程序的时候调用log.SetOutput配置日志文件的路径,然后通过调用log.Printf()、log.Panic() 等方法就可以将日志输出到文件了。然而,对于一些服务程序而言,当操作系统启动时就开始运行,那么所有日志都会输出到启动时配置的那个文件当中。实际应用中,为了查看方便,我们经常需要按时间来生成日志,比如每天生成一个日志。那么可以通过单独运行一个goroutine,定时检查日志的最后创建时间来重新调用log.SetOutput方法,从而设置新的日志输出路径。
下面的代码实现了这一过程:
package main import ( "fmt" "log" "math/rand" "os" "path/filepath" "time" ) func main() { testMyLog() } func testMyLog() { // logger := &MyLog{LoopDuration: time.Second, FileFormat: "20060102150405"} // 测试配置 logger := &MyLog{} logger.Setup() for i := 0; i < 5; i++ { fmt.Printf("%v, 第%d次循环\n", time.Now().Format("150405"), i+1) log.Printf("第%d次循环\n", i+1) time.Sleep(time.Duration(rand.Intn(2000)) * time.Millisecond) } } // MyLog 日志 type MyLog struct { lastFile *os.File // 最后创建的日志文件 lastFileDate *time.Time // 最后创建日志文件的时间 FileFormat string // 文件格式 LoopDuration time.Duration // 循环检查的时间间隔 } // Setup 配置文件 func (x *MyLog) Setup() { if x.LoopDuration.Seconds() == 0 { x.LoopDuration = time.Minute } if x.FileFormat == "" { x.FileFormat = "20060102" } runningDir, _ := filepath.Abs(filepath.Dir(os.Args[0])) logDir := runningDir + "/logs" fmt.Printf("日志目录: %s\n", logDir) if _, err := os.Stat(logDir); os.IsNotExist(err) { os.Mkdir(logDir, 0755) } // 先设置一遍 x.setLogFile() go func() { for { time.Sleep(x.LoopDuration) x.setLogFile() } }() } func (x *MyLog) setLogFile() { if x.needNewFile() { path := fmt.Sprintf("./logs/%s", time.Now().Format(x.FileFormat)) newFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) if err != nil { fmt.Printf("os.OpenFile(): %v\n", err) } else { // 关闭旧日志文件 if x.lastFile != nil { err := x.lastFile.Close() if err != nil { fmt.Printf("x.lastFile.Close(): %v\n", err) } } // 更新日期 x.lastFile = newFile now := time.Now() x.lastFileDate = &now // 配置新日志 fmt.Printf("%v, 配置日志:%s\n", time.Now().Format("20060102150405"), newFile.Name()) log.SetOutput(newFile) } } } func (x *MyLog) needNewFile() bool { if x.lastFileDate == nil || x.lastFile == nil { return true } compare := "20060102" //compare := "20060102150405" // 测试配置 now := time.Now().Format(compare) last := x.lastFileDate.Format(compare) if now != last { return true } return false }
将上面“测试配置”的注释打开后(每秒钟生成一个日志文件),输出如下:
# go run main.go 日志目录: /Users/zhangzy/code/go/src/test/logs 20181222122614, 配置日志:./logs/20181222122614 122614, 第1次循环 122614, 第2次循环 20181222122615, 配置日志:./logs/20181222122615 122616, 第3次循环 20181222122616, 配置日志:./logs/20181222122616 20181222122617, 配置日志:./logs/20181222122617 122617, 第4次循环 122618, 第5次循环
可以看到一共创建了4个日志文件,并将结果写入了进去。这里有一个不完善的地方,就是可能出现空日志。
对于日志这样的通用功能,github上面已经有很多的开源项目。使用现成的项目会更方便快捷,自己重复造轮子需要花更多时间,而且还可能存在更多的潜在BUG。但在自己重复造轮子这个过程中,对这门语言却可以学到更多东西,而不仅仅是调调别人的API而已。
上面的代码还有一个主要的问题:它仅仅实现了按时间来创建新文件,如果想根据其他规则,例如根据不同的用户来创建日志(每个用户创建一个日志),则不支持了。此时,可以在MyStruct中包含一个*log.Logger对象,然后在这个对象上调用SetOutput()配置日志位置,再通过Printf()方法来输出日志。
除此以外,Golang自带的日志并没有提供分级功能(Trace、Debug、Info、Warn、Error、Fatal),而对熟悉Log4j等著名日志库的同学来说,这是必不可少的功能。这些同样可以通过扩展MyLog、调用SetPrefix()配置前缀来实现。这里就不演示了,以后有时间再完善一下。
感谢阅读,希望这篇文章能给你带来帮助!