Golang 日志
1. Logger
Logger 用来记录程序运行的中间状态,通常用于排查、定位问题和业务数据的统计
Logger 应该具备的功能:
分级,DEBUG、INFO、WARN、ERROR,方便定位问题、屏蔽低级别日志
日志里需要显示时间以及输出日志的文件名和行号
并发调用,多协程写同一个日志文件
日志分割,通常一天一个日志文件,日志量太大时也可一小时一个日志文件 太久的日志文件需要定期清理
2. Logrus
func InitLogrus(logFile string, logLevel logrus.Level) {
logFile = "log/" + logFile
LogRus = logrus.New()
LogRus.SetLevel(logLevel)
LogRus.SetFormatter(&logrus.TextFormatter{
TimestampFormat: "2006-01-02 15:04:05.000", // 显示ms
}) // 选择文本格式或者JSON格式
fout, err := rotatelogs.New(
logFile+".%Y%m%d%H", //指定日志文件的路径和名称
rotatelogs.WithLinkName(logFile), //为最新的一份日志创建软链接,在windows上就是一个快捷方式(并不是把日志写入了2个文件)
rotatelogs.WithRotationTime(1*time.Hour), //每隔1小时生成一份新的日志文件
rotatelogs.WithMaxAge(7*24*time.Hour), //只留最近7天的日志,或使用WithRotationCount只保留最近的几份日志
)
if err != nil {
panic(err)
}
LogRus.SetOutput(fout) //设置日志文件
LogRus.SetReportCaller(true) //输出是从哪里调起的日志打印,这样日志里会多出func和file
}
3. zap
4. zerolog
5. 标准库
var (
myLogger *log.Logger
)
func init() {
if logOut, err := os.OpenFile("Sakura.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o664); err != nil { //注意是APPEND模式
panic(err)
} else {
myLogger = log.New(logOut, "[Sakura] ", log.Ldate|log.Ltime|log.Lmicroseconds|log.Llongfile) //Llongfile会输出go文件的绝对路径和行号,但是是执行log.Logger.Printf的行号,不是调用Debug()或Info()的行号
}
}
func Debug(format string, v ...any) {
myLogger.Printf("DEBUG "+format, v...) //如果不加...,会把v当成一个slice参数来处理,即只能对应到第一个%占位符
}
func Info(format string, v ...any) {
myLogger.Printf("INFO "+format, v...)
}
func main() {
Debug("%s", "TCP监听失败") //输出到日志文件
Info("%s", "UDP连接失败")
}
缺陷:
日志文件中打印的行号有问题,需求的是调用 Debug 的位置的行号,而不是 Debug() 和 Info() 两个函数的位置
6. 自行实现高性能 Logger
package CustomerLogger
import (
"log"
"os"
"runtime"
"strconv"
"strings"
"time"
)
// 自行实现高性能日志需求:
// 1. 日志要能同时输出到文件和终端
// 2. 日志分级
// 3. 显示调用的位置
var (
// log.Logger 支持并发调用
// 这些负责打印到文件
debugFile *log.Logger
infoFile *log.Logger
warnFile *log.Logger
errorFile *log.Logger
// 这些负责在终端打印
debugStdout *log.Logger
infoStdout *log.Logger
warnStdout *log.Logger
errorStdout *log.Logger
logfile string
)
func InitLogger() {
// 日志命名为当天时间
logFileName := setLogFileName()
if fileOut, err := os.OpenFile(logFileName, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666); err != nil {
panic(err)
} else {
debugFile = log.New(fileOut, "[Debug]", log.LstdFlags) //LstdFlags == log.time + log.data
infoFile = log.New(fileOut, "[Indo]", log.LstdFlags)
warnFile = log.New(fileOut, "[Warn]", log.LstdFlags)
errorFile = log.New(fileOut, "[Error]", log.LstdFlags)
}
debugStdout = log.New(os.Stdout, "[Debug]", log.LstdFlags) //LstdFlags == log.time + log.data
infoStdout = log.New(os.Stdout, "[Info]", log.LstdFlags)
warnStdout = log.New(os.Stdout, "[Warn]", log.LstdFlags)
errorStdout = log.New(os.Stdout, "[Error]", log.LstdFlags)
}
// 设置日志文件
func setLogFileName() string {
// 自定义每小时为日志文件或者每天为日志文件
filename := time.Now().Format("2006_01_02")
filename = "log_" + filename + ".log"
return filename
}
// 获取当前运行代码的文件名和行号,并且拼接
func getFileAndLineNo() string {
_, file, line, _ := runtime.Caller(2) //返回函数名、文件名、行号。runtime.Caller(3)是第3层调用堆栈,getFileAndLineNo()第0层 --> addPrefix()第1层 --> Info()第2层 --> 调用logger.Info()的地方第3层
arr := strings.Split(file, "/")
if len(arr) > 3 {
arr = arr[len(arr)-3:] //不需要完整的绝对路径,只取最末3级路径
}
return strings.Join(arr, "/") + ":" + strconv.Itoa(line) //文件名加行号
}
// 计算
func Debug(format string, v ...any) {
funcname := getFileAndLineNo()
debugFile.Printf(funcname+" "+format, v...)
//debugStdout.Printf(funcname+" "+format, v...)
}
func Info(format string, v ...any) {
funcname := getFileAndLineNo()
infoFile.Printf(funcname+" "+format, v...)
infoStdout.Printf(funcname+" "+format, v...)
}
func Warn(format string, v ...any) {
funcname := getFileAndLineNo()
warnFile.Printf(funcname+" "+format, v...)
warnStdout.Printf(funcname+" "+format, v...)
}
func Error(format string, v ...any) {
funcname := getFileAndLineNo()
errorFile.Printf(funcname+" "+format, v...)
errorStdout.Printf(funcname+" "+format, v...)
}
// 进行性能测试
go test -bench=BenchmarkLogger -v -benchmem
只开启文件写入日志
文件写入和终端打印日志都开启
评论区