Gin日志功能
1 基礎(chǔ)介紹
1.1 快速入門
在使用Gin框架的過程中,日志是一個重要的組成部分,它可以記錄框架和應(yīng)用程序的運行情況,幫助開發(fā)者排查問題和監(jiān)控應(yīng)用程序的性能。Gin框架提供了方便的方法來設(shè)置和使用日志。
- 默認日志 Gin框架默認使用的是標準庫的log包,將日志輸出到控制臺??梢酝ㄟ^gin.Default()方法來創(chuàng)建一個帶有默認中間件的路由引擎。
// 使用Gin.Default自帶一個日志中間件
router := gin.Default()
// Logger()就是
func Default() *Engine {
debugPrintWARNINGDefault()
engine := New()
engine.Use(Logger(), Recovery())
return engine
}
以上代碼創(chuàng)建的路由引擎將會使用默認的日志中間件,該中間件會將請求的信息以及處理時間記錄到控制臺。
- 自定義日志輸出 如果想要自定義日志的輸出方式,可以通過gin.New()方法來創(chuàng)建一個不帶默認中間件的路由引擎,并使用gin.Logger()方法設(shè)置自定義的日志中間件。
router := gin.New()
router.Use(gin.Logger())
以上代碼創(chuàng)建了一個不帶默認中間件的路由引擎,并設(shè)置了自定義的日志中間件。
不過上面這里,直接又調(diào)用了gin自帶的日志中間件,后面會講解如何自定義日志。
1.2 日志基礎(chǔ)使用
- 使用日志 在實際項目中,可以在處理請求的函數(shù)中使用日志記錄相關(guān)信息。
func handler(c *gin.Context) {
log.Println("Handling request...")
c.JSON(http.StatusOK, gin.H{"message": "Hello, world!"})
}
以上代碼在處理請求的函數(shù)中使用了log包的Println函數(shù)記錄了一條信息。
- 日志格式化 Gin框架中提供了方便的方法來格式化日志的輸出??梢允褂胠og包的Printf函數(shù)來格式化日志信息。
func handler(c *gin.Context) {
log.Printf("Handling request: %s", c.Request.URL.Path)
c.JSON(http.StatusOK, gin.H{"message": "Hello, world!"})
}
以上代碼使用Printf函數(shù)格式化了日志輸出,打印了請求的URL路徑。
1.3 將日志輸出到文件
日志文件 除了輸出到控制臺,還可以將日志輸出到文件中。
-
gin.DefaultWriter = io.MultiWriter(f)
將日志寫入文件,但是控制臺不顯示 -
gin.DefaultWriter = io.MultiWriter(f, os.Stdout)
同時將日志寫入文件和控制臺
func main() {
// 輸出到文件
f, _ := os.Create("gin.log")
//gin.DefaultWriter = io.MultiWriter(f)
// 如果需要同時將日志寫入文件和控制臺,請使用以下代碼。
gin.DefaultWriter = io.MultiWriter(f, os.Stdout)
router := gin.Default()
router.GET("/", func(c *gin.Context) {
c.JSON(200, gin.H{"msg": "/"})
})
router.Run()
}
以上代碼將日志輸出到名為"logfile.log"的文件中。
還有一種方式,可以做一個中間件,用來將日志寫入文件,并且控制臺也顯示:
使用log包的SetOutput函數(shù)將日志輸出到指定的文件。
func main() {
router := gin.Default()
router.GET("/", handler)
router.Run(":8000")
}
func handler(c *gin.Context) {
file, _ := os.OpenFile("logfile.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
log.SetOutput(file)
log.Printf("Handling request: %s", c.Request.URL.Path)
c.JSON(http.StatusOK, gin.H{"message": "Hello, world!"})
}
2 定義路由格式
啟動gin,它會顯示所有的路由,默認格式如下
[GIN-debug] GET / --> main.main.func1 (3 handlers)
[GIN-debug] GET /hello --> main.main.func2 (3 handlers)
我們也可以進行修改,自定義這個輸出格式:
gin.DebugPrintRouteFunc
是Gin框架提供的一個全局變量,用于自定義路由信息的調(diào)試輸出格式和行為。該變量是一個函數(shù)類型,聲明如下:
type DebugPrintRouteFunc func(httpMethod, absolutePath, handlerName string, nuHandlers int)
該函數(shù)類型接收以下參數(shù):
-
httpMethod
:HTTP方法,表示請求使用了哪種HTTP方法(GET、POST、PUT、DELETE等)。 -
absolutePath
:請求路徑,包括了路由組前綴和被路由匹配的路徑。 -
handlerName
:處理函數(shù)的名稱,用于標識該路由綁定的處理函數(shù)。 -
nuHandlers
:處理函數(shù)的數(shù)量,即路由綁定的處理函數(shù)個數(shù)。
用戶可以通過定義一個自定義的DebugPrintRouteFunc
函數(shù),并將其賦值給gin.DebugPrintRouteFunc
變量來定制網(wǎng)站路由信息的輸出。
func main() {
gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) {
fmt.Printf("[小智] %v - url:%v --> handlerName:%v (%v handlers)\n", httpMethod, absolutePath, handlerName, nuHandlers)
}
router := gin.Default()
router.GET("/", func(c *gin.Context) {
c.JSON(200, gin.H{"msg": "/"})
})
router.GET("/hello", func(c *gin.Context) {
c.JSON(200, gin.H{"msg": "/"})
})
router.Run()
}
輸出結(jié)果:
[小智] GET - url:/ --> handlerName:main.main.func2 (3 handlers)
[小智] GET - url:/hello --> handlerName:main.main.func3 (3 handlers)
3 修改日志級別
Gin框架提供了四種日志級別:
-
gin.DebugMode
:啟用DEBUG級別日志,顯示所有日志信息。 -
gin.ReleaseMode
:啟用INFO級別日志,僅顯示INFO、WARN和ERROR級別的日志信息。 -
gin.TestMode
:禁用日志,不顯示任何日志信息。
可以通過gin.DebugMode
、gin.ReleaseMode
和gin.TestMode
方法設(shè)置不同的日志級別。
// 設(shè)置為DEBUG級別日志
gin.SetMode(gin.DebugMode)
// 設(shè)置為INFO級別日志
gin.SetMode(gin.ReleaseMode)
// 禁用日志
gin.SetMode(gin.TestMode)
我這里選擇了一個設(shè)置,再次運行,下面的內(nèi)容就少了很多,特別是設(shè)置INFO之后,完美~~
4 修改日志格式
默認的是這樣的
[GIN] 2023/10/28 - 23:21:00 | 200 | 0s | 127.0.0.1 | GET "/"
如果覺得不好看,我們可以自定義
package main
import (
"fmt"
"github.com/gin-gonic/gin"
)
func LoggerWithFormatter(params gin.LogFormatterParams) string {
return fmt.Sprintf(
"[ 小智 ] %s | %d | \t %s | %s | %s \t %s\n",
params.TimeStamp.Format("2006/01/02 - 15:04:05"),
params.StatusCode, // 狀態(tài)碼
params.ClientIP, // 客戶端ip
params.Latency, // 請求耗時
params.Method, // 請求方法
params.Path, // 路徑
)
}
func main() {
router := gin.New()
router.Use(gin.LoggerWithFormatter(LoggerWithFormatter))
router.Run()
}
也可以這樣
func LoggerWithFormatter(params gin.LogFormatterParams) string {
return fmt.Sprintf(
"[ 小智 ] %s | %d | \t %s | %s | %s \t %s\n",
params.TimeStamp.Format("2006/01/02 - 15:04:05"),
params.StatusCode,
params.ClientIP,
params.Latency,
params.Method,
params.Path,
)
}
func main() {
router := gin.New()
router.Use(
gin.LoggerWithConfig(
gin.LoggerConfig{Formatter: LoggerWithFormatter},
),
)
router.Run()
}
但是你會發(fā)現(xiàn)自己這樣輸出之后,沒有顏色了,不太好看,我們可以輸出有顏色的log
func LoggerWithFormatter(params gin.LogFormatterParams) string {
var statusColor, methodColor, resetColor string
statusColor = params.StatusCodeColor()
methodColor = params.MethodColor()
resetColor = params.ResetColor()
return fmt.Sprintf(
"[ 小智 ] %s | %s %d %s | \t %s | %s | %s %-7s %s \t %s\n",
params.TimeStamp.Format("2006/01/02 - 15:04:05"),
statusColor, params.StatusCode, resetColor,
params.ClientIP,
params.Latency,
methodColor, params.Method, resetColor,
params.Path,
)
}
我們進行運行,然后看一下結(jié)果~~
第三方日志工具logrus
1 快速入門
1.1 安裝
可以使用Go的包管理工具go get來安裝Logrus:
go get github.com/sirupsen/logrus
安裝完成后,可以在項目的代碼中引入Logrus的包:
import log "github.com/sirupsen/logrus"
1.2 使用
下面是一個簡單的入門案例,展示了如何使用Logrus進行基本的日志輸出:
package main
import (
log "github.com/sirupsen/logrus"
"os"
)
func main() {
// 設(shè)置日志輸出格式為JSON格式
log.SetFormatter(&log.JSONFormatter{})
// 設(shè)置日志輸出到標準輸出
log.SetOutput(os.Stdout)
// 得到當前的日志級別
fmt.Println("修改前",log.GetLevel())
// 設(shè)置日志級別為debug
log.SetLevel(log.DebugLevel)
// 得到當前的日志級別
fmt.Println("修后",log.GetLevel())
// 輸出不同級別的日志信息
log.Debug("This is a debug message")
log.Info("This is an info message")
log.Warn("This is a warning message")
log.Error("This is an error message")
}
輸出結(jié)果:
{"level":"debug","msg":"This is a debug message","time":"2023-10-29T09:51:48+08:00"}
{"level":"info","msg":"This is an info message","time":"2023-10-29T09:51:48+08:00"}
{"level":"warning","msg":"This is a warning message","time":"2023-10-29T09:51:48+08:00"}
{"level":"error","msg":"This is an error message","time":"2023-10-29T09:51:48+08:00"}
在這個例子中,我們首先設(shè)置了日志輸出的格式為JSON格式,然后將日志輸出到標準輸出。接著,我們設(shè)置了日志級別為debug,這意味著只有debug級別及以上的日志信息才會被輸出。
最后,我們分別輸出了debug、info、warning和error級別的日志信息??梢栽诳刂婆_中看到對應(yīng)級別的日志信息以及它們的格式。
2 基本功能使用
2.1 設(shè)置日志輸出格式
日志級別一般是和系統(tǒng)環(huán)境掛鉤,例如開發(fā)環(huán)境,肯定就要顯示debug信息,測試環(huán)境也是需要的
線上環(huán)境就不需要這些日志,可能只顯示warnning的日志
Logrus支持多種日志輸出格式,如JSON、文本(默認)、自定義格式等。可以使用logrus提供的SetFormatter
方法來設(shè)置日志輸出格式。以下是一些常用的日志輸出格式:
- JSON格式:
log.SetFormatter(&log.JSONFormatter{})
- 文本格式(默認格式):
log.SetFormatter(&log.TextFormatter{})
- 自定義格式:
自定義能夠選擇的類型:
ForceColors:是否強制使用顏色輸出。
DisableColors:是否禁用顏色輸出。
ForceQuote:是否強制引用所有值。
DisableQuote:是否禁用引用所有值。
DisableTimestamp:是否禁用時間戳記錄。
FullTimestamp:是否在連接到 TTY 時輸出完整的時間戳。
TimestampFormat:用于輸出完整時間戳的時間戳格式。
使用方式:
log.SetFormatter(&log.TextFormatter{
DisableColors: true,
FullTimestamp: true,
})
完整代碼:
func main() {
// JSON格式
log.SetFormatter(&log.JSONFormatter{})
log.Errorf("JSON格式")
// TEXT格式
log.SetFormatter(&log.TextFormatter{})
log.Errorf("TEXT格式")
// 自定義格式
log.SetFormatter(&log.TextFormatter{
DisableColors: false,
FullTimestamp: true,
ForceColors: true,
})
log.Errorf("自定義格式")
log.Error("你好")
log.Info("你好")
log.Warnln("你好")
log.Debug("你好")
log.Println("你好")
}
輸出結(jié)果:
{"level":"error","msg":"JSON格式","time":"2023-10-29T10:23:25+08:00"}
time="2023-10-29T10:23:25+08:00" level=error msg="TEXT格式"
ERRO[2023-10-29T10:23:25+08:00] 自定義格式
ERRO[2023-10-29T10:23:25+08:00] 你好
INFO[2023-10-29T10:23:25+08:00] 你好
WARN[2023-10-29T10:23:25+08:00] 你好
INFO[2023-10-29T10:23:25+08:00] 你好
2.2 設(shè)置日志輸出位置
Logrus可以將日志輸出到標準輸出、文件、網(wǎng)絡(luò)等多個位置。默認情況下,日志輸出到標準輸出。
以下是一些常用的日志輸出位置:
- 標準輸出:
log.SetOutput(os.Stdout)
- 文件:
- 輸出在文件,并且控制臺不輸出
func main() {
log.SetFormatter(&log.JSONFormatter{})
file, err := os.OpenFile("logfile.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666)
if err == nil {
log.SetOutput(file)
} else {
log.Info("Failed to log to file, using default stderr")
}
log.Error("你好")
}
- 輸出在文件,并且控制臺也輸出
func main() {
// 創(chuàng)建一個文件,用于寫入日志
file, err := os.OpenFile("logfile.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666)
if err == nil {
// 設(shè)置日志輸出到文件和標準輸出
mw := io.MultiWriter(file, os.Stdout)
// 設(shè)置日志輸出格式為文本格式
log.SetFormatter(&log.TextFormatter{})
// 設(shè)置日志輸出位置為MultiWriter
log.SetOutput(mw)
// 設(shè)置日志級別為debug
log.SetLevel(log.DebugLevel)
// 輸出日志信息
log.Debug("This is a debug message")
log.Info("This is an info message")
log.Warn("This is a warning message")
log.Error("This is an error message")
// 關(guān)閉日志文件
file.Close()
} else {
fmt.Println("Failed to log to file, using default stderr")
}
}
在這個例子中,我們首先創(chuàng)建了一個文件,用于寫入日志信息。然后,我們創(chuàng)建了一個MultiWriter,并將文件和標準輸出作為參數(shù)傳入MultiWriter的構(gòu)造函數(shù)中。
接著,我們設(shè)置了日志的輸出格式為TextFormatter,并將輸出位置設(shè)置為MultiWriter。最后,我們設(shè)置了日志的級別為debug,并輸出不同級別的日志信息。
通過使用MultiWriter,日志信息將同時輸出到文件和控制臺,控制臺會顯示日志信息,而文件中也會寫入相同的日志內(nèi)容。
- 網(wǎng)絡(luò):
conn, err := net.Dial("tcp", "localhost:12345")
if err == nil {
log.SetOutput(conn)
} else {
log.Info("Failed to connect to log server, using default stderr")
}
2.3 設(shè)置日志級別
Logrus支持多個日志級別,包括debug、info、warning、error、fatal和panic。可以使用SetLevel
方法來設(shè)置日志級別:
log.SetLevel(log.DebugLevel)
可以根據(jù)項目需要設(shè)置不同的日志級別。當設(shè)置為不同的日志級別時,只有大于等于該級別的日志信息才會被輸出。
2.4 添加字段到日志信息中
Logrus提供了多種方法來添加自定義字段到日志信息中。可以使用WithField
或WithFields
方法來添加字段。
func main() {
// 給日志添加一個字段
log1 := log.WithField("user1", "alice")
log1.Errorf("你好")
log2 := log.WithFields(log.Fields{
"user2": "alice",
"ip": "127.0.0.1",
})
log2.Errorf("你好")
// 嵌套使用
log3 := log.WithFields(log.Fields{
"user3": "alice",
"ip": "127.0.0.1",
}).WithField("user4", "alice")
log3.Errorf("你好")
}
輸出結(jié)果:
time="2023-10-29T10:16:22+08:00" level=error msg="你好" user1=alice
time="2023-10-29T10:16:22+08:00" level=error msg="你好" ip=127.0.0.1 user2=alice
time="2023-10-29T10:16:22+08:00" level=error msg="你好" ip=127.0.0.1 user3=alice user4=alice
2.5 錯誤處理
Logrus可以記錄和處理錯誤信息??梢允褂?code>WithError方法來添加錯誤信息到日志中。
err := someFunc()
if err != nil {
log.WithError(err).Error("Error occurred")
}
2.6 格式化參數(shù)
Logrus支持使用格式化字符串和參數(shù)。
log.Infof("The answer is %d", 42)
2.7 顯示行號
沒有行號,無法定位具體的日志位置
logrus.SetReportCaller(true)
輸出結(jié)果:看后面多了一個file,并且最后有個29,就是報錯的那一行
time="2023-10-29T10:35:56+08:00" level=debug msg="This is a debug message" func=main.main file="F:/goproject/studyGin/GinStudy03_Logrus/main.go:26"
time="2023-10-29T10:35:56+08:00" level=info msg="This is an info message" func=main.main file="F:/goproject/studyGin/GinStudy03_Logrus/main.go:27"
time="2023-10-29T10:35:56+08:00" level=warning msg="This is a warning message" func=main.main file="F:/goproject/studyGin/GinStudy03_Logrus/main.go
:28"
time="2023-10-29T10:35:56+08:00" level=error msg="This is an error message" func=main.main file="F:/goproject/studyGin/GinStudy03_Logrus/main.go:29
"
3 自定義日志格式
logrus默認的樣式我不太喜歡,沒有顏色輸出
需要實現(xiàn)Formatter(entry *logrus.Entry) ([]byte, error)
接口
package main
import (
"bytes"
"fmt"
"github.com/sirupsen/logrus"
"os"
"path"
)
// 顏色
const (
red = 31
yellow = 33
blue = 36
gray = 37
)
type LogFormatter struct{}
// Format 實現(xiàn)Formatter(entry *logrus.Entry) ([]byte, error)接口
func (t *LogFormatter) Format(entry *logrus.Entry) ([]byte, error) {
//根據(jù)不同的level去展示顏色
var levelColor int
switch entry.Level {
case logrus.DebugLevel, logrus.TraceLevel:
levelColor = gray
case logrus.WarnLevel:
levelColor = yellow
case logrus.ErrorLevel, logrus.FatalLevel, logrus.PanicLevel:
levelColor = red
default:
levelColor = blue
}
var b *bytes.Buffer
if entry.Buffer != nil {
b = entry.Buffer
} else {
b = &bytes.Buffer{}
}
//自定義日期格式
timestamp := entry.Time.Format("2006-01-02 15:04:05")
if entry.HasCaller() {
//自定義文件路徑
funcVal := entry.Caller.Function
fileVal := fmt.Sprintf("%s:%d", path.Base(entry.Caller.File), entry.Caller.Line)
//自定義輸出格式
fmt.Fprintf(b, "[%s] \x1b[%dm[%s]\x1b[0m %s %s %s\n", timestamp, levelColor, entry.Level, fileVal, funcVal, entry.Message)
} else {
fmt.Fprintf(b, "[%s] \x1b[%dm[%s]\x1b[0m %s\n", timestamp, levelColor, entry.Level, entry.Message)
}
return b.Bytes(), nil
}
var log *logrus.Logger
func init() {
log = NewLog()
}
func NewLog() *logrus.Logger {
mLog := logrus.New() //新建一個實例
mLog.SetOutput(os.Stdout) //設(shè)置輸出類型
mLog.SetReportCaller(true) //開啟返回函數(shù)名和行號
mLog.SetFormatter(&LogFormatter{}) //設(shè)置自己定義的Formatter
mLog.SetLevel(logrus.DebugLevel) //設(shè)置最低的Level
return mLog
}
func main() {
log.Errorln("你好")
log.Infof("你好")
log.Warnln("你好")
log.Debugf("你好")
}
輸出結(jié)果:
[2023-10-29 10:37:46] [error] main.go:20 main.main 你好
[2023-10-29 10:37:46] [info] main.go:21 main.main 你好
[2023-10-29 10:37:46] [warning] main.go:22 main.main 你好
[2023-10-29 10:37:46] [debug] main.go:23 main.main 你好
4 鉤子Hook
4.1 快速入門
logrus最令人心動的功能就是其可擴展的HOOK機制了,通過在初始化時為logrus添加hook,logrus可以實現(xiàn)各種擴展功能。
type Hook interface {
Levels() []Level
Fire(*Entry) error
}
logrus支持鉤子函數(shù),使得在特定的條件下自動觸發(fā)操作??梢允褂?code>AddHook方法來添加鉤子函數(shù)。
log.AddHook(&MyHook{})
完整代碼:
// 這個 CustomHook 名字是可以改動的
type CustomHook struct {
}
// 設(shè)置一個field
func (hook *CustomHook) Fire(entry *logrus.Entry) error {
// 在日志輸出之前執(zhí)行自定義操作,比如發(fā)送到消息隊列、保存到數(shù)據(jù)庫等
// 這里只是一個示例,實際操作可以根據(jù)需求進行自定義
entry.Data["custom_field"] = "custom_value"
return nil
}
// 哪些等級的日志才會生效
func (hook *CustomHook) Levels() []logrus.Level {
// 指定要被觸發(fā)的日志級別,這里設(shè)置為所有級別
return logrus.AllLevels
}
func main() {
// 日志的打開格式是追加,所以不能用os.Create
logrus.SetFormatter(&logrus.TextFormatter{ForceColors: true, TimestampFormat: "2006-01-02 15:04:05", FullTimestamp: true})
logrus.AddHook(&CustomHook{})
logrus.Errorf("hello")
}
輸出結(jié)果:
ERRO[2023-10-29 10:49:33] hello custom_field=custom_value
4.2 場景案例
logrus hook 是一個值得深入學(xué)習的設(shè)計,你可以輕易適用hook來實現(xiàn)多文件寫入。
比如,error級別的日志獨立輸出到error.log文件里,其他都放在一起。
type MyHook struct {
Writer *os.File
}
func (hook *MyHook) Fire(entry *logrus.Entry) error {
line, err := entry.String()
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to read entry, %v", err)
return err
}
hook.Writer.Write([]byte(line))
return nil
}
func (hook *MyHook) Levels() []logrus.Level {
return []logrus.Level{logrus.ErrorLevel}
}
func main() {
logrus.SetFormatter(&logrus.TextFormatter{ForceColors: true, TimestampFormat: "2006-01-02 15:04:05", FullTimestamp: true})
logrus.SetReportCaller(true)
file, _ := os.OpenFile("err.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
hook := &MyHook{Writer: file}
logrus.AddHook(hook)
logrus.Errorf("hello")
}
5 日志分割
5.1 時間分割日志
自定義Write方法
自定義Write方法: logrus允許我們自定義寫日志的方法,可以通過實現(xiàn)io.Writer
接口來自定義寫日志的行為。這是在網(wǎng)上找的案例,簡單看看它是咋實現(xiàn)的,后面有這段代碼的實現(xiàn)詳細解析。
import (
"errors"
"fmt"
log "github.com/sirupsen/logrus"
"io"
"os"
"path/filepath"
"strings"
"time"
)
// LogFormatter 日志自定義格式
type LogFormatter struct{}
// Format 格式詳情
func (s *LogFormatter) Format(entry *log.Entry) ([]byte, error) {
// 獲取當前時間
timestamp := time.Now().Local().Format("2006-01-02 15:04:05")
var file string
var line int
if entry.Caller != nil {
// 獲取調(diào)用者的文件名和行號
file = filepath.Base(entry.Caller.File)
line = entry.Caller.Line
}
// 格式化日志信息
msg := fmt.Sprintf("[%s] %s [%s:%d] %s\n", strings.ToUpper(entry.Level.String()), timestamp, file, line, entry.Message)
return []byte(msg), nil
}
// logFileWriter 自定義日志寫入器
type logFileWriter struct {
file *os.File
logPath string
fileDate string // 判斷日期切換目錄
appName string
}
// Write 將日志內(nèi)容寫入文件
func (p *logFileWriter) Write(data []byte) (n int, err error) {
if p == nil {
return 0, errors.New("logFileWriter is nil")
}
if p.file == nil {
return 0, errors.New("file not opened")
}
// 判斷是否需要切換日期
fileDate := time.Now().Format("2006-01-02")
if p.fileDate != fileDate {
p.file.Close()
err = os.MkdirAll(fmt.Sprintf("%s/%s", p.logPath, fileDate), os.ModePerm)
if err != nil {
return 0, err
}
filename := fmt.Sprintf("%s/%s/%s-%s.log", p.logPath, fileDate, p.appName, fileDate)
p.file, err = os.OpenFile(filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600)
if err != nil {
return 0, err
}
}
n, e := p.file.Write(data)
return n, e
}
// 初始化日志配置
func InitLog(logPath string, appName string) {
fileDate := time.Now().Format("20060102")
// 創(chuàng)建目錄
err := os.MkdirAll(fmt.Sprintf("%s/%s", logPath, fileDate), os.ModePerm)
if err != nil {
log.Fatal(err)
}
filename := fmt.Sprintf("%s/%s/%s-%s.log", logPath, fileDate, appName, fileDate)
file, err := os.OpenFile(filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600)
if err != nil {
log.Fatal(err)
}
fileWriter := logFileWriter{file, logPath, fileDate, appName}
log.SetOutput(os.Stdout)
writers := []io.Writer{
&fileWriter,
os.Stdout,
}
// 同時寫入文件和終端
fileAndStdoutWriter := io.MultiWriter(writers...)
log.SetOutput(fileAndStdoutWriter)
log.SetReportCaller(true)
log.SetFormatter(new(LogFormatter))
}
func main() {
InitLog("./logs", "myApp")
log.Println("This is a log message.")
log.Printf("This is another log message. Current time: %s\n", time.Now().Format("15:04:05"))
log.Fatal("This is a fatal error.")
}
輸出結(jié)果:
-
首先定義了一個
LogFormatter
結(jié)構(gòu)體,用于自定義日志的格式。它實現(xiàn)了
Format
方法,接收一個log.Entry
參數(shù),該結(jié)構(gòu)體包含了日志的級別、時間戳、調(diào)用者的文件名和行號,以及日志信息。在Format
方法中,通過time.Now().Local().Format("2006-01-02 15:04:05")
獲取當前時間,并使用filepath.Base(entry.Caller.File)
獲取調(diào)用者的文件名,entry.Caller.Line
獲取行號。最后使用fmt.Sprintf
將這些信息格式化為特定的字符串。 -
然后定義了一個
logFileWriter
結(jié)構(gòu)體,實現(xiàn)了io.Writer
接口的Write
方法。該方法用于將日志寫入文件。在
Write
方法中,首先判斷是否需要切換日期。如果日期發(fā)生變化,需要關(guān)閉之前的日志文件,并創(chuàng)建新的日期目錄和文件。然后將日志寫入文件,并返回寫入的字節(jié)數(shù)和錯誤。 -
最后定義了一個
InitLog
函數(shù),用于初始化日志。該函數(shù)接收兩個參數(shù):日志路徑logPath
和應(yīng)用名稱appName
。在函數(shù)內(nèi)部,首先根據(jù)當前日期創(chuàng)建日志目錄。然后根據(jù)日志路徑、應(yīng)用名稱和當前日期創(chuàng)建日志文件。接著創(chuàng)建一個
logFileWriter
實例,并將其作為輸出寫入器。同時,還將標準輸出作為另一個寫入器。然后使用io.MultiWriter
將這兩個寫入器組合起來,實現(xiàn)同時將日志寫入文件和標準輸出。最后,使用log.SetOutput
將寫入器設(shè)置為日志輸出,并設(shè)置log.SetReportCaller(true)
啟用調(diào)用者報告功能。最后,將LogFormatter
設(shè)置為日志的格式化器。
這樣,通過調(diào)用 InitLog
函數(shù)可以初始化日志記錄器,并將日志輸出到文件和標準輸出。日志的格式可以通過修改 LogFormatter
結(jié)構(gòu)體的 Format
方法來自定義。
自定義hook鉤子
自定義 hook: logrus還允許我們自定義hook函數(shù),在日志輸出前或輸出后執(zhí)行額外的操作。
import (
"fmt"
"github.com/sirupsen/logrus"
"os"
"time"
)
// 定義了一個類型FileDateHook,它包含了需要的字段,如日志文件的文件指針、日志保存路徑、當前的日期和應(yīng)用名稱。
type FileDateHook struct {
file *os.File
logPath string
fileDate string //判斷日期切換目錄
appName string
}
// 定義了FileDateHook類型的方法Levels。
// 該方法返回了一個包含所有日志級別的切片,表示該鉤子對所有日志級別都生效。
func (hook FileDateHook) Levels() []logrus.Level {
return logrus.AllLevels
}
/* 定義了FileDateHook類型的方法Fire,它是鉤子觸發(fā)時所執(zhí)行的行為。該方法首先獲取當前時間,并格式化為"2006-01-02_15-04"的字符串。然后通過entry.String()方法獲取日志的字符串表示。接著判斷當前日期與上一次的日期是否相同,如果相同,則向日志文件寫入日志內(nèi)容;如果不同,關(guān)閉上一個日志文件,創(chuàng)建新的目錄并打開一個新的日志文件,并將當前日期和日志內(nèi)容寫入該文件。*/
func (hook FileDateHook) Fire(entry *logrus.Entry) error {
timer := entry.Time.Format("2006-01-02_15-04")
line, _ := entry.String()
if hook.fileDate == timer {
hook.file.Write([]byte(line))
return nil
}
// 時間不等
hook.file.Close()
os.MkdirAll(fmt.Sprintf("%s/%s", hook.logPath, timer), os.ModePerm)
filename := fmt.Sprintf("%s/%s/%s.log", hook.logPath, timer, hook.appName)
hook.file, _ = os.OpenFile(filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600)
hook.fileDate = timer
hook.file.Write([]byte(line))
return nil
}
/* 定義了InitFile函數(shù),用于初始化日志文件配置。首先獲取當前日期并創(chuàng)建對應(yīng)的目錄。然后構(gòu)建日志文件的路徑,創(chuàng) 建并打開日志文件,并將文件指針和其他參數(shù)傳遞給FileDateHook,最后通過logrus的AddHook方法將該鉤子添加到日 志記錄器中。*/
func InitFile(logPath, appName string) {
fileDate := time.Now().Format("2006-01-02_15-04")
//創(chuàng)建目錄
err := os.MkdirAll(fmt.Sprintf("%s/%s", logPath, fileDate), os.ModePerm)
if err != nil {
logrus.Error(err)
return
}
filename := fmt.Sprintf("%s/%s/%s.log", logPath, fileDate, appName)
file, err := os.OpenFile(filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600)
if err != nil {
logrus.Error(err)
return
}
fileHook := FileDateHook{file, logPath, fileDate, appName}
logrus.AddHook(&fileHook)
}
func main() {
InitFile("logrus_study/log", "XiaoZhi")
for {
logrus.Errorf("error")
time.Sleep(20 * time.Second)
logrus.Warnln("warnning")
}
}
在main函數(shù)中,首先調(diào)用InitFile函數(shù)初始化日志文件配置。然后進入一個無限循環(huán),每隔20秒記錄一條帶有"error"級別的日志,并記錄一條帶有"warning"級別的日志。
這段代碼實現(xiàn)了一個簡單的日志記錄功能,根據(jù)日期創(chuàng)建目錄和文件,并將不同日期的日志分別保存到對應(yīng)的文件中。通過FileDateHook和logrus庫的配合使用,可以靈活地對日志進行處理和管理。
5.2 按日志等級分割
package main
import (
"fmt"
"github.com/sirupsen/logrus"
"os"
)
// 定義了4個常量,分別用于表示所有日志、錯誤日志、警告日志和信息日志。
const (
allLog = "all"
errLog = "err"
warnLog = "warn"
infoLog = "info"
)
// 定義了一個類型FileLevelHook,它包含了需要的字段,
// 如所有日志文件、錯誤日志文件、警告日志文件、信息日志文件和保存日志的路徑。
type FileLevelHook struct {
file *os.File
errFile *os.File
warnFile *os.File
infoFile *os.File
logPath string
}
// 定義了FileLevelHook類型的方法Levels。
// 該方法返回一個包含所有日志級別的切片,表示該鉤子對所有日志級別都生效。
func (hook FileLevelHook) Levels() []logrus.Level {
return logrus.AllLevels
}
// 定義了FileLevelHook類型的方法Fire,它是鉤子觸發(fā)時所執(zhí)行的行為。
/* 該方法將entry轉(zhuǎn)換為字符串,然后根據(jù)entry的級別分別向錯誤日志文件、警告日志文件和信息日志文件中寫入日志內(nèi)容。另外,該方法還向所有日志文件中寫入日志內(nèi)容。*/
func (hook FileLevelHook) Fire(entry *logrus.Entry) error {
line, _ := entry.String()
switch entry.Level {
case logrus.ErrorLevel:
hook.errFile.Write([]byte(line))
case logrus.WarnLevel:
hook.warnFile.Write([]byte(line))
case logrus.InfoLevel:
hook.infoFile.Write([]byte(line))
}
hook.file.Write([]byte(line))
return nil
}
// 定義了InitLevel函數(shù),用于初始化日志文件配置。
/* 該函數(shù)首先創(chuàng)建保存日志的目錄,然后打開所有日志文件、錯誤日志文件、警告日志文件和信息日志文件,并將它們添加到FileLevelHook中。最后通過logrus的AddHook方法將該鉤子添加到日志記錄器中。*/
func InitLevel(logPath string) {
err := os.MkdirAll(fmt.Sprintf("%s", logPath), os.ModePerm)
if err != nil {
logrus.Error(err)
return
}
allFile, err := os.OpenFile(fmt.Sprintf("%s/%s.log", logPath, allLog), os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600)
errFile, err := os.OpenFile(fmt.Sprintf("%s/%s.log", logPath, errLog), os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600)
warnFile, err := os.OpenFile(fmt.Sprintf("%s/%s.log", logPath, warnLog), os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600)
infoFile, err := os.OpenFile(fmt.Sprintf("%s/%s.log", logPath, infoLog), os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600)
fileHook := FileLevelHook{allFile, errFile, warnFile, infoFile, logPath}
logrus.AddHook(&fileHook)
}
func main() {
InitLevel("logrus_study/log_level")
logrus.Errorln("你好")
logrus.Errorln("err")
logrus.Warnln("warn")
logrus.Infof("info")
logrus.Println("print")
}
在main函數(shù)中,首先調(diào)用InitLevel函數(shù)初始化日志文件配置。然后使用logrus記錄了一些不同級別的日志,這些日志將根據(jù)級別分別保存到不同的文件中。
這段代碼實現(xiàn)了一個可以將不同級別的日志分別保存到不同文件的日志記錄器,可以根據(jù)實際需求對不同級別的日志進行細分管理。
Gin 集成 logrus
視頻學(xué)習:Gin 集成 logrus
先創(chuàng)建兩個文件夾,log,middleware,并且兩個文件,log.go,log_middleware.go
log_middleware.go文件的代碼:
package middleware
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
"time"
)
const (
status200 = 42
status404 = 43
status500 = 41
methodGET = 44
)
func LogMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
path := c.Request.URL.Path
raw := c.Request.URL.RawQuery
// Process request
c.Next()
// Log only when path is not being skipped
// Stop timer
end := time.Now()
timeSub := end.Sub(start)
clientIP := c.ClientIP()
method := c.Request.Method
statusCode := c.Writer.Status()
//bodySize := c.Writer.Size()
if raw != "" {
path = path + "?" + raw
}
var statusColor string
switch statusCode {
case 200:
statusColor = fmt.Sprintf("\033[%dm %d \033[0m", status200, statusCode)
case 404:
statusColor = fmt.Sprintf("\033[%dm %d \033[0m", status404, statusCode)
}
var methodColor string
switch method {
case "GET":
methodColor = fmt.Sprintf("\033[%dm %s \033[0m", methodGET, method)
}
logrus.Infof("[GIN] %s |%s| %d | %s | %s | %s",
start.Format("2006-01-02 15:04:06"),
statusColor,
timeSub,
clientIP,
methodColor,
path,
)
}
}
log.go文件的代碼:
package log
import (
"bytes"
"fmt"
"github.com/sirupsen/logrus"
"os"
"time"
)
type FileDateHook struct {
file *os.File
logPath string
fileDate string //判斷日期切換目錄
appName string
}
func (hook FileDateHook) Levels() []logrus.Level {
return logrus.AllLevels
}
func (hook FileDateHook) Fire(entry *logrus.Entry) error {
timer := entry.Time.Format("2006-01-02_15-04")
line, _ := entry.String()
if hook.fileDate == timer {
hook.file.Write([]byte(line))
return nil
}
// 時間不等
hook.file.Close()
os.MkdirAll(fmt.Sprintf("%s/%s", hook.logPath, timer), os.ModePerm)
filename := fmt.Sprintf("%s/%s/%s.log", hook.logPath, timer, hook.appName)
hook.file, _ = os.OpenFile(filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600)
hook.fileDate = timer
hook.file.Write([]byte(line))
return nil
}
type MyFormatter struct {
}
func (f MyFormatter) Format(entry *logrus.Entry) ([]byte, error) {
// 設(shè)置buffer 緩沖區(qū)
var b *bytes.Buffer
if entry.Buffer == nil {
b = &bytes.Buffer{}
} else {
b = entry.Buffer
}
// 設(shè)置格式
fmt.Fprintf(b, "%s\n", entry.Message)
return b.Bytes(), nil
}
func InitFile(logPath, appName string) {
logrus.SetFormatter(&MyFormatter{})
fileDate := time.Now().Format("2006-01-02_15-04")
//創(chuàng)建目錄
err := os.MkdirAll(fmt.Sprintf("%s/%s", logPath, fileDate), os.ModePerm)
if err != nil {
logrus.Error(err)
return
}
filename := fmt.Sprintf("%s/%s/%s.log", logPath, fileDate, appName)
file, err := os.OpenFile(filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600)
if err != nil {
logrus.Error(err)
return
}
fileHook := FileDateHook{file, logPath, fileDate, appName}
logrus.AddHook(&fileHook)
}
最后main代碼:
import (
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
"studyGin/GinStudy03_Logrus/log"
"studyGin/GinStudy03_Logrus/middleware"
)
func main() {
log.InitFile("logrus_study/gin_logrus/logs", "server")
router := gin.New()
router.Use(middleware.LogMiddleware())
router.GET("/", func(c *gin.Context) {
logrus.Info("來了")
c.JSON(200, gin.H{"msg": "你好"})
})
router.Run(":8081")
}
訪問地址:http://127.0.0.1:8081/
文章來源:http://www.zghlxwxcb.cn/news/detail-726877.html
Over?。?!文章來源地址http://www.zghlxwxcb.cn/news/detail-726877.html
到了這里,關(guān)于Go學(xué)習第十八章——Gin日志與第三方工具Logrus的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!