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

这篇具有很好参考价值的文章主要介绍了Go学习第十八章——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)

Go学习第十八章——Gin日志与第三方工具Logrus,golang,学习,gin,开发语言,笔记,后端

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

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 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,  // 状态码
    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()

}

但是你会发现自己这样输出之后,没有颜色了,不太好看,我们可以输出有颜色的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,
  )
}

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

Go学习第十八章——Gin日志与第三方工具Logrus,golang,学习,gin,开发语言,笔记,后端

第三方日志工具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() {
	// 设置日志输出格式为JSON格式
	log.SetFormatter(&log.JSONFormatter{})

	// 设置日志输出到标准输出
	log.SetOutput(os.Stdout)
    
    // 得到当前的日志级别
    fmt.Println("修改前",log.GetLevel()) 

	// 设置日志级别为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")
}

输出结果:

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

Go学习第十八章——Gin日志与第三方工具Logrus,golang,学习,gin,开发语言,笔记,后端

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{})
       // 设置日志输出位置为MultiWriter
       log.SetOutput(mw)
       // 设置日志级别为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")

       // 关闭日志文件
       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 main

import (
  "bytes"
  "fmt"
  "github.com/sirupsen/logrus"
  "os"
  "path"
)

// 颜色
const (
  red    = 31
  yellow = 33
  blue   = 36
  gray   = 37
)

type LogFormatter struct{}

// Format 实现Formatter(entry *logrus.Entry) ([]byte, error)接口
func (t *LogFormatter) Format(entry *logrus.Entry) ([]byte, error) {
  //根据不同的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)          //设置输出类型
  mLog.SetReportCaller(true)         //开启返回函数名和行号
  mLog.SetFormatter(&LogFormatter{}) //设置自己定义的Formatter
  mLog.SetLevel(logrus.DebugLevel)   //设置最低的Level
  return 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() []Level
    Fire(*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.Create
    logrus.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 string
	var line int
	if 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.File
	logPath  string
	fileDate 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.")
}

输出结果:

Go学习第十八章——Gin日志与第三方工具Logrus,golang,学习,gin,开发语言,笔记,后端

  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.File
	logPath  string
	fileDate 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 = timer
	hook.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 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,它是钩子触发时所执行的行为。
/* 该方法将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

Go学习第十八章——Gin日志与第三方工具Logrus,golang,学习,gin,开发语言,笔记,后端

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) {

	// 设置buffer 缓冲区
	var b *bytes.Buffer
	if 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/

Go学习第十八章——Gin日志与第三方工具Logrus,golang,学习,gin,开发语言,笔记,后端

Over!!!文章来源地址https://www.toymoban.com/news/detail-726877.html

到了这里,关于Go学习第十八章——Gin日志与第三方工具Logrus的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 第十八章 ObjectScript - 使用例程

    可以将例程视为 ObjectScript 程序。例程可以从头开始编写,也可以在编译类时自动生成。 在 ObjectScript 例程中,标签定义以下代码单元之一的起点: Procedures 过程(可选地返回一个值)。过程中定义的变量是该过程私有的,这意味着它们不可用于其他代码。对于函数和子例程

    2024年02月10日
    浏览(44)
  • Go学习第十六章——Gin文件上传与下载

    1.1 入门案例(单文件) 1.2 服务端保存文件的几种方式 SaveUploadedFile SaveUploadedFile函数用于将文件保存到指定的路径下。第一个参数是文件对象,第二个参数是保存文件的路径。 Create+Copy FormFile函数用于获取上传的文件。它返回一个文件对象,其中包含了文件的元数据(名称、

    2024年02月08日
    浏览(40)
  • Go学习第十七章——Gin中间件与路由

    Gin框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数。这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等 即比如,如果访问一个网页的话,不管访问什么路径都需要进行登录,

    2024年02月07日
    浏览(46)
  • 《TCP IP网络编程》第十八章

    线程背景:         第 10 章介绍了多进程服务端的实现方法。多进程模型与 select 和 epoll 相比的确有自身的优点,但同时也有问题。如前所述, 创建(复制)进程的工作本身会给操作系统带来相当沉重的负担。而且,每个进程都具有独立的内存空间,所以进程间通信的实

    2024年02月12日
    浏览(49)
  • Go学习第十五章——Gin参数绑定bind与验证器

    在Gin框架中, bind 用于绑定参数,即将请求参数绑定到结构体中。通过使用 bind ,我们可以方便地将请求参数与结构体字段进行绑定,从而更方便地处理和验证参数。 Gin框架提供了多种绑定方法,包括Query参数绑定、Form参数绑定、JSON参数绑定等。下面分别介绍这些方法的详

    2024年02月07日
    浏览(40)
  • 【新版系统架构】第十八章-安全架构设计理论与实践

    信息系统安全设计重点考虑:系统安全保障体系,信息安全体系架构 系统安全保障体系: 安全区域策略的确定,根据安全区域的划分,主管部门应制定针对性的安全策略 统一配置和管理防病毒系统,主管部门应当建立整体防御策略,以实现统一的配置和管理 网络安全管理,

    2024年02月13日
    浏览(45)
  • Hotspot源码解析-第十八章-元空间的创建与分配

    元空间就是从C堆中划出来的一片完整的区域,为了提升元数据的内存分配效率,又把元空间按若干个chunk内存块管理起来,其中chunk块又分为已使用和空间两种类型,并分别用VirtualSpaceList和ChunkManager来管理,chunk内存块之间以链表的形式关联起来,同时为了满足不同元数据占

    2024年02月01日
    浏览(47)
  • 华为HCIE课堂笔记第十八章 SR技术

    SR可以通过控制器实现集中算路,并根据业务的不同下发不同的路径给到头端设备,头端设备将路径标签通过标签栈的形式压入到报文中,沿途的设备不需要维护路径信息,仅按照标签栈中的栈顶进行报文转发即可。 控制平面:扩展后的IGP协议,通过IGP分发标签 转发平面:基

    2024年02月19日
    浏览(39)
  • Docker第十八章 : 构建您的第一个Java镜像

    第十八章 : 构建您的第一个Java镜像 本章知识点: 介绍构建java镜像的基本步骤,以及如何通过阿里云效和阿里云容器镜像服务,构建您的第一个Java镜像。 导读 Java入门指南教您如何使用Docker创建容器化的Spring Boot应用程序。在本模块中,您将学习如何: 使用Maven克隆并运行

    2024年02月22日
    浏览(52)
  • 第十八章_Redis缓存预热+缓存雪崩+缓存击穿+缓存穿透

    缓存预热 缓存预热就是系统启动前,提前将相关的缓存数据直接加载到缓存系统。避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据。 可以通过@PostConstruct初始化白名单数据 缓存雪崩 发生  redis主机挂了,Redis 全盘崩溃

    2024年02月07日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包