Golang log工具
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工具,这里写的这个还很简陋,不过能用就行。
2020-6-29新增:
下述代码我直接新建了一个github repo,这样日常项目里我就可以直接go get使用了~~
详见:https://github.com/realcp1018/tinylog
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]:\n%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.Info("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
补充一:
突然意识到setPrefix时需要加锁,不然并发打印日志时可能出现goroutines交叉修改prefix,导致打印出来的prefix有问题的情况。只需要给Logger额外加个mu sync.Mutex然后每个level打印时用mu.Lock/Unlock包裹起来即可,保证两个操作是atomic的。

浙公网安备 33010602011771号