自定义日志(推荐) 这里实现了控制台和文件日志
/utils/zlog/zlog.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 package zlogimport ( "fmt" "os" "path/filepath" "runtime" "strconv" "strings" "time" ) type LogType int const ( Console LogType = iota File ConsoleFile ) var mLogType = Consolevar mProjectPath = "" func Init () { _, file, _, _ := runtime.Caller(1 ) dirPath := filepath.Dir(file) dirPath = strings.ReplaceAll(dirPath, "\\" , "/" ) mProjectPath = dirPath + "/" } func SetLogType (mType LogType) { mLogType = mType } func LogDebug (msg string , a ...any) { logRecord(fmt.Sprintf(msg, a...), "DEBUG" , mLogType) } func LogError (msg string , a ...any) { logRecord(fmt.Sprintf(msg, a...), "ERROR" , mLogType) } func logRecord (msg string , level string , logType LogType) { now := time.Now().Format("2006-01-02 15:04:05" ) _, mFilepath, line, _ := runtime.Caller(2 ) if mProjectPath != "" { mFilepath = strings.Replace(mFilepath, mProjectPath, "" , 1 ) } logStr := "[" + now + "]\t" + level + "\t" + mFilepath + ":" + strconv.Itoa(line) + "\n\t" + msg + "\n" switch logType { case Console: writeConsole(logStr) case File: writeFile(logStr, level) case ConsoleFile: writeConsole(logStr) writeFile(logStr, level) } } func logConsoleDebug (msg string ) { logRecord(msg, "DEBUG" , Console) } func logConsoleError (msg string ) { logRecord(msg, "ERROR" , Console) } func writeConsole (logStr string ) { fmt.Print(logStr) } var fileName = "" var saveFile *os.Filefunc writeFile (logStr string , level string ) { ymd := time.Now().Format("2006-01-02" ) err := os.MkdirAll("logs" , os.ModePerm) if err != nil { logConsoleError(err.Error()) return } separator := string (filepath.Separator) tempFileName := "logs" + separator + level + "-" + ymd + ".log" if tempFileName != fileName || saveFile == nil { logConsoleDebug("日志文件路径: " + tempFileName) if saveFile != nil { err := saveFile.Close() if err != nil { logConsoleError(err.Error()) } } saveFile, err = os.OpenFile(tempFileName, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644 ) if err != nil { logConsoleError(err.Error()) return } fileName = tempFileName } _, err = saveFile.WriteString(logStr) if err != nil { logConsoleError(err.Error()) return } return }
调用
1 2 3 4 5 6 7 8 9 zlog.Init() zlog.SetLogType(zlog.Console) zlog.LogDebug("测试日志" ) zlog.LogDebug("insert success, last id:%d\n" , 123 )
和Gin结合 基本封装 当我们使用Gin框架的时候,我们可以简单的封装一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package zlog import ( "fmt" "github.com/gin-gonic/gin" "time" ) func Debug (format string, values ...any) { now := time.Now ().Format ("2006/01/02 15:04:05" ) f := fmt.Sprintf ("[Debug] %s %s\n" , now, format) _, _ = fmt.Fprintf (gin.DefaultWriter , f, values...) } func Error (format string, values ...any) { now := time.Now ().Format ("2006/01/02 15:04:05" ) f := fmt.Sprintf ("[Error] %s %s\n" , now, format) _, _ = fmt.Fprintf (gin.DefaultWriter , f, values...) }
调用
1 zlog.Debug(utils.ObjToJson(list))
打印行号 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package zlogimport ( "fmt" "github.com/gin-gonic/gin" "runtime" "time" ) func mCaller (skip int ) string { pc, file, line, ok := runtime.Caller(skip) if !ok { return "" } funcName := runtime.FuncForPC(pc).Name() return fmt.Sprintf("%s:%d(%s)" , file, line, funcName) } func Info (format string , values ...any) { fileStrAll := mCaller(2 ) now := time.Now().Format("2006-01-02 15:04:05" ) f := fmt.Sprintf("%s INFO %s ↓\n%s\n" , now, fileStrAll, format) _, _ = fmt.Fprintf(gin.DefaultWriter, f, values...) } func Error (format string , values ...any) { fileStrAll := mCaller(2 ) now := time.Now().Format("2006-01-02 15:04:05" ) f := fmt.Sprintf("%s ERROR %s ↓\n%s\n" , now, fileStrAll, format) _, _ = fmt.Fprintf(gin.DefaultWriter, f, values...) }
调用
1 zlog.Info("Hello World" )
打印日志
2024-02-26 21:53:15 INFO D:/Project/go/z-wiki/main.go:33(main.main) ↓ Hello World
这样我们点击日志就直接跳转到对应的代码位置了。
在文件输出日志 Gin框架的请求日志默认在控制台输出,但更多的时候,尤其上线运行时,我们希望将用户的请求日志保存到日志文件中,以便更好的分析与备份。
在Gin框架中,通过gin.DefaultWriter变量可能控制日志的保存方式,gin.DefaultWriter在Gin框架中的定义如下:
1 var DefaultWriter io.Writer = os.Stdout
从上面的定义我们可以看出,gin.DefaultWriter的类型为io.Writer,默认值为os.Stdout,即控制台输出,
因此我们可以通过修改gin.DefaultWriter值来将请求日志保存到日志文件或其他地方(比如数据库)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package mainimport ( "github.com/gin-gonic/gin" "io" "os" ) func main () { gin.DisableConsoleColor() file, _ := os.Create("access.log" ) gin.DefaultWriter = file router := gin.Default() router.GET("/test" , func (c *gin.Context) { c.String(200 , "test" ) }) _ = router.Run(":8080" ) }
运行后上面的程序,会在程序所在目录创建access.log文件,当我们发起Web请求后,请求的日志会保存到access.log文件,而不会在控制台输出。
也可以让请求日志同行保存到文件和在控制台输出:
1 2 file, _ := os.Create("access.log" ) gin.DefaultWriter = io.MultiWriter(file,os.Stdout)
zap(推荐)
需要go1.19版本及以上
安装
设置全局变量 如果您想在不先创建 Logger 实例的情况下写入日志,您可以在 init() 函数中使用 ReplaceGlobals() 方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 package mainimport ( "go.uber.org/zap" ) func init () { zap.ReplaceGlobals(zap.Must(zap.NewDevelopment())) } func main () { zap.L().Info("Hello from Zap!" ) }
此方法将可通过 zap.L() 访问的全局记录器替换为功能性 Logger 实例,以便您只需将 zap 包导入文件即可直接使用它。
不同环境切换 您可以使用环境变量轻松地在开发和生产 Logger 之间切换:
1 2 3 4 logger := zap.Must(zap.NewProduction()) if os.Getenv("APP_ENV" ) == "development" { logger = zap.Must(zap.NewDevelopment()) }
在Gin中
1 2 3 4 5 6 7 func init () { if gin.Mode() == gin.DebugMode { zap.ReplaceGlobals(zap.Must(zap.NewDevelopment())) } else { zap.ReplaceGlobals(zap.Must(zap.NewProduction())) } }
不同模式 示例
1 2 3 4 5 6 7 8 9 10 11 logger := zap.NewExample(zap.AddCaller()) defer logger.Sync()logger.Info("Hello World" , zap.String("name" , "psvmc" )) logger2, _ := zap.NewProduction(zap.AddCaller()) defer logger2.Sync()logger2.Info("Hello World" , zap.String("name" , "psvmc" )) logger3, _ := zap.NewDevelopment(zap.AddCaller()) defer logger3.Sync()logger3.Info("Hello World" , zap.String("name" , "psvmc" ))
打印输出结果:
{“level”:”info”,”msg”:”Hello World”,”name”:”psvmc”} {“level”:”info”,”ts”:1708956240.9281151,”caller”:”z-wiki/main.go:38”,”msg”:”Hello World”,”name”:”psvmc”} 2024-02-26T22:04:00.928+0800 INFO z-wiki/main.go:42 Hello World {“name”: “psvmc”}
log/slog
需要go1.21版本及以上
log/slog是Go 1.21中引入的一个新的结构化日志库,它与标准库的log包兼容,但提供了更多的功能和灵活性。
log/slog定义了一个类型,Logger,用于记录不同级别和格式的日志信息。每个Logger都关联一个Handler,用于处理日志记录。
log/slog还提供了一个默认的Logger,可以通过顶级函数(如Info和Error)来使用,它们会调用相应的Logger方法。
该默认Logger将日志信息写入标准错误,并在每条日志信息前添加日期和时间。
log/slog的日志记录由以下几个部分组成:
时间:日志记录发生的时间,可以是本地时间或UTC时间。
级别:日志记录的严重程度,可以是预定义的四个级别之一(Debug、Info、Warn、Error),也可以是自定义的整数值。
消息:日志记录的主要内容,通常是一个简短的描述性字符串。
属性:日志记录的额外信息,以键值对的形式表示,键是字符串,值可以是任意类型。
基本示例 例如,以下代码:
1 2 3 4 5 6 7 8 9 10 package main import ( "log/slog" "os" ) func main () { slog.Info("helloworld" , "user" , os.Getenv("USER" )) }
会产生以下输出:
1 2024 /02 /26 18 :58 :17 INFO hello-world user=""
其中,2024/02/26 18:58:17是时间,INFO是级别,hello-world是消息,user=""是属性。
log/slog还提供了一些有用的特性,如:
可以自定义日志级别、输出目标、格式器(JSON或文本)、时间戳等。
可以使用字段(Fields)来添加结构化的上下文信息,如键值对。
可以使用处理器(Handler)来处理不同级别或条件的日志信息,如过滤、分割、彩色等。
可以使用条目(Entry)来记录带有字段的日志信息,或者使用WithFields、WithTime、WithError等方法来创建带有字段的条目。
可以使用日志级别函数(如Info、Warn、Error等)来记录不同级别的日志信息,或者使用Log或Print等方法来记录默认级别的日志信息。
标准错误和标准输出 以下是一个使用log/slog的示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package main import ( "log/slog" "os" ) func main () { textHandler := slog.NewTextHandler(os.Stderr, nil ) textLogger := slog.New(textHandler) textLogger.Info("hello-world" , "user" , os.Getenv("USER" )) jsonHandler := slog.NewJSONHandler(os.Stdout, nil ) jsonLogger := slog.New(jsonHandler) jsonLogger.Info("hello-world" , "user" , os.Getenv("USER" )) }
该程序会在标准错误上输出文本格式的日志信息:
1 time=2024 -02 -26 T22:35 :53.533 +08 :00 level=INFO msg=hello-world user=""
然后在标准输出上输出JSON格式的日志信息:
1 {"time" :"2024-02-26T22:35:53.5512541+08:00" ,"level" :"INFO" ,"msg" :"hello-world" ,"user" :"" }
logrus 1 go get github.com/sirupsen/logrus
引用
1 import "github.com/sirupsen/logrus"
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 package main import ( "os" "github.com/sirupsen/logrus" ) func main () { log := logrus.New() log.SetLevel(logrus.DebugLevel) log.SetOutput(os.Stdout) log.SetFormatter(&logrus.JSONFormatter{}) log.WithFields(logrus.Fields{ "key1" : "value1" , "key2" : "value2" , }).Info("This is a structured log" ) entry := log.WithFields(logrus.Fields{ "key3" : "value3" , "key4" : "value4" , }) entry.Warn("This is another structured log" ) log.Debug("This is a debug log" ) log.Info("This is an info log" ) log.Warn("This is a warn log" ) log.Error("This is an error log" ) log.Fatal("This is a fatal log" ) log.Panic("This is a panic log" ) log.Log(logrus.InfoLevel, "This is a log with level" ) log.Print("This is a print log" ) }
IDEA日志高亮
File => Settings => Plugins 搜索 Grep Console安装即可,安装后会根据日志中日志级别显示不同颜色。