G

[Golang]Gin框架 11.日志

RoLingG 2023-10-15

日志

1.记录用户操作,猜测用户行为

2.记录bug,方便维护维修

Gin框架自带的日志系统

package main

import "github.com/gin-gonic/gin"

func main() {
    router := gin.Default()

    //查看default的内置日志
    router.GET("/index", func(c *gin.Context) {})
    router.POST("/user", func(c *gin.Context) {})
    router.POST("/article", func(c *gin.Context) {})

    router.Run(":80")
}

↑上述代码的gin.Default的内置日志,运行生成出来就是下面这样↓

[GIN-debug] GET    /index                    --> main.main.func1 (3 handlers)
[GIN-debug] POST   /user                     --> main.main.func2 (3 handlers)
[GIN-debug] POST   /article                  --> main.main.func3 (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :80

Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :80

现在输出还在控制台里,我们可以把它改到一个日志文件里,这里就需要用到一个名为gin.DefaultWriter,这是一个IO,它的源码会这样的:

var DefaultWriter io.Writer = os.Stdout

(注意,gin.DefaultWriter要放在gin.Default之前)

package main

import (
    "github.com/gin-gonic/gin"
    "io"
    "os"
)

func main() {
    //将日志生成进一个叫gin.log的文件中
    file, _ := os.Create("gin.log")
    gin.DefaultWriter = io.MultiWriter(file)

    router := gin.Default()

    //查看default的内置日志
    router.GET("/index", func(c *gin.Context) {})
    router.POST("/user", func(c *gin.Context) {})
    router.POST("/article", func(c *gin.Context) {})

    api := router.Group("api")
    api.GET("/test", func(c *gin.Context) {})

    router.Run(":80")
}

执行了上述代码后,就会发现控制台的内置日志没了,转而会发现根目录下生成出了一个gin.log的文件,里面存放着原本在控制台输出的内置日志。(日志会根据运行及时写入进gin.log文件中)

如果想要控制台和gin.log文件中都有日志输出,则可以将gin.DefaultWriter改成:

gin.DefaultWriter = io.MultiWriter(file, os.Stdout)

自定义路由格式

默认的路由格式:其实认真看默认的内置日志会发现我们写的路由都有在里面:

            方法      请求的路径                        对应的匿名函数
[GIN-debug] GET    /index                    --> main.main.func1 (3 handlers)
[GIN-debug] POST   /user                     --> main.main.func2 (3 handlers)
[GIN-debug] POST   /article                  --> main.main.func3 (3 handlers)
[GIN-debug] GET    /api/test                 --> main.main.func4 (3 handlers)

匿名函数里面的内容包括了main包、main函数中、func1函数、处理数量(一般来说默认为2,也就是没有新增自订处理的时候)

自定义路由格式:新加一个gin.DebugPrintRouteFunc

gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) {
    log.Printf("[ RoLingG %s %s %s %s \n]", httpMethod, absolutePath, handlerName, nuHandlers)
}
package main

import (
    "github.com/gin-gonic/gin"
    "log"
)

func main() {
    gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) {
        log.Printf("[ RoLingG ] %s %s %s %s \n", httpMethod, absolutePath, handlerName, nuHandlers)
    }
    router := gin.Default()

    //查看default的内置日志
    router.GET("/index")
    router.POST("/user", func(c *gin.Context) {})
    router.POST("/article", func(c *gin.Context) {})

    api := router.Group("api")
    api.GET("/test", func(c *gin.Context) {})

    router.Run(":80")
}

这样控制台的输出就会是:

//注意:index路径是什么都没写,Gin框架会给他一个默认函数进行日志生成。
2023/10/15 12:47:10 [ RoLingG ] GET /index github.com/gin-gonic/gin.CustomRecoveryWithWriter.func1 %!s(int=2)
2023/10/15 12:47:10 [ RoLingG ] POST /user main.main.func2 %!s(int=3)
2023/10/15 12:47:10 [ RoLingG ] POST /article main.main.func3 %!s(int=3)
2023/10/15 12:47:10 [ RoLingG ] GET /api/test main.main.func4 %!s(int=3)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :80

[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :80

查询路由:router.Route()

package main

import (
    "fmt"
    "github.com/gin-gonic/gin"
)

func main() {
    router := gin.Default()

    //查看default的内置日志
    router.GET("/index")
    router.POST("/user", func(c *gin.Context) {})
    router.POST("/article", func(c *gin.Context) {})

    api := router.Group("api")
    api.GET("/test", func(c *gin.Context) {})

    //查询路由
    fmt.Println(router.Routes())

    router.Run(":80")
}

这样控制台就会将路由打印出来:

[GIN-debug] GET    /index                    --> github.com/gin-gonic/gin.CustomRecoveryWithWriter.func1 (2 handlers)
[GIN-debug] POST   /user                     --> main.main.func1 (3 handlers)
[GIN-debug] POST   /article                  --> main.main.func2 (3 handlers)
[GIN-debug] GET    /api/test                 --> main.main.func3 (3 handlers)

//打印出来的所有路由
[{GET /index github.com/gin-gonic/gin.CustomRecoveryWithWriter.func1 0x7d91c0} {GET /api/test main.main.func3 0x7df2a0} {POST /user main.main.func1 0x7df260} {POST /article main.main.func2 0x7df280}]

[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :80

用for range循环打印方式:

package main

import (
    "fmt"
    "github.com/gin-gonic/gin"
)

func main() {
    router := gin.Default()

    //查看default的内置日志
    router.GET("/index")
    router.POST("/user", func(c *gin.Context) {})
    router.POST("/article", func(c *gin.Context) {})

    api := router.Group("api")
    api.GET("/test", func(c *gin.Context) {})

    ////查询路由
    //fmt.Println(router.Routes())
    for _, info := range router.Routes() {
        fmt.Println(info.Path, info.Method, info.Handler)
    }

    router.Run(":80")
}

这样的方式打印出来在控制台是这样的:

/index GET github.com/gin-gonic/gin.CustomRecoveryWithWriter.func1
/api/test GET main.main.func3
/user POST main.main.func1
/article POST main.main.func2
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :80

如果想让它显示这么多的Gin-Debug,可以用gin.SetMode(gin.ReleaseMode) ,因为默认的环境就是Debug模式。(内置日志里有提示)

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

加了gin.SetMode(gin.ReleaseMode)之后的效果如下:

GOROOT=D:\GoLand\SDK\go1.20.3 #gosetup
GOPATH=D:\GoLand #gosetup
D:\GoLand\SDK\go1.20.3\bin\go.exe build -o C:\Users\RoLingG\AppData\Local\Temp\GoLand\___go_build_Gogin_Gogin_.exe D:\GoLand\Gogin\Gogin\视图\11.日志.go #gosetup
C:\Users\RoLingG\AppData\Local\Temp\GoLand\___go_build_Gogin_Gogin_.exe

有请求才有日志生成。

修改log的显示

一般的log日志:

[GIN] 2023/10/15 - 20:02:08 | 404 |            0s |       127.0.0.1 | GET      "/api/login"
[GIN] 2023/10/15 - 20:02:30 | 404 |            0s |       127.0.0.1 | GET      "/api/index"
[GIN] 2023/10/15 - 20:02:36 | 200 |            0s |       127.0.0.1 | GET      "/index"
[GIN] 2023/10/15 - 20:02:51 | 404 |            0s |       127.0.0.1 | POST     "/users"
[GIN] 2023/10/15 - 20:02:56 | 200 |            0s |       127.0.0.1 | POST     "/user"

一般来说ip得是请求的客户ip,但一般内置的log不能这样显示,这时候就需要自己改,将客户的ip通过请求头的方式传递过来。

自定义log:

package main

import (
    "fmt"
    "github.com/gin-gonic/gin"
)

func main() {
    //New方便自定义log
    router := gin.New()
    router.Use(gin.LoggerWithFormatter(func(params gin.LogFormatterParams) string {
        //自定义log在控制台的输出格式
        return fmt.Sprintf("[RoLingG]  %s  -  %d  -  %s  -  %s  -  %s  \n", params.TimeStamp.Format("2006-01-02 15:04:05"), params.StatusCode, params.ClientIP, params.Method, params.Path)
    }))

    //查看default的内置日志
    router.GET("/index")
    router.POST("/user", func(c *gin.Context) {})
    router.POST("/article", func(c *gin.Context) {})

    api := router.Group("api")
    api.GET("/test", func(c *gin.Context) {})

    router.Run(":80")
}
//自定义的log输出形式
[RoLingG]  2023-10-15 20:13:44  -  200  -  127.0.0.1  -  GET  -  /index  

但自己自定义的没有高亮显示,要自己搞一搞,涉及到Gin框架的源码以及控制台的特殊颜色输出写法。

↓下面就是还原gin.Default的那种高亮显示的代码↓:

package main

import (
    "fmt"
    "github.com/gin-gonic/gin"
)

func main() {
    //New方便自定义log
    router := gin.New()
    
router.Use(gin.LoggerWithFormatter(func(params gin.LogFormatterParams) string {
        return fmt.Sprintf("[RoLingG]  %s  -  %d  -  %s  -  %s%s%s  -  %s  \n",
            params.TimeStamp.Format("2006-01-02 15:04:05"),
            params.StatusCodeColor(), params.StatusCode, params.ResetColor(),
            params.ClientIP,
            params.MethodColor(), params.Method, params.ResetColor(),
            params.Path)
    }))

    //查看default的内置日志
    router.GET("/index")
    router.POST("/user", func(c *gin.Context) {})
    router.POST("/article", func(c *gin.Context) {})

    api := router.Group("api")
    api.GET("/test", func(c *gin.Context) {})

    router.Run(":80")
}

%s%s%s的原因是因为params.MethodColor()传过来的是类似\033[97;42m这样的一串特殊的控制台颜色输出代码,在这种代码后面的字符串都会有相对应的颜色,所以接上params.Method就会有类似高亮显示的效果,但之后的显示又不要高亮显示,则需要params.ResetColor()进行颜色重置。(为了美观可以%s %s %s之间隔开来,会让颜色散开一点)

为了代码简洁明显化,可以把LogFormatterParams函数化:

package main

import (
    "fmt"
    "github.com/gin-gonic/gin"
)

func LogFormatterParams(params gin.LogFormatterParams) string {
    return fmt.Sprintf("[RoLingG]  %s  -  %s %d %s  -  %s  -  %s %s %s  -  %s  \n",
        params.TimeStamp.Format("2006-01-02 15:04:05"),
        params.StatusCodeColor(), params.StatusCode, params.ResetColor(),
        params.ClientIP,
        params.MethodColor(), params.Method, params.ResetColor(),
        params.Path)
}

func main() {
    //New方便自定义log
    router := gin.New()
    router.Use(gin.LoggerWithFormatter(LogFormatterParams))

    //查看default的内置日志
    router.GET("/index")
    router.POST("/user", func(c *gin.Context) {})
    router.POST("/article", func(c *gin.Context) {})

    api := router.Group("api")
    api.GET("/test", func(c *gin.Context) {})

    router.Run(":80")
}

另一种写法(目前我没看出区别,但肯定有):

package main

import (
    "fmt"
    "github.com/gin-gonic/gin"
)

func LogFormatterParams(params gin.LogFormatterParams) string {
    return fmt.Sprintf("[RoLingG]  %s  -  %s %d %s  -  %s  -  %s %s %s  -  %s  \n",
        params.TimeStamp.Format("2006-01-02 15:04:05"),
        params.StatusCodeColor(), params.StatusCode, params.ResetColor(),
        params.ClientIP,
        params.MethodColor(), params.Method, params.ResetColor(),
        params.Path)
}

func main() {
    //New方便自定义log
    router := gin.New()
    //router.Use(gin.LoggerWithFormatter(LogFormatterParams))
    //另一种写法,在这里效果基本一样
    router.Use(gin.LoggerWithConfig(gin.LoggerConfig{Formatter: LogFormatterParams}))

    //查看default的内置日志
    router.GET("/index")
    router.POST("/user", func(c *gin.Context) {})
    router.POST("/article", func(c *gin.Context) {})

    api := router.Group("api")
    api.GET("/test", func(c *gin.Context) {})

    ////查询路由
    //fmt.Println(router.Routes())
    //for _, info := range router.Routes() {
    //    fmt.Println(info.Path, info.Method, info.Handler)
    //}

    router.Run(":80")
}

——————————————————————————————————————————————

Logrus

目前比较火的一个第三方日志组件库。

package main

import "github.com/sirupsen/logrus"

func main() {
    logrus.Error("出错了")
    logrus.Warnln("警告")
    logrus.Infof("信息")
    logrus.Debugf("Debug")
    logrus.Println("输出")
}

但是会发现输出只有四行,debug没了:

time="2023-10-15T21:22:36+08:00" level=error msg="出错了"
time="2023-10-15T21:22:36+08:00" level=warning msg="警告"
time="2023-10-15T21:22:36+08:00" level=info msg="信息"
time="2023-10-15T21:22:36+08:00" level=info msg="输出"

原因是因为日志默认等级的问题,用logrus.GetLevel()可以看最低优先级,会发现最低是info,则比info小的都不会输出。

源码优先级设定:

const (
    // PanicLevel level, highest level of severity. Logs and then calls panic with the
    // message passed to Debug, Info, ...
    PanicLevel Level = iota
    // FatalLevel level. Logs and then calls `logger.Exit(1)`. It will exit even if the
    // logging level is set to Panic.
    FatalLevel
    // ErrorLevel level. Logs. Used for errors that should definitely be noted.
    // Commonly used for hooks to send errors to an error tracking service.
    ErrorLevel
    // WarnLevel level. Non-critical entries that deserve eyes.
    WarnLevel
    // InfoLevel level. General operational entries about what's going on inside the
    // application.
    InfoLevel
    // DebugLevel level. Usually only enabled when debugging. Very verbose logging.
    
    //下两个无输出
    DebugLevel
    // TraceLevel level. Designates finer-grained informational events than the Debug.
    TraceLevel
)
//从上到下优先级依次降低
//默认输出最低优先级是info,debug和trace都不算在里面,因为没有默认的输出。

我们可以通过logrus.SetLevel()进行优先级的更改

package main

import (
    "fmt"
    "github.com/sirupsen/logrus"
)

func main() {
    //将最底线优先改为Debug
    logrus.SetLevel(logrus.DebugLevel)

    logrus.Error("出错了")
    logrus.Warnln("警告")
    logrus.Infof("信息")
    logrus.Debugf("Debug")
    logrus.Println("输出")
    
    fmt.Println(logrus.GetLevel())
}
//这样会发现优先级底线是Debug,其他也能输出在控制台中。
//但如果本身优先级就高的设置为底线优先级,则比它低的都不会输出出来。

设置特定的字段

package main

import "github.com/sirupsen/logrus"

func main() {
   //↓会返回一个Entry
   log := logrus.WithField("app", "study")
   log.Errorf("你好,但是报错")
}
//上述代码会给日志输出加上自定义的字段
time="2023-10-15T21:38:55+08:00" level=error msg="你好,但是报错" app=study

甚至它还可以链式写入:

log := logrus.WithField("app", "study").WithField("service", "logrus")

写多个特定字段也可以用logrus.WithFields:

package main

import "github.com/sirupsen/logrus"

func main() {
   //↓会返回一个Entry
   log := logrus.WithField("app", "study").WithField("service", "logrus")

   //它的写入是以个map,它可以用在中间件获取ip直接利用。
   logs := logrus.WithFields(logrus.Fields{
      "user_name": "RoLingG",
      "user_age":  26,
      "ip":        "192.168.1.1",
   })
   log.Errorf("你好,但是报错")
   logs.Errorf("你好啊家人,但是报错")
}

输出是这样:

time="2023-10-15T21:48:21+08:00" level=error msg="你好,但是报错" app=study service=logrus
time="2023-10-15T21:48:21+08:00" level=error msg="你好啊家人,但是报错" ip=192.168.1.1 user_age=26 user_name=RoLingG

当然还可以套娃:

package main

import "github.com/sirupsen/logrus"

func main() {
   log1 := logrus.WithField("app", "study").WithField("service", "logrus")

   //它的写入是以个map,它可以用在中间件获取ip直接利用。
   logs := log1.WithFields(logrus.Fields{
      "user_name": "RoLingG",
      "user_age":  26,
      "ip":        "192.168.1.1",
   })

   log1.Errorf("你好,但是报错")
   logs.Errorf("你好啊家人,但是报错")
}

通常在一个应用中、或者应用的一部分中,都有一些固定的Field,比如在处理用户的http请求时,行下文中,所有的日志都会有request_id和user_ip,为了避免每次记录日志都要会用log.WithFields({"request_id": request_id, "user_ip": user_ip}),我们可以创建一个logrus.Entry的实例,为这个实例设置默认的Field,在上下文使用这个实例记录日志即可,免去麻烦繁琐的步骤。

显示样式

一般默认来说,logrus默认以text的形式去显示日志,但我们也可以通过更改将其用json的方式显示出来。

在正式使用前加入logrus.SetFormatter(&logrus.JSONFormatter{})logrus.SetFormatter(&logrus.TextFormatter{})将其改为json格式或者text格式。

自定义颜色输出

原理:ANSI 控制码,用于设置文本颜色。\033 是控制码的开始,是八进制数字,[31m 表示将文本设置为红色。ANSI 控制码是用于在终端和控制台中控制文本格式和颜色的一种标准。它们通常用于在命令行界面 (CLI) 程序中输出彩色文本或者在文本模式下的图形界面 (GUI) 中输出文本。

颜色列表:

\033[30m 黑色 \033[0m
\033[31m 红色 \033[0m
\033[32m 绿色 \033[0m
\033[33m 黄色 \033[0m
\033[34m 蓝色 \033[0m
\033[35m 紫色 \033[0m
\033[36m 青色 \033[0m
\033[37m 灰色 \033[0m
// 背景色
\033[40m 黑色 \033[0m
\033[41m 红色 \033[0m
\033[42m 绿色 \033[0m
\033[43m 黄色 \033[0m
\033[44m 蓝色 \033[0m
\033[45m 紫色 \033[0m
\033[46m 青色 \033[0m
\033[47m 灰色 \033[0m

注意:首先日志输出格式得是text格式。

输入:logrus.SetFormatter(&logrus.TextFormatter{ForceColors: true})

这样可以在控制台输出里有颜色了:

package main

import "github.com/sirupsen/logrus"

func main() {
   //↓会返回一个Entry
   log := logrus.WithField("app", "study").WithField("service", "logrus")
   //它的写入是以个map,它可以用在中间件获取ip直接利用。
   logs := logrus.WithFields(logrus.Fields{
      "user_name": "RoLingG",
      "user_age":  26,
      "ip":        "192.168.1.1",
   })
   logrus.SetFormatter(&logrus.TextFormatter{ForceColors: true})
   log.Errorf("你好,但是报错")
   logs.Errorf("你好啊家人,但是报错")
   
   logrus.Error("出错了")
   logrus.Warnln("警告")
   logrus.Infof("信息")
   logrus.Debugf("Debug")
   logrus.Println("输出")

}

还有一些别的配置:

ForceColors:是否强制使用颜色输出。
DisableColors:是否禁用颜色输出。
ForceQuote:是否强制引用所有值。
DisableQuote:是否禁用引用所有值。
DisableTimestamp:是否禁用时间戳记录。
FullTimestamp:是否在连接到 TTY 时输出完整的时间戳。
TimestampFormat:用于输出完整时间戳的时间戳格式。

更改之后就能简单的达到添加出完整时间戳的样式啦:

package main

import "github.com/sirupsen/logrus"

func main() {
   logrus.SetFormatter(&logrus.TextFormatter{ForceColors: true, TimestampFormat: "2006-01-02 15:04:05", FullTimestamp: true})

   logrus.Error("出错了")
   logrus.Warnln("警告")
   logrus.Infof("信息")
   logrus.Debugf("Debug")
   logrus.Println("输出")

}

通过上述这个颜色列表,我们可以使用它们用来自定义输出颜色。

例如把Error的输出内容改成紫色:logrus.Error("\033[35m 出错了 \033[0m")

为了方便颜色的设置,我们还可以通过自定义函数去实现:

package main

import (
    "fmt"
    "github.com/sirupsen/logrus"
)

// 这里是根据源码定义颜色的序号
const (
    cBlack  = 0
    cRed    = 1
    cGreen  = 2
    cYello  = 3
    cBlue   = 4
    cPurple = 5
    cCyan   = 6
    cGray   = 7
)

// 自定义颜色输出
func PrintColor(colorCode int, text string, isBackground bool) {
    if isBackground != true {
        fmt.Printf("\033[3%dm %s \033[0m", colorCode, text)
    } else {
        fmt.Printf("\033[4%dm %s \033[0m", colorCode, text)
    }
    // 也可以这样写,少一个括号和一个else,精简代码
//    if isBackground != true {
//        fmt.Printf("\033[3%dm %s \033[0m", colorCode, text)
//        return
//    }
//    fmt.Printf("\033[4%dm %s \033[0m", colorCode, text)
}

func main() {
    PrintColor(4, "内容颜色是蓝色", false)
    PrintColor(4, "背景和内容都是蓝色", true)
    // 也可以用const内的变量名做colorCode
    PrintColor(cBlue, "背景和内容都是蓝色", true)
}

行号显示

没有行号,就无法准确获取日志的位置在哪

logrus.SetReportCaller(true)

控制台日志显示如下:

GOROOT=D:\GoLand\SDK\go1.20.3 #gosetup
GOPATH=D:\GoLand #gosetup
D:\GoLand\SDK\go1.20.3\bin\go.exe build -o C:\Users\RoLingG\AppData\Local\Temp\GoLand\___go_build_2__go.exe D:\GoLand\Gogin\Gogin\logrus\2.设置特定字段.go #gosetup
C:\Users\RoLingG\AppData\Local\Temp\GoLand\___go_build_2__go.exe
ERRO[2023-10-17 21:09:41]D:/GoLand/Gogin/Gogin/logrus/2.设置特定字段.go:52 main.main() 你好,但是报错                                       app=study service=logrus
ERRO[2023-10-17 21:09:41]D:/GoLand/Gogin/Gogin/logrus/2.设置特定字段.go:53 main.main() 你好啊家人,但是报错                                    ip=192.168.1.1 user_age=26 user_name=RoLingG
ERRO[2023-10-17 21:09:41]D:/GoLand/Gogin/Gogin/logrus/2.设置特定字段.go:55 main.main()  出错了
WARN[2023-10-17 21:09:41]D:/GoLand/Gogin/Gogin/logrus/2.设置特定字段.go:56 main.main() 警告
INFO[2023-10-17 21:09:41]D:/GoLand/Gogin/Gogin/logrus/2.设置特定字段.go:57 main.main() 信息
INFO[2023-10-17 21:09:41]D:/GoLand/Gogin/Gogin/logrus/2.设置特定字段.go:59 main.main() 输出
 颜色是蓝色  颜色是蓝色
PREV
[Golang]Gin框架 10.路由分组和中间件
NEXT
[Golang]Gin框架 12.Hook

评论(0)

发布评论