Go学习第十八章——Gin日志与第三方工具Logrus

Go web框架——Gin日志与第三方工具Logrus

  • Gin日志功能
    • 1 基础介绍
      • 1.1 快速入门
      • 1.2 日志基础使用
      • 1.3 将日志输出到文件
    • 2 定义路由格式
    • 3 修改日志级别
    • 4 修改日志格式
  • 第三方日志工具logrus
    • 1 快速入门
      • 1.1 安装
      • 1.2 使用
    • 2 基本功能使用
      • 2.1 设置日志输出格式
      • 2.2 设置日志输出位置
      • 2.3 设置日志级别
      • 2.4 添加字段到日志信息中
      • 2.5 错误处理
      • 2.6 格式化参数
      • 2.7 显示行号
    • 3 自定义日志格式
    • 4 钩子Hook
      • 4.1 快速入门
      • 4.2 场景案例
    • 5 日志分割
      • 5.1 时间分割日志
        • 自定义Write方法
        • 自定义hook钩子
      • 5.2 按日志等级分割
  • Gin 集成 logrus

Gin日志功能

1 基础介绍

1.1 快速入门

在使用Gin框架的过程中,日志是一个重要的组成部分,它可以记录框架和应用程序的运行情况,帮助开发者排查问题和监控应用程序的性能。Gin框架提供了方便的方法来设置和使用日志。

  1. 默认日志 Gin框架默认使用的是标准库的log包,将日志输出到控制台。可以通过gin.Default()方法来创建一个带有默认中间件的路由引擎。
// 使用Gin.Default自带一个日志中间件
router := gin.Default()// Logger()就是
func Default() *Engine {debugPrintWARNINGDefault()engine := New()engine.Use(Logger(), Recovery())return engine
}

以上代码创建的路由引擎将会使用默认的日志中间件,该中间件会将请求的信息以及处理时间记录到控制台。

  1. 自定义日志输出 如果想要自定义日志的输出方式,可以通过gin.New()方法来创建一个不带默认中间件的路由引擎,并使用gin.Logger()方法设置自定义的日志中间件。
router := gin.New()
router.Use(gin.Logger())

以上代码创建了一个不带默认中间件的路由引擎,并设置了自定义的日志中间件。

不过上面这里,直接又调用了gin自带的日志中间件,后面会讲解如何自定义日志。

1.2 日志基础使用

  1. 使用日志 在实际项目中,可以在处理请求的函数中使用日志记录相关信息。
func handler(c *gin.Context) {log.Println("Handling request...")c.JSON(http.StatusOK, gin.H{"message": "Hello, world!"})
}

以上代码在处理请求的函数中使用了log包的Println函数记录了一条信息。

  1. 日志格式化 Gin框架中提供了方便的方法来格式化日志的输出。可以使用log包的Printf函数来格式化日志信息。
func handler(c *gin.Context) {log.Printf("Handling request: %s", c.Request.URL.Path)c.JSON(http.StatusOK, gin.H{"message": "Hello, world!"})
}

以上代码使用Printf函数格式化了日志输出,打印了请求的URL路径。

1.3 将日志输出到文件

日志文件 除了输出到控制台,还可以将日志输出到文件中。

  1. gin.DefaultWriter = io.MultiWriter(f) 将日志写入文件,但是控制台不显示
  2. 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函数将日志输出到指定的文件。

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)

image-20231028231600355

我们也可以进行修改,自定义这个输出格式:

gin.DebugPrintRouteFunc是Gin框架提供的一个全局变量,用于自定义路由信息的调试输出格式和行为。该变量是一个函数类型,声明如下:

type DebugPrintRouteFunc func(httpMethod, absolutePath, handlerName string, nuHandlers int)

该函数类型接收以下参数:

  • httpMethod:HTTP方法,表示请求使用了哪种HTTP方法(GET、POST、PUT、DELETE等)。
  • absolutePath:请求路径,包括了路由组前缀和被路由匹配的路径。
  • handlerName:处理函数的名称,用于标识该路由绑定的处理函数。
  • nuHandlers:处理函数的数量,即路由绑定的处理函数个数。

用户可以通过定义一个自定义的DebugPrintRouteFunc函数,并将其赋值给gin.DebugPrintRouteFunc变量来定制网站路由信息的输出。

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()
}

输出结果:

[小智] 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.DebugModegin.ReleaseModegin.TestMode方法设置不同的日志级别。

// 设置为DEBUG级别日志
gin.SetMode(gin.DebugMode)// 设置为INFO级别日志
gin.SetMode(gin.ReleaseMode)// 禁用日志
gin.SetMode(gin.TestMode)

我这里选择了一个设置,再次运行,下面的内容就少了很多,特别是设置INFO之后,完美~~

4 修改日志格式

默认的是这样的

[GIN] 2023/10/28 - 23:21:00 | 200 |  0s |  127.0.0.1 | GET  "/"

如果觉得不好看,我们可以自定义

package mainimport ("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,  // 状态码params.ClientIP,  // 客户端ipparams.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()}

但是你会发现自己这样输出之后,没有颜色了,不太好看,我们可以输出有颜色的log

func LoggerWithFormatter(params gin.LogFormatterParams) string {var statusColor, methodColor, resetColor stringstatusColor = 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,)
}

我们进行运行,然后看一下结果~~

image-20231028233512662

第三方日志工具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 mainimport (log "github.com/sirupsen/logrus""os"
)func main() {// 设置日志输出格式为JSON格式log.SetFormatter(&log.JSONFormatter{})// 设置日志输出到标准输出log.SetOutput(os.Stdout)// 得到当前的日志级别fmt.Println("修改前",log.GetLevel()) // 设置日志级别为debuglog.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")
}

输出结果:

{"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"}

在这个例子中,我们首先设置了日志输出的格式为JSON格式,然后将日志输出到标准输出。接着,我们设置了日志级别为debug,这意味着只有debug级别及以上的日志信息才会被输出。

最后,我们分别输出了debug、info、warning和error级别的日志信息。可以在控制台中看到对应级别的日志信息以及它们的格式。

2 基本功能使用

2.1 设置日志输出格式

日志级别一般是和系统环境挂钩,例如开发环境,肯定就要显示debug信息,测试环境也是需要的

线上环境就不需要这些日志,可能只显示warnning的日志

Logrus支持多种日志输出格式,如JSON、文本(默认)、自定义格式等。可以使用logrus提供的SetFormatter方法来设置日志输出格式。以下是一些常用的日志输出格式:

  • 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("你好")
}

输出结果:

{"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] 你好

image-20231029102407564

2.2 设置日志输出位置

Logrus可以将日志输出到标准输出、文件、网络等多个位置。默认情况下,日志输出到标准输出。

以下是一些常用的日志输出位置:

  • 标准输出:
log.SetOutput(os.Stdout)
  • 文件:
  1. 输出在文件,并且控制台不输出
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("你好")
}
  1. 输出在文件,并且控制台也输出
func main() {// 创建一个文件,用于写入日志file, err := os.OpenFile("logfile.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666)if err == nil {// 设置日志输出到文件和标准输出mw := io.MultiWriter(file, os.Stdout)// 设置日志输出格式为文本格式log.SetFormatter(&log.TextFormatter{})// 设置日志输出位置为MultiWriterlog.SetOutput(mw)// 设置日志级别为debuglog.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")// 关闭日志文件file.Close()} else {fmt.Println("Failed to log to file, using default stderr")}
}

在这个例子中,我们首先创建了一个文件,用于写入日志信息。然后,我们创建了一个MultiWriter,并将文件和标准输出作为参数传入MultiWriter的构造函数中。

接着,我们设置了日志的输出格式为TextFormatter,并将输出位置设置为MultiWriter。最后,我们设置了日志的级别为debug,并输出不同级别的日志信息。

通过使用MultiWriter,日志信息将同时输出到文件和控制台,控制台会显示日志信息,而文件中也会写入相同的日志内容。

  • 网络:
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 设置日志级别

Logrus支持多个日志级别,包括debug、info、warning、error、fatal和panic。可以使用SetLevel方法来设置日志级别:

log.SetLevel(log.DebugLevel)

可以根据项目需要设置不同的日志级别。当设置为不同的日志级别时,只有大于等于该级别的日志信息才会被输出。

2.4 添加字段到日志信息中

Logrus提供了多种方法来添加自定义字段到日志信息中。可以使用WithFieldWithFields方法来添加字段。

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("你好")
}

输出结果:

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可以记录和处理错误信息。可以使用WithError方法来添加错误信息到日志中。

err := someFunc()
if err != nil {log.WithError(err).Error("Error occurred")
}

2.6 格式化参数

Logrus支持使用格式化字符串和参数。

log.Infof("The answer is %d", 42)

2.7 显示行号

没有行号,无法定位具体的日志位置

logrus.SetReportCaller(true)

输出结果:看后面多了一个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默认的样式我不太喜欢,没有颜色输出

需要实现Formatter(entry *logrus.Entry) ([]byte, error) 接口

package mainimport ("bytes""fmt""github.com/sirupsen/logrus""os""path"
)// 颜色
const (red    = 31yellow = 33blue   = 36gray   = 37
)type LogFormatter struct{}// Format 实现Formatter(entry *logrus.Entry) ([]byte, error)接口
func (t *LogFormatter) Format(entry *logrus.Entry) ([]byte, error) {//根据不同的level去展示颜色var levelColor intswitch entry.Level {case logrus.DebugLevel, logrus.TraceLevel:levelColor = graycase logrus.WarnLevel:levelColor = yellowcase logrus.ErrorLevel, logrus.FatalLevel, logrus.PanicLevel:levelColor = reddefault:levelColor = blue}var b *bytes.Bufferif 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.FunctionfileVal := 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.Loggerfunc init() {log = NewLog()
}func NewLog() *logrus.Logger {mLog := logrus.New()               //新建一个实例mLog.SetOutput(os.Stdout)          //设置输出类型mLog.SetReportCaller(true)         //开启返回函数名和行号mLog.SetFormatter(&LogFormatter{}) //设置自己定义的FormattermLog.SetLevel(logrus.DebugLevel)   //设置最低的Levelreturn mLog
}
func main() {log.Errorln("你好")log.Infof("你好")log.Warnln("你好")log.Debugf("你好")
}

输出结果:

[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可以实现各种扩展功能。

type Hook interface {Levels() []LevelFire(*Entry) error
}

logrus支持钩子函数,使得在特定的条件下自动触发操作。可以使用AddHook方法来添加钩子函数。

log.AddHook(&MyHook{})

完整代码:

// 这个 CustomHook 名字是可以改动的
type CustomHook struct {
}// 设置一个field
func (hook *CustomHook) Fire(entry *logrus.Entry) error {// 在日志输出之前执行自定义操作,比如发送到消息队列、保存到数据库等// 这里只是一个示例,实际操作可以根据需求进行自定义entry.Data["custom_field"] = "custom_value"return nil
}// 哪些等级的日志才会生效
func (hook *CustomHook) Levels() []logrus.Level {// 指定要被触发的日志级别,这里设置为所有级别return logrus.AllLevels
}func main() {// 日志的打开格式是追加,所以不能用os.Createlogrus.SetFormatter(&logrus.TextFormatter{ForceColors: true, TimestampFormat: "2006-01-02 15:04:05", FullTimestamp: true})logrus.AddHook(&CustomHook{})logrus.Errorf("hello")
}

输出结果:

ERRO[2023-10-29 10:49:33] hello                                         custom_field=custom_value

4.2 场景案例

logrus hook 是一个值得深入学习的设计,你可以轻易适用hook来实现多文件写入。

比如,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允许我们自定义写日志的方法,可以通过实现io.Writer接口来自定义写日志的行为。这是在网上找的案例,简单看看它是咋实现的,后面有这段代码的实现详细解析。

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 stringvar line intif entry.Caller != nil {// 获取调用者的文件名和行号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.FilelogPath  stringfileDate string // 判断日期切换目录appName  string
}// Write 将日志内容写入文件
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")// 创建目录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.")
}

输出结果:

image-20231029111740058

  1. 首先定义了一个 LogFormatter 结构体,用于自定义日志的格式。

    它实现了 Format 方法,接收一个 log.Entry 参数,该结构体包含了日志的级别、时间戳、调用者的文件名和行号,以及日志信息。在 Format 方法中,通过 time.Now().Local().Format("2006-01-02 15:04:05") 获取当前时间,并使用 filepath.Base(entry.Caller.File) 获取调用者的文件名,entry.Caller.Line 获取行号。最后使用 fmt.Sprintf 将这些信息格式化为特定的字符串。

  2. 然后定义了一个 logFileWriter 结构体,实现了 io.Writer 接口的 Write 方法。

    该方法用于将日志写入文件。在 Write 方法中,首先判断是否需要切换日期。如果日期发生变化,需要关闭之前的日志文件,并创建新的日期目录和文件。然后将日志写入文件,并返回写入的字节数和错误。

  3. 最后定义了一个 InitLog 函数,用于初始化日志。该函数接收两个参数:日志路径 logPath 和应用名称 appName

    在函数内部,首先根据当前日期创建日志目录。然后根据日志路径、应用名称和当前日期创建日志文件。接着创建一个 logFileWriter 实例,并将其作为输出写入器。同时,还将标准输出作为另一个写入器。然后使用 io.MultiWriter 将这两个写入器组合起来,实现同时将日志写入文件和标准输出。最后,使用 log.SetOutput 将写入器设置为日志输出,并设置 log.SetReportCaller(true) 启用调用者报告功能。最后,将 LogFormatter 设置为日志的格式化器。

这样,通过调用 InitLog 函数可以初始化日志记录器,并将日志输出到文件和标准输出。日志的格式可以通过修改 LogFormatter 结构体的 Format 方法来自定义。

自定义hook钩子

自定义 hook: logrus还允许我们自定义hook函数,在日志输出前或输出后执行额外的操作。

import ("fmt""github.com/sirupsen/logrus""os""time"
)// 定义了一个类型FileDateHook,它包含了需要的字段,如日志文件的文件指针、日志保存路径、当前的日期和应用名称。
type FileDateHook struct {file     *os.FilelogPath  stringfileDate string //判断日期切换目录appName  string
}// 定义了FileDateHook类型的方法Levels。
// 该方法返回了一个包含所有日志级别的切片,表示该钩子对所有日志级别都生效。
func (hook FileDateHook) Levels() []logrus.Level {return logrus.AllLevels
}/* 定义了FileDateHook类型的方法Fire,它是钩子触发时所执行的行为。该方法首先获取当前时间,并格式化为"2006-01-02_15-04"的字符串。然后通过entry.String()方法获取日志的字符串表示。接着判断当前日期与上一次的日期是否相同,如果相同,则向日志文件写入日志内容;如果不同,关闭上一个日志文件,创建新的目录并打开一个新的日志文件,并将当前日期和日志内容写入该文件。*/
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 = timerhook.file.Write([]byte(line))return nil
}/* 定义了InitFile函数,用于初始化日志文件配置。首先获取当前日期并创建对应的目录。然后构建日志文件的路径,创		建并打开日志文件,并将文件指针和其他参数传递给FileDateHook,最后通过logrus的AddHook方法将该钩子添加到日	志记录器中。*/
func InitFile(logPath, appName string) {fileDate := time.Now().Format("2006-01-02_15-04")//创建目录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函数中,首先调用InitFile函数初始化日志文件配置。然后进入一个无限循环,每隔20秒记录一条带有"error"级别的日志,并记录一条带有"warning"级别的日志。

这段代码实现了一个简单的日志记录功能,根据日期创建目录和文件,并将不同日期的日志分别保存到对应的文件中。通过FileDateHook和logrus库的配合使用,可以灵活地对日志进行处理和管理。

5.2 按日志等级分割

package mainimport ("fmt""github.com/sirupsen/logrus""os"
)
// 定义了4个常量,分别用于表示所有日志、错误日志、警告日志和信息日志。
const (allLog  = "all"errLog  = "err"warnLog = "warn"infoLog = "info"
)// 定义了一个类型FileLevelHook,它包含了需要的字段,
// 如所有日志文件、错误日志文件、警告日志文件、信息日志文件和保存日志的路径。
type FileLevelHook struct {file     *os.FileerrFile  *os.FilewarnFile *os.FileinfoFile *os.FilelogPath  string
}// 定义了FileLevelHook类型的方法Levels。
// 该方法返回一个包含所有日志级别的切片,表示该钩子对所有日志级别都生效。
func (hook FileLevelHook) Levels() []logrus.Level {return logrus.AllLevels
}// 定义了FileLevelHook类型的方法Fire,它是钩子触发时所执行的行为。
/* 该方法将entry转换为字符串,然后根据entry的级别分别向错误日志文件、警告日志文件和信息日志文件中写入日志内容。另外,该方法还向所有日志文件中写入日志内容。*/
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函数,用于初始化日志文件配置。
/* 该函数首先创建保存日志的目录,然后打开所有日志文件、错误日志文件、警告日志文件和信息日志文件,并将它们添加到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函数中,首先调用InitLevel函数初始化日志文件配置。然后使用logrus记录了一些不同级别的日志,这些日志将根据级别分别保存到不同的文件中。

这段代码实现了一个可以将不同级别的日志分别保存到不同文件的日志记录器,可以根据实际需求对不同级别的日志进行细分管理。

Gin 集成 logrus

视频学习:Gin 集成 logrus

先创建两个文件夹,log,middleware,并且两个文件,log.go,log_middleware.go

image-20231029113609743

log_middleware.go文件的代码:

package middlewareimport ("fmt""github.com/gin-gonic/gin""github.com/sirupsen/logrus""time"
)const (status200 = 42status404 = 43status500 = 41methodGET = 44
)func LogMiddleware() gin.HandlerFunc {return func(c *gin.Context) {start := time.Now()path := c.Request.URL.Pathraw := c.Request.URL.RawQuery// Process requestc.Next()// Log only when path is not being skipped// Stop timerend := time.Now()timeSub := end.Sub(start)clientIP := c.ClientIP()method := c.Request.MethodstatusCode := c.Writer.Status()//bodySize := c.Writer.Size()if raw != "" {path = path + "?" + raw}var statusColor stringswitch 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 stringswitch 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 logimport ("bytes""fmt""github.com/sirupsen/logrus""os""time"
)type FileDateHook struct {file     *os.FilelogPath  stringfileDate 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 = timerhook.file.Write([]byte(line))return nil
}type MyFormatter struct {
}func (f MyFormatter) Format(entry *logrus.Entry) ([]byte, error) {// 设置buffer 缓冲区var b *bytes.Bufferif entry.Buffer == nil {b = &bytes.Buffer{}} else {b = entry.Buffer}// 设置格式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")//创建目录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/

image-20231029113919780

Over!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/173100.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【C++初阶(三)】引用内联函数auto关键字

目录 前言 1. 引用 1.1 引用的概念 1.2 引用的特性 1.3 引用的权限 1.4 引用的使用 1.5 引用与指针的区别 2. 内联函数 2.1 什么是内联函数 2.2 内联函数的特性 3. auto关键字 3.1 auto简介 3.2 auto使用规则 3.3 auto不能使用的场景 4. 基于范围的for循环 4.1 范围for…

震惊!原来BUG是这么理解的!什么是BUG?软件错误(BUG)的概念

较为官方的概念: 当且仅当规格说明是存在的并且正确,程序与规格说明之间的 不匹配才是错误。 当需求规格说明书没有提到的功能,判断标准以最终用户为准:当程序没有实现其最终用户合理预期的 功能要求时,就是软…

【Python · PyTorch】线性代数 微积分

本文采用Python及PyTorch版本如下: Python:3.9.0 PyTorch:2.0.1cpu 本文为博主自用知识点提纲,无过于具体介绍,详细内容请参考其他文章。 线性代数 & 微积分 1. 线性代数1.1 基础1.1.1 标量1.1.2 向量长度&…

JEnv使用初体验

Java多版本控制器初体验 1、前言 由于公司项目使用jdk8版本,而日常学习会使用其他版本例如jdk17等,往常都是修改环境配置目录实现。 2、下载资料 链接:https://pan.baidu.com/s/1UqzHv8K8WBu-75Ysyc_h3A 提取码:ra6a 3、安装 …

目前和未来的缓存构建

说起来可能有点反直觉,有时候不运行反而可以帮助我们加快速度,这正是网络浏览器运行的指导原则。不必在页面上加载所有内容,缓存的元素已经存在,不需要每次访问网站或网页时都重新加载。页面加载速度越快,浏览器的工作…

汇编学习(1)

汇编、CPU架构、指令集、硬编码之间的关系 ● 汇编语言:这是一种低级语言,用于与硬件直接交互。它是由人类可读的机器码或指令组成的,这些指令告诉CPU如何执行特定的任务。每条汇编指令都有一个对应的机器码指令,CPU可以理解和执…

Ps:对象选择工具

对象选择工具 Object Selection Tool是 Photoshop 2020 版以后新增的选区工具,可用于自动选择图像中的对象或区域,如人物、汽车、宠物、天空、水、建筑物和山脉等。 快捷键:W 让对象选择工具自动检测并选择图像内的对象或区域,或者…

CTF-php特性绕过

注意&#xff1a;null0 正确 nullflase 错误 Extract变量覆盖 <?php$flagxxx; extract($_GET);if(isset($shiyan)){ $contenttrim(file_get_contents($flag));//trim移除引号if($shiyan$content){ echoctf{xxx}; }else{ echoOh.no;} }?> extract() 函数从数组中将…

真实感渲染的非正式调研与近期热门研究分享

真实感渲染的非正式调研与近期热门研究分享 1 期刊1 Top2 Venues 2 Rendering Reserach1 Material2 BRDF3 Appearance Modeling4 Capture5 Light Transport光线传播6 Differetiable Rendring-可微渲染7 Ray Tracing8 Denoising降噪9 NeRF 3 VR/AR4 Non-Photorealistic Renderin…

matlab simulink PMSM永磁电机DTC控制

1、内容简介 略 10-可以交流、咨询、答疑 2、内容说明 PMSM永磁电机DTC控制 PMSM、永磁电机、DTC控制 传 是 &#xff0c;它的工作原理是&#xff1a;首先设定好运行期望值和滞环的容差值&#xff0c;然后把电机 到的差值 号输出&#xff0c;可以 示需要减小 大输出…

10000字!图解机器学习特征工程

文章目录 引言特征工程1.特征类型1.1 结构化 vs 非结构化数据1.2 定量 vs 定性数据 2.数据清洗2.1 数据对齐2.2 缺失值处理 原文链接&#xff1a;https://www.showmeai.tech/article-detail/208 作者&#xff1a;showmeAI 引言 上图为大家熟悉的机器学习建模流程图&#xff0c;…

计算机中了locked勒索病毒怎么办,locked勒索病毒解密,数据恢复

当下网络技术飞速发展&#xff0c;但同样带来的网络安全威胁也不断增加&#xff0c;其中较为明显的威胁就是locked勒索病毒&#xff0c;自从今年以来&#xff0c;很多企业的计算机都遭受到了locked勒索病毒攻击&#xff0c;导致企业的计算机系统瘫痪。通过云天数据恢复工程师对…

【解决方案】ubuntu 解决办法 ImportError: cannot import name ‘_gi‘ from ‘gi‘

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;公众号&#x1f448;&#xff1a;测试开发自动化【获取源码商业合作】 &#x1f449;荣__誉&#x1f448;&#xff1a;阿里云博客专家博主、5…

37回溯算法-理论基础

目录 什么是回溯算法 基本思想 问题场景 回溯算法的理解 回溯算法模板 LeetCode之路——257. 二叉树的所有路径 分析 什么是回溯算法 回溯算法是一种解决组合优化问题、搜索问题以及决策问题的算法。它通常用于尝试在一组可能的解决方案中搜索并找到满足特定条件的解。…

H5游戏源码分享-色块选择游戏

H5游戏源码分享-色块选择游戏 玩到后面色块越来越小&#xff0c;越来越难找出 <!DOCTYPE html><html><head><meta http-equiv"Content-Type" content"text/html; charsetUTF-8"><meta charset"UTF-8"><meta na…

【LeetCode每日一题合集】2023.10.23-2023.10.29(简单的一周)

文章目录 2678. 老人的数目&#xff08;简单遍历模拟&#xff09;1155. 掷骰子等于目标和的方法数&#xff08;动态规划&#xff09;2698. 求一个整数的惩罚数&#xff08;预处理dfs回溯&#xff09;2520. 统计能整除数字的位数&#xff08;简单模拟&#xff09;1465. 切割后面…

C++系列之list的模拟实现

&#x1f497; &#x1f497; 博客:小怡同学 &#x1f497; &#x1f497; 个人简介:编程小萌新 &#x1f497; &#x1f497; 如果博客对大家有用的话&#xff0c;请点赞关注再收藏 &#x1f31e; list的节点类 template struct list_Node { public: list_Node* _prev; list_…

67 内网安全-域横向smbwmi明文或hash传递

#知识点1: windows2012以上版本默认关闭wdigest&#xff0c;攻击者无法从内存中获取明文密码windows2012以下版本如安装KB2871997补丁&#xff0c;同样也会导致无法获取明文密码针对以上情况&#xff0c;我们提供了4种方式解决此类问题 1.利用哈希hash传递(pth&#xff0c;ptk等…

CCF CSP认证历年题目自练 Day39

题目 试题编号&#xff1a; 201312-5 试题名称&#xff1a; I’m stuck! 时间限制&#xff1a; 1.0s 内存限制&#xff1a; 256.0MB 问题描述&#xff1a; 问题描述   给定一个R行C列的地图&#xff0c;地图的每一个方格可能是’#’, ‘’, ‘-’, ‘|’, ‘.’, ‘S’, ‘…

C/C++跨平台构建工具CMake-----灵活添加库并实现开发和生产环境的分离

目录 1.概述2.创建项目3 配置运行项目3.1 编写开平方根示例代码3.2 编写CMake构建脚本 4.使用子模块实现求平方根的功能4.1 在子模块中实现两种求平方根的方法4.2 构建Mathfunctions子模块4.3 在根目录引用子模块的功能4.3.1 编写构建脚本4.3.2 编写C代码使用MathFunctions库中…