Gin框架学习笔记(六)——gin中的日志使用

gin内置日志组件的使用

前言

在之前我们要使用Gin框架定义路由的时候我们一般会使用Default方法来实现,我们来看一下他的实现:


func Default(opts ...OptionFunc) *Engine {
	debugPrintWARNINGDefault()
	engine := New()
	engine.Use(Logger(), Recovery())
	return engine.With(opts...)
}

我们可以看到它注册了两个中间件Logger()Recovery(),而Logger就是我们今天的主角:gin框架自带的日志组件。

输出日志到文件中

package main

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

func main() {
	file, err := os.Create("ginlog")
	if err != nil {
		fmt.Println("Create file error! err:", err)
	}
	gin.DefaultWriter = io.MultiWriter(file)
	r := gin.Default()
	r.GET("/", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "Hello World!",
		})
	})
	r.Run()
}

运行上面代码我们会发现,控制台不再会有相关日志的输出,而是打印到了ginlog文件中:

[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[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-debug] GET    /                         --> main.main.func1 (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] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on :8080

当然我们也可以选择既在控制台输出也在文件内输出:

package main

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

func main() {
	file, err := os.Create("ginlog")
	if err != nil {
		fmt.Println("Create file error! err:", err)
	}
	gin.DefaultWriter = io.MultiWriter(file, os.Stdout)
	r := gin.Default()
	r.GET("/", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "Hello World!",
		})
	})
	r.Run()
}

在这里插入图片描述
我们可以看到无论是日志文件ginlog和控制台,都实现了对日志的打印

定义日志中的路由格式

当我们运行Gin框架的时候,它会自动打印当前所有被定义的路由,比如下面这样的格式:

[GIN-debug] GET    /                         --> main.main.func1 (3 handlers)

而在Gin框架中它允许我们去自己定义路由的输出格式,我们可以自己去定义我们的路由格式:

func _Router_print_init() {
	gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string,
		nuHandlers int) {
		fmt.Printf("[三玖]: %v  %v   %v   %v  \n",
			httpMethod, absolutePath, handlerName, nuHandlers)
	}
}

输出的路由格式是这样的:

[三玖]: GET  /   main.main.func1   3

生产模式与开发模式

在我们程序其实是有两种模式的:

  • debug:开发模式
  • release:生产模式

如果我们希望控制台不在显示日志,可以将模式切换到release模式:

	gin.SetMode(gin.ReleaseMode)
	r := gin.Default()

在这里插入图片描述
我们可以看到控制台已不再输出日志信息了。

第三方包logrus日志包的使用

logrus包的安装与基本使用

logrus包的安装

logrus的安装很简单,只需要终端输入以下命令即可:

go get github.com/sirupsen/logrus

logrus包的基本使用

logrus常用方法:
	logrus.Debug("debug")
	logrus.Info("info")
	logrus.Warn("warn")
	logrus.Error("error")
	logrus.Println("println")

当我们运行该代码的时候会发现打印结果只有四行:
在这里插入图片描述
这主要是因为logrus默认的打印等级是info,在这个等级之下的不会打印,在我们生产环境下一般会要求不打印Warn以下的日志,我们可以对打印等级进行调整:

logrus.SetLevel(logrus.WarnLevel)

再次运行上面的代码,运行结果就会有所不同:
在这里插入图片描述
我们还可以查看当前的打印等级:

fmt.Println(logrus.GetLevel())

设置特定字段

如果我们希望某条日志记录的打印中添加某一条特定的字段,我们可以使用WithField方法:

log1 := logrus.WithField("key1", "value1")
log1.Info("hello world")

通常,在一个应用中、或者应用的一部分中,都有一些固定的Field。比如我们在处理用户http请求时,上下文中,所有的日志都会有request_id和user_ip为了避免每次记录日志都要使用log.WithFields(log.Fields{“request_id”: request_id, “user_ip”: user_ip}),我们可以创建一个logrus.Entry实例,为这个实例设置默认Fields,在上下文中使用这个logrus.Entry实例记录日志即可,这里我写了一个demo,仅做参考:

package main

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

type DefaultLogger struct {
	*logrus.Entry
	defaultFields logrus.Fields
}

func NewDefaultLogger() *DefaultLogger {
	logger := logrus.New()
	entry := logrus.NewEntry(logger)
	return &DefaultLogger{
		Entry:         entry,
		defaultFields: logrus.Fields{},
	}
}

func (l *DefaultLogger) WithFields(fields logrus.Fields) *logrus.Entry {
	allFields := make(logrus.Fields, len(fields))
	for k, v := range fields {
		allFields[k] = v
	}
	return l.Entry.WithFields(allFields)
}

func (l *DefaultLogger) WithDefaultField() {
	l.Entry = l.Entry.WithFields(l.defaultFields)
}

func (l *DefaultLogger) Info(msg string) {
	l.WithDefaultField()
	l.Entry.Info(msg)
}

func (l *DefaultLogger) AddDefaultField(key string, value interface{}) {
	l.defaultFields[key] = value
}

func main() {
	defaultLogger := NewDefaultLogger()
	defaultLogger.AddDefaultField("request_id", "123")
	defaultLogger.AddDefaultField("user_ip", "127.0.0.1")

	// 使用默认字段记录日志
	defaultLogger.Info("This is a log message with default fields")

	// 添加额外字段记录日志
	defaultLogger.WithFields(logrus.Fields{
		"additional_field": "abc",
	}).Info("This is a log message with additional field")

}

输出结果为:
在这里插入图片描述

设置显示样式

虽然日志的打印默认是txt格式的,但是我们也可以将格式修改为json格式的:

logrus.SetFormatter(&logrus.TextFormatter{})

将日志输入到文件

package main

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

func main() {
	file, err := os.OpenFile("./logrus.log", os.O_CREATE|os.O_WRONLY, 0666)
	if err != nil {
		panic(err)
	}
	logrus.SetOutput(file)
	logrus.Error("error")
}

我们还可以让控制台和日志文件一起输出:

package main

import (
	"github.com/sirupsen/logrus"
	"golang.org/x/sys/windows"
	"io"
	"os"
)

func main() {
	file, err := os.OpenFile("./logrus.log", os.O_CREATE|os.O_WRONLY|windows.O_APPEND, 0666)
	if err != nil {
		panic(err)
	}
	writers := []io.Writer{
		file,
		os.Stdout,
	}
	lod := io.MultiWriter(writers...)
	logrus.SetOutput(lod)
	logrus.Error("error")
	logrus.Info("info")
}

显示行号

logrus.SetReportCaller(true)

logus的Hook机制

在使用logrus这一第三方包的时候,我们可以基于Hook机制来为logrus添加一些拓展功能。

首先我们先定义Hook结构体:

type Hook struct {
	Levels() []logrus.Level  // 返回日志级别
	Fire(entry *logrus.Entry) error  // 日志处理
}

我们Hook结构体中一般会有两个成员:

  • Levels:Hook机制起作用的日志级别
  • Fire:对应的日志处理方式

这里我们举一个例子,如果我们希望将所有Error级别的日志单独拎出来,我们可以基于Hook机制来实现:

package main

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

type Hook struct {
	Writer *os.File
}

func (MyHook *Hook) Fire(entry *logrus.Entry) error {
	line, err := entry.String()
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
	}
	MyHook.Writer.Write([]byte(line))
	return nil
}

func (MyHook *Hook) 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("./error.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666)
	hook := &Hook{Writer: file}
	logrus.AddHook(hook)
	logrus.Error("error")
}

日志分割

按时间分割

  • Write写法
package main

import (
	"fmt"
	"github.com/sirupsen/logrus"
	"io"
	"os"
	"path/filepath"
	"strings"
	"time"
)

type LogFormatter struct{}

// Format 格式详情
func (s *LogFormatter) Format(entry *logrus.Entry) ([]byte, error) {
	timestamp := time.Now().Local().Format("2006-01-02 15:04:05")
	var file string
	var len int
	if entry.Caller != nil {
		file = filepath.Base(entry.Caller.File)
		len = entry.Caller.Line
	}
	//fmt.Println(entry.Data)
	msg := fmt.Sprintf("[%s] %s [%s:%d] %s\n", strings.ToUpper(entry.Level.String()), timestamp, file, len, entry.Message)
	return []byte(msg), nil
}

type LogWriter struct {
	Writer   *os.File
	logPath  string
	fileDate string //判断是否需要切换日志文件
	fileName string //日志文件名
}

func (writer *LogWriter) Write(p []byte) (n int, err error) {
	if writer == nil {
		logrus.Error("writer is nil")
		return 0, nil
	}
	if writer.Writer == nil {
		logrus.Error("writer.Writer is nil")
		return 0, nil
	}
	timer := time.Now().Format("2006-01-02 04:12")

	//需要切换日志文件
	if writer.fileDate != timer {
		writer.fileDate = timer
		writer.Writer.Close()
		err = os.MkdirAll(writer.logPath, os.ModePerm)
		if err != nil {
			logrus.Error(err)
			return 0, nil
		}
		filename := fmt.Sprintf("%s/%s.log", writer.logPath, writer.fileDate)
		writer.Writer, err = os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666)
		if err != nil {
			logrus.Error(err)
			return 0, nil
		}
	}
	return writer.Writer.Write(p)
}

func Initing(logPath string, fileName string) {
	fileDate := time.Now().Format("20060102")
	filepath := fmt.Sprintf("%s/%s", logPath, fileDate)
	err := os.MkdirAll(filepath, os.ModePerm)
	if err != nil {
		logrus.Error(err)
		return
	}

	filename := fmt.Sprintf("%s/%s.log", filepath, fileName)
	writer, err := os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666)
	if err != nil {
		logrus.Error(err)
		return
	}

	Logwriter := LogWriter{logPath: logPath, fileDate: fileDate, fileName: fileName, Writer: writer}
	logrus.SetOutput(os.Stdout)
	writers := []io.Writer{
		Logwriter.Writer,
		os.Stdout,
	}
	multiWriter := io.MultiWriter(writers...)
	logrus.SetOutput(multiWriter)
	logrus.SetReportCaller(true)
	logrus.SetFormatter(new(LogFormatter))
}

func main() {
	Initing("./", "fengxu")
	logrus.Warn("fengxu")
	logrus.Error("fengxu")
	logrus.Info("fengxu")

}
  • Hook写法
package main

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

type Hook struct {
	writer   *os.File
	logPath  string
	fileName string
	fileDate string
}

func (MyHook *Hook) Levels() []logrus.Level {
	return logrus.AllLevels
}

func (MyHook *Hook) Fire(entry *logrus.Entry) error {
	timer := time.Now().Format("2006-01-02")
	line, _ := entry.String()
	//需要切换日志文件
	if MyHook.fileDate != timer {
		MyHook.fileDate = timer
		MyHook.writer.Close()
		filepath := fmt.Sprintf("%s/%s", MyHook.logPath, MyHook.fileDate)
		err := os.MkdirAll(filepath, os.ModePerm)
		if err != nil {
			logrus.Error(err)
			return err
		}

		filename := fmt.Sprintf("%s/%s.log", filepath, MyHook.fileName)
		MyHook.writer, _ = os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666)
	}
	MyHook.writer.Write([]byte(line))
	return nil
}

func InitFile(logPath string, fileName string) {
	timer := time.Now().Format("2006-01-02")
	filepath := fmt.Sprintf("%s/%s", logPath, timer)
	err := os.MkdirAll(filepath, os.ModePerm)
	if err != nil {
		logrus.Error(err)
		return
	}

	filename := fmt.Sprintf("%s/%s.log", filepath, fileName)
	writer, err := os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666)
	if err != nil {
		logrus.Error(err)
		return
	}
	logrus.AddHook(&Hook{
		writer:   writer,
		logPath:  logPath,
		fileName: fileName,
		fileDate: timer,
	})
}

func main() {
	InitFile("./log", "fengxu")
	logrus.Error("test")

}

按日志等级分割

package main

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

const (
	alllog   = "all"
	errorlog = "error"
	warnlog  = "warn"
)

type Hook struct {
	allLevel   *os.File
	errorLevel *os.File
	warnLevel  *os.File
}

func (MyHook *Hook) Levels() []logrus.Level {
	return logrus.AllLevels
}

func (MyHook *Hook) Fire(entry *logrus.Entry) error {
	line, _ := entry.String()
	switch entry.Level {
	case logrus.ErrorLevel:
		MyHook.errorLevel.Write([]byte(line))
	case logrus.WarnLevel:
		MyHook.warnLevel.Write([]byte(line))
	}
	MyHook.allLevel.Write([]byte(line))
	return nil
}

func InitLevel(logPath string) {
	err := os.MkdirAll(logPath, os.ModePerm)
	if err != nil {
		logrus.Error("创建目录失败")
		return
	}
	allFile, err := os.OpenFile((fmt.Sprintf("%s/%s", logPath, alllog)), os.O_CREATE|os.O_APPEND|os.O_RDWR, 0600)
	errFile, err := os.OpenFile((fmt.Sprintf("%s/%s", logPath, errorlog)), os.O_CREATE|os.O_APPEND|os.O_RDWR, 0600)
	warnFile, err := os.OpenFile((fmt.Sprintf("%s/%s", logPath, warnlog)), os.O_CREATE|os.O_APPEND|os.O_RDWR, 0600)
	logrus.AddHook(&Hook{allLevel: allFile, errorLevel: errFile, warnLevel: warnFile})
}

func main() {
	InitLevel("./log")
	logrus.SetReportCaller(true)
	logrus.Errorln("你好")
	logrus.Errorln("err")
	logrus.Warnln("warn")
	logrus.Infof("info")
	logrus.Println("print")

}

gin集成logrus

  • main函数(main.go)
package main

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

func main() {
	log.InitFile("./log", "fengxu")
	r := gin.New()
	r.Use(middleware.Logmiddleware())
	r.GET("/", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "pong",
		})
	})
	r.Run(":8080")
}

  • log.go
package log

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

type Hook struct {
	writer   *os.File
	logPath  string
	fileName string
	fileDate string
}

func (MyHook *Hook) Levels() []logrus.Level {
	return logrus.AllLevels
}

func (MyHook *Hook) Fire(entry *logrus.Entry) error {
	timer := time.Now().Format("2006-01-02")
	line, _ := entry.String()
	//需要切换日志文件
	if MyHook.fileDate != timer {
		MyHook.fileDate = timer
		MyHook.writer.Close()
		filepath := fmt.Sprintf("%s/%s", MyHook.logPath, MyHook.fileDate)
		err := os.MkdirAll(filepath, os.ModePerm)
		if err != nil {
			logrus.Error(err)
			return err
		}

		filename := fmt.Sprintf("%s/%s.log", filepath, MyHook.fileName)
		MyHook.writer, _ = os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666)
	}
	MyHook.writer.Write([]byte(line))
	return nil
}

type LogFormat struct {
}

func (l *LogFormat) Format(entry *logrus.Entry) ([]byte, error) {
	var buff *bytes.Buffer
	if entry.Buffer != nil {
		buff = entry.Buffer
	} else {
		buff = &bytes.Buffer{}
	}
	_, _ = fmt.Fprintf(buff, "%s\n", entry.Message) //这里可以自己去设置输出格式
	return buff.Bytes(), nil
}

func InitFile(logPath string, fileName string) {
	logrus.SetFormatter(&LogFormat{})
	timer := time.Now().Format("2006-01-02")
	filepath := fmt.Sprintf("%s/%s", logPath, timer)
	err := os.MkdirAll(filepath, os.ModePerm)
	if err != nil {
		logrus.Error(err)
		return
	}

	filename := fmt.Sprintf("%s/%s.log", filepath, fileName)
	writer, err := os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666)
	if err != nil {
		logrus.Error(err)
		return
	}
	logrus.AddHook(&Hook{
		writer:   writer,
		logPath:  logPath,
		fileName: fileName,
		fileDate: timer,
	})
}

  • 中间件(lmiddleware.go)
package middleware

import (
	"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
		if raw != "" {
			path = path + "?" + raw
		}

		c.Next() //执行其他中间件

		//end := time.Now()
		//timesub := end.Sub(start)  //响应所需时间
		//ClientIp := c.ClientIP()  //客户端ip
		statuscode := c.Writer.Status()
		//var statusColor string  
		//switch c.Writer.Status() {
		//case 200:
		//	statusColor = fmt.Sprintf("\033[%dm%d\033[0m", status200, statuscode)
		//case 404:
		//	statusColor = fmt.Sprintf("\033[%dm%d\033[0m", status404, statuscode)
		//default:
		//	statusColor = fmt.Sprintf("\033[%dm%d\033[0m", status500, statuscode)
		//}
		//
		//var methodColor string
		//switch c.Request.Method {
		//case "GET":
		//	methodColor = fmt.Sprintf("\033[%dm%s\033[0m", methodGET, c.Request.Method)
		//}

		logrus.Infof("[GIN] %s  |%d  |%s  |%s",
			start.Format("2006-01-02 15:04:06"),
			statuscode,
			c.Request.Method,
			path,
		)

	}
}

项目结构:
在这里插入图片描述

结语

至此我们对Gin框架的简单学习就到此为止了,更多的学习大家可以前去查看Gin框架官方文档:
Gin框架官方文档

后面就要开始对Gorm的学习了,下篇见!

  • 13
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

落雨便归尘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值