log - The Go Programming Language (golang.org)
import ( "log" ) func main() { log.Print("Logging in Go!") }
log模块的Fatal与Panic开头的function都会直接终止程序运行(os.Exit(1)),这点需要注意。
log模块只有Print与Fatal、Panic三个level对应的函数,可以通过SetOutput函数定义输出目的地,如下为输出至文件的示例。
package main import ( "log" "os" ) func main() { file, _ := os.OpenFile("my.log", os.O_CREATE|os.O_RDWR,0644) defer file.Close() logger := &log.Logger{} logger.SetOutput(file) for i:=0; i<50; i++{ logger.SetFlags(i) // 经测试,有效的flag值有32个,表示各种不同的输出格式,平时我们选取比较顺眼的flag即可,可以通过一些const参数指定,见下例 logger.Print("------") } }
go源生log包虽然可以用,但比较原始。无法便捷的实现像java,python那样快速的格式化输出。
许多开源的第三方的log包也标榜自己多快多好(没错说的就是你zap),但实际上对我来说并不能即插即用,因为结构化输出居多,必须要经过详实的了解和配置才能组合出自己需要的格式。可能这些log包适合直接集成logstash或者到kafka,毕竟直接输出的都是json格式的模块化日志,但是未经可视化处理前对于人眼并不友好。
因此这里自己写一个简单的log包,适用于日常工具类场景。这里的很多参数都写死了,如有自定义lumberkjack参数的需求可以再写其他的new构造函数。lumberkjack是一个用于维护日志文件进行日志切换的包,例如控制文件大小、保存日期、保留个数等等,类似linux上的logrotate功能。
相比于一些成熟的log工具,这里写的这个还很简陋,不过能用就行。
package log import ( "fmt" "gopkg.in/natefinch/lumberjack.v2" "log" "os" "runtime/debug" "sync" ) const ( DEBUG = iota INFO WARN ERROR FATAL ) type Logger struct { *log.Logger mu sync.Mutex logLevel uint8 } func NewFileLogger(fileName string, level uint8) *Logger { logger := new(log.Logger) logger.SetOutput(&lumberjack.Logger{ Filename: fileName, MaxSize: 500, MaxBackups: 30, MaxAge: 7, Compress: true, }) logger.SetFlags(log.LstdFlags | log.Lmicroseconds | log.Lshortfile | log.Lmsgprefix) return &Logger{Logger: logger, logLevel: level} } func NewStreamLogger(level uint8) *Logger { logger := new(log.Logger) logger.SetOutput(os.Stdout) logger.SetFlags(log.LstdFlags | log.Lmicroseconds | log.Lshortfile | log.Lmsgprefix) return &Logger{Logger: logger, logLevel: level} } func (l *Logger) SetLevel(level uint8) { l.logLevel = level } func (l *Logger) GetLevel() uint8 { return l.logLevel } func (l *Logger) Debug(logStr string, v ...interface{}) { l.mu.Lock() defer l.mu.Unlock() if l.logLevel == DEBUG { l.SetPrefix(fmt.Sprintf("[DEBUG]: ")) _ = l.Output(2, fmt.Sprintf(logStr, v...)) } } func (l *Logger) Info(logStr string, v ...interface{}) { l.mu.Lock() defer l.mu.Unlock() if l.logLevel <= INFO { l.SetPrefix("[INFO]: ") _ = l.Output(2, fmt.Sprintf(logStr, v...)) } } func (l *Logger) Warn(logStr string, v ...interface{}) { l.mu.Lock() defer l.mu.Unlock() if l.logLevel <= WARN { l.SetPrefix("[WARN]: ") _ = l.Output(2, fmt.Sprintf(logStr, v...)) } } func (l *Logger) Error(logStr string, v ...interface{}) { l.mu.Lock() defer l.mu.Unlock() if l.logLevel <= ERROR { l.SetPrefix("[ERROR]: ") _ = l.Output(2, fmt.Sprintf(logStr, v...)) } } func (l *Logger) Fatal(logStr string, v ...interface{}) { l.mu.Lock() defer l.mu.Unlock() if l.logLevel <= FATAL { l.SetPrefix("[FATAL]: ") _ = l.Output(2, fmt.Sprintf("%s [stacktrace]: %s", fmt.Sprintf(logStr, v...), string(debug.Stack()))) os.Exit(1) } }
使用:
如果是单个包内使用,直接调用相关New构造函数即可。
如果是在包含多个包的项目内使用,只需要在任意包内定义一个GlobalLogger即可,为方便项目内其他包调用,可以直接放在log package内,这样其他包直接使用log.GlobalLogger就可以将日志原子性的写入同一个日志内了。
logutil.go:
package log // 如有从配置文件中读取参数的需求,也可一并写在这里 var GlobalLogger = NewFileLogger("log/myproject.log", INFO)
测试下:
package test // 任意包内都可测试 import "testing" import "myproject/log" func TestAll(t *testing.T) { log.GlobalLogger.Infof("This is info log, %s", "name=leo") }
输出的log格式如下:
2021/08/17 15:19:55.281819 log_test.go:8 [Info]: This is info log, name=leo
可以看到这个格式稍微好看了那么一丢丢,但是没有熟悉的[]方框还是不美,鉴于加方框需要修改写标准log库的代码,保险起见直接拷贝标准log包改名为baselog,然后把里边Logger的formatHeader方法修改下就可以了。
我们在标准log包的log.go文件里添加如下几行:
if l.flag&Ldate != 0部分添加一行:*buf = append(*buf, '[') if l.flag&(Ltime|Lmicroseconds) != 0部分添加一行:*buf = append(*buf, ']') if l.flag&(Lshortfile|Llongfile) != 0添加两行:*buf = append(*buf, '[') *buf = append(*buf, "] "...) //总之核心目的就是为了加上方框......low是low了点,能用就完事了
[2021/08/17 15:19:55.281819] [log_test.go:8] [Info]: This is info log, name=leo
最后这里推荐下使用PingCAP家的log模块,格式够详细使用够简单,产品也够成熟,常用的使用Demo如下:
package main import ( "github.com/pingcap/log" ) func main() { conf := &log.Config{ Level: "info", File: log.FileLogConfig{ Filename: "main.log", MaxSize: 1024 * 1024 * 1024, MaxDays: 30, MaxBackups: 60}} filelogger, _, _ := log.InitLogger(conf) filelogger.Error("This is a Error Msg!") // 文件中打印日志 log.Error("This is a Error Msg!") // 标准输出中打印日志 }
补充一:
突然意识到setPrefix时需要加锁,不然并发打印日志时可能出现goroutines交叉修改prefix,导致打印出来的prefix有问题的情况。只需要给Logger额外加个mu sync.Mutex然后每个level打印时用mu.Lock/Unlock包裹起来即可,保证两个操作是atomic的。