logrus库的使用
一.基础语句
可以通过代码go get github.com/sirupsen/logrus
来获取logrus
库(go中更常称作包而不是库)
通过logrus
库可以获得更高效的日志输出。
1.输出等级
下列为logrus
库中常见的几种输出格式
1
2
3
4
5
logrus.Error("ERROR")
logrus.Warnln("Warn")
logrus.Infof("Info")
logrus.Debugf("Debug")
logrus.Println("Println")
输出结果:
1
2
3
4
time="2024-06-12T20:07:43+08:00" level=error msg=ERROR
time="2024-06-12T20:07:43+08:00" level=warning msg=Warn
time="2024-06-12T20:07:43+08:00" level=info msg=Info
time="2024-06-12T20:07:43+08:00" level=info msg=Println
其中Debugf
并未成功输出,是因为logrus
内存在输出的优先级,当低于该优先级时,该日志便不会输出。可以通过logrus.GetLevel()
来获取当前的优先级信息。
1
2
3
fmt.Println(logrus.GetLevel())
//输出结果
time="2024-06-12T20:08:56+08:00" level=info msg=Println
即此时的输出等级为info
级别,低于info
级别的日志将不会被输出。
其中logrus
库中的等级排序为:
1
2
3
4
5
6
7
8
9
var AllLevels = []Level{
PanicLevel,
FatalLevel,
ErrorLevel,
WarnLevel,
InfoLevel,
DebugLevel,
TraceLevel,
}
如果我们想更改最低输出等级,可以通过logrus.SetLevel()
来继续修改。
1
2
3
4
5
6
7
8
9
10
11
12
func main() {
logrus.SetLevel(logrus.DebugLevel)
logrus.Error("ERROR")
logrus.Warnln("Warn")
logrus.Infof("Info")
logrus.Debugf("Debug")
}
//输出结果
time="2024-06-12T20:16:24+08:00" level=error msg=ERROR
time="2024-06-12T20:16:24+08:00" level=warning msg=Warn
time="2024-06-12T20:16:24+08:00" level=info msg=Info
time="2024-06-12T20:16:24+08:00" level=debug msg=Debug
此时,Debugf
便可正常输出。
对于所有的7个等级中,常用的等级为Error、Warn、Info、Debug
,其余的等级存在特殊性,如Panic
会引起程序恐慌(Panic),而Fatal
则会在输出日志后退出。
2.设置特定字段
通过logrus.WithField
指令,可以增加日志输出时的字段
1
2
3
4
5
6
func main() {
log := logrus.WithField("first_name", "Outer").WithField("last_name", "Cyrex")
log.Errorf("Error")
}
//输出结果
time="2024-06-12T20:24:31+08:00" level=error msg=Error first_name=Outer last_name=Cyrex
更常用的是logrus.WithFields
,能够批量增加输出字段
1
2
3
4
5
6
7
8
9
10
func main() {
log := logrus.WithFields(logrus.Fields{
"name": "Outer",
"ip": "127.0.0.1",
"port": 8080,
})
log.Errorf("Error")
}
//输出结果
time="2024-06-12T20:28:25+08:00" level=error msg=Error ip=127.0.0.1 name=Outer port=8080
由于logrus
为我们默认提供的输出格式是Text
格式,我们也可以选择将其更改为JSON
格式
1
2
logrus.SetFormatter(&logrus.JSONFormatter{})//JSON格式
logrus.SetFormatter(&logrus.TextFormatter{})//Text格式
若以JSON
格式输出,则会输出下列字段:
1
2
3
4
5
6
7
8
9
//JSON格式已美化
{
"ip":"127.0.0.1",
"level":"error",
"msg":"Error",
"name":"Outer",
"port":8080,
"time":"2024-06-12T20:35:25+08:00",
}
通过ForceColors
可以调节颜色输出,但只能应用于Text
格式
1
2
3
4
5
6
7
8
logrus.SetFormatter(&logrus.TextFormatter{
ForceColors: true,
})
//输出效果
ERRO[0000] ERROR //红色
WARN[0000] Warn //黄色
INFO[0000] Info //蓝色
DEBU[0000] Debug //灰色
其中,SetFormatter
的常用指令还有
1
2
3
4
5
6
7
ForceColors: true, //是否强制颜色输出
FullTimestamp: true, //是否输出完整的时间戳
TimestampFormat: "2006-01-02 15:04:05", //自定义时间戳格式
DisableColors: true, //是否关闭颜色显示
DisableQuote: true, //是否禁止引用所有制
ForceQuote: true, //是否强制引用所有值
DisableTimestamp: true, //是否关闭时间戳
补充:自定义颜色
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func main() {
//字体色
fmt.Println("\033[30mBlack\033[0m")
fmt.Println("\033[31mRed\033[0m")
fmt.Println("\033[32mGreen\033[0m")
fmt.Println("\033[33mYellow\033[0m")
fmt.Println("\033[34mBlue\033[0m")
fmt.Println("\033[35mPurple\033[0m")
fmt.Println("\033[36mCyan\033[0m")
fmt.Println("\033[37mGrey\033[0m")
//背景色
fmt.Println("\033[40mBlack\033[0m")
fmt.Println("\033[41mRed\033[0m")
fmt.Println("\033[42mGreen\033[0m")
fmt.Println("\033[43mYellow\033[0m")
fmt.Println("\033[44mBlue\033[0m")
fmt.Println("\033[45mPurple\033[0m")
fmt.Println("\033[46mCyan\033[0m")
fmt.Println("\033[47mGrey\033[0m")
}
二.自定义输出
1.文件输出
在进行文件输出之前,需要熟悉os
库的OpenFile
函数是如何使用的。
如下所示,OpenFile
的输入值有三个,分别为相对路径、附加指令、权限掩码。
附加指令的定义如下方const
内的内容。
1
2
3
4
5
6
7
8
9
10
11
12
func OpenFile(name string, flag int, perm FileMode) (*File, error)
const (
//O_RDONLY、O_WRONLY或O_RDWR中必存在且仅能存在一个
O_RDONLY int = syscall.O_RDONLY // 打开的文件只可读
O_WRONLY int = syscall.O_WRONLY // 打开的文件只可写
O_RDWR int = syscall.O_RDWR // 打开的文件可读可写
O_APPEND int = syscall.O_APPEND // 写入时将数据附加到文件中
O_CREATE int = syscall.O_CREAT // 若文件不存在则创建新文件
O_EXCL int = syscall.O_EXCL // 与O_CREATE一起使用时,文件不能存在。
O_SYNC int = syscall.O_SYNC // 打开以进行同步I/O。
O_TRUNC int = syscall.O_TRUNC // 打开时截断常规可写文件
)
而最后的perm
字段用于指定新文件的权限和/或现有文件的权限掩码(umask),若没有权限掩码时,文件的默认权限为0666
,文件夹的默认权限为0777
。
在Unix-like系统中,权限位通常由三组数字表示,每组三位,分别对应文件的所有者(user)、所属组(group)和其他人(others)的权限。每组数字的每一位代表一种权限:读(r,数字4)、写(w,数字2)和执行(x,数字1)。
一些常见的事例为:
0644
: 所有者有读写权限,组和其他人有读权限。(rw-r–r–)0755
: 所有者有读写执行权限,组有读和执行权限,其他人有读和执行权限。(rwxr-xr-x)0600
: 所有者有读写权限,组和其他人没有任何权限。(rw——-)0777
: 所有者、组和其他人都有读写执行权限。(rwxrwxrwx)
由上方的介绍便可以理解os.Create()
的内容了
1
2
3
func Create(name string) (*File, error) {
return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
}
由此,我们可以将日志内容写入log/gin.log
文件中
1
2
3
4
5
6
7
8
9
10
11
func main() {
file, _ := os.OpenFile("log/gin.log", os.O_CREATE|os.O_WRONLY, 0666)
logrus.SetOutput(file)
logrus.SetFormatter(&logrus.TextFormatter{
DisableTimestamp: true,
})
logrus.Infof("Hello,World!")
}
//log文件内
level=info msg="Hello,World!"
同样,为了让日志即在文件中写入,又可在控制台输出
可以采用io.MultiWriter()
来实现
1
2
3
4
5
6
7
writers := []io.Writer{
file,
os.Stdout,
}
logrus.SetOutput(io.MultiWriter(writers...))
//或者也可直接写入
logrus.SetOutput(io.MultiWriter(file, os.Stdout))
2.自定义输出格式
为了自定义输出格式,我们需要定义一个结构体来存储方法。
1
2
3
4
5
6
7
8
9
10
11
12
type myFormatter struct{}
func (myFormatter) Format(entry *logrus.Entry) ([]byte, error) {
var b *bytes.Buffer
if entry.Buffer == nil {
b = &bytes.Buffer{}
} else {
b = entry.Buffer
}
TimeForm := entry.Time.Format("2006-01-02")
_, _ = fmt.Fprintf(b, "Level:%s msg:%s time:[%s]", entry.Level, entry.Message, TimeForm)
return b.Bytes(), nil
}
随后在主函数中调用myFormatter
来实现自定义输出格式
1
2
3
4
logrus.SetFormatter(Format)
logrus.Infof("Hello,World!")
//输出格式
Level:info msg:Hello,World! time:[2024-06-12]
通过在Format
内加入下列代码可以输出详细的文件信息和行号以及对应的函数名。
1
2
fileVal := fmt.Sprintf("file:%s line:%d", path.Base(entry.Caller.File), entry.Caller.Line)
funcname := entry.Caller.Func.Name()
其中,path.Base
可以截取文件路径中相对于当前项目的相对路径。
加入上述代码后的输出结果:
1
2
Level:info msg:Hello,World! time:[2024-06-12] file:lesson 15.go line:36 Funcname:main.main
//main.main是因为函数为匿名函数
上述代码能够成功实现的前提是要注意开启ReportCaller
1
2
logrus.SetReportCaller(true)
//SetReportCaller用来设置是否标准日志将包含calling方法
此外,可以在方法对应的结构体中传参
1
2
3
4
5
6
type myFormatter struct {
prefix string
}
_, _ = fmt.Fprintf(b, "[%s] Level:%s msg:%s time:[%s] %s Funcname:%s", f.prefix, entry.Level, entry.Message, TimeForm, fileVal, funcname)
//在Format方法中加入前缀的输出
logrus.SetFormatter(&myFormatter{prefix: "Gin"})//在调用时传入参数"Gin"
此时便可灵活的输出结果:
1
[Gin] Level:info msg:Hello,World! time:[2024-06-12] file:lesson 15.go line:38 Funcname:main.main
三.Hook
Hook
作为logrus
的重要功能,主要用于扩展日志系统的功能,允许用户自定义日志的处理逻辑。其作用类似于选择特定等级的日志进行特定操作的钩子。
Hook
作为一个接口类型,需要接受一个Levels()
方法和一个Fire()
方法
1
2
3
4
type Hook interface {
Levels() []Level
Fire(*Entry) error
}
此处的Levels()
通过返回的等级值来判断哪些等级会触发Hook
机制。而Fire(*Entry)
则是Hook
机制的本体,通过设定Fire()
的内容来决定Hook
将进行什么工作。
如何使用Hook
?
1
2
3
4
5
6
7
8
9
10
11
12
13
type myHook struct{}
func (*myHook) Levels() []logrus.Level {
return logrus.AllLevels
}
func (h *myHook) Fire(entry *logrus.Entry) error {
fmt.Println(entry.Message)
return nil
}
func main() {
logrus.AddHook(&myHook{})
logrus.Errorf("hello world!")
}
上述代码首先创建了一个用于存储方法的结构体,存储了两个方法,Levels
的返回值是logrus.AllLevels
,即所有等级均可触发Hook
。后边定义Fire
的内容,即输出entry.Message
。
1
entry.Data["name"] = "OuterCyrex"
通过在Fire
中加入上述语句,可以让所有被Hook
处理的entry
语句加上特定信息,如name=OuterCyrex
1
2
3
func (*myHook) Levels() []logrus.Level {
return []logrus.Level{logrus.ErrorLevel}
}
通过设置Levels()
方法的返回值,我们可以指定哪些类型的日志将会被处理。但注意Levels()
的返回值一定为[]logrus.Level
类型。
1
2
3
4
5
func (h *myHook) Fire(entry *logrus.Entry) error {
file, _ := os.OpenFile("log/Error.log",os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
_, _ = file.WriteString(entry.Message)
return nil
}
通过对Fire()
方法进行上述的更改,可以实现对特定等级的日志存入特定的文件。
四.日志分割
1.按时间分割
通过对日志写入的时间进行划分,将不同时间的日志纳入对应时间的文件夹,可以便于日志的存储和查询。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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 := time.Now().Format("2006-01-02")
if Timer == hook.fileDate {
_, _ = hook.file.WriteString(entry.Time.Format("2006-01-02 15:04:05") + " " + entry.Message + "\n")
}
hook.fileDate = Timer
err := hook.file.Close()
if err != nil {
logrus.Fatal(err)
}
err = os.MkdirAll(fmt.Sprintf("%s/%s", hook.logPath, hook.fileDate), os.ModePerm)
filename := fmt.Sprintf("%s/%s/%s.log", hook.logPath, hook.fileDate, hook.appName)
hook.file, _ = os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
_, _ = hook.file.WriteString(entry.Time.Format("2006-01-02 15:04:05") + " " + entry.Message + "\n")
return nil
}
func initHook(logPath string, appName string) {
fileDate := time.Now().Format("2006-01-02")
err := os.MkdirAll(fmt.Sprintf("%s/%s", logPath, fileDate), os.ModePerm)
if err != nil {
logrus.Fatal(err)
}
filename := fmt.Sprintf("%s/%s/%s.log", logPath, fileDate, appName)
file, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
logrus.Fatal(err)
}
hook := FileDateHook{file, logPath, fileDate, appName}
logrus.AddHook(&hook)
}
上述代码通过日期为单位来划分对应的日志文件。
initHook
函数用于创建首个文件夹并将file、logPath、fileDate、appName
的初始信息录入Hook
结构体中,便于后续比较。
Hook
的Fire
方法主要用于判断日期是否相同,以此来实现划分,实现了若日期相同则直接写入现有的文件,若日期不同则创建新的文件夹并写入。
2.按日志等级分割
通过日志等级分割主要是采用了Hook
的选择功能
下边的代码展示了一种日志等级分割的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package main
import (
"fmt"
log "github.com/sirupsen/logrus"
"os"
)
const (
allLog = "all"
errLog = "error"
warnLog = "warn"
infoLog = "info"
otherLog = "other"
)
type FileLevelHook struct {
Allfile *os.File
Errfile *os.File
Warnfile *os.File
Infofile *os.File
Otherfile *os.File
logPath string
}
func (hook *FileLevelHook) Levels() []log.Level {
return log.AllLevels
}
func (hook *FileLevelHook) Fire(entry *log.Entry) error {
line, _ := entry.String()
switch entry.Level {
case log.ErrorLevel:
hook.Errfile.WriteString(line)
case log.WarnLevel:
hook.Warnfile.WriteString(line)
case log.InfoLevel:
hook.Infofile.WriteString(line)
default:
hook.Otherfile.WriteString(line)
}
hook.Allfile.WriteString(line)
return nil
}
func InitLevel(logPath string) {
err := os.MkdirAll(logPath, os.ModePerm)
if err != nil {
log.Fatal(err)
return
}
All, _ := os.OpenFile(fmt.Sprintf("%s/%s.log", logPath, allLog), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
Err, _ := os.OpenFile(fmt.Sprintf("%s/%s.log", logPath, errLog), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
Warn, _ := os.OpenFile(fmt.Sprintf("%s/%s.log", logPath, warnLog), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
Info, _ := os.OpenFile(fmt.Sprintf("%s/%s.log", logPath, infoLog), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
Other, _ := os.OpenFile(fmt.Sprintf("%s/%s.log", logPath, otherLog), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
fileHook := FileLevelHook{All, Err, Warn, Info, Other, logPath}
log.AddHook(&fileHook)
}
首先,const
定义了一些字符串常量,便于之后再创建文件和打开文件时的调用,也可以不定义常量,直接在打开文件时输入对应的字符串即可。
之后在定义Hook
结构体时,定义了多个*os.file
字段,便于后续对应文件的处理。
Fire(*entry)
中以等级对日志内容进行了分辨,并存入写入对应的文件中。
InitLevel
中的MkdirAll
函数用于创建一个文件夹。剩余操作便是创建/打开文件,并启用Hook
。
通过下方的主函数进行测试:
1
2
3
4
5
6
7
8
func main() {
InitLevel("newlog")
log.SetLevel(log.DebugLevel)
log.Error("ERROR")
log.Warnln("Warn")
log.Infof("Info")
log.Debugf("Debug")
}
测试结果为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//创建了一个文件夹名为newlog,其内部包含5个文件,分别为all.log、error.log、info.log、other.log、warn.log
//all.log文件内容:
time="2024-06-13T11:59:42+08:00" level=error msg=ERROR
time="2024-06-13T11:59:42+08:00" level=warning msg=Warn
time="2024-06-13T11:59:42+08:00" level=info msg=Info
time="2024-06-13T11:59:42+08:00" level=debug msg=Debug
//error.log文件内容:
time="2024-06-13T11:59:42+08:00" level=error msg=ERROR
//info.log文件内容:
time="2024-06-13T11:59:42+08:00" level=info msg=Info
//other.log文件内容:
time="2024-06-13T11:59:42+08:00" level=debug msg=Debug
//warn.log文件内容:
time="2024-06-13T11:59:42+08:00" level=warning msg=Warn
3.基于lumberjack的滚动日志
在实际开发过程中,为了节省磁盘和方便查看,日志需要按照时间或者大小维度进行切割分成多分归档过期的日志,删除久远的日志。这个就是在日常开发中经常遇见的日志滚动(log rotation)。logrus
本身并没有实现滚动日志功能,但是我们可以使用第三方滚动插件lumberjack
来实现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main
import (
log "github.com/Sirupsen/logrus"
"gopkg.in/natefinch/lumberjack.v2"
)
func main() {
logger := &lumberjack.Logger{
// 日志输出文件路径。
Filename: "log/gin.log",
// 日志文件最大 size, 单位是 MB。
MaxSize: 100,
// 最大过期日志保留的个数。
MaxBackups: 10,
// 保留过期文件的最大时间间隔,单位天。
MaxAge: 30,
// 是否需要压缩滚动日志, 使用的 gzip 压缩,缺省为 false。
Compress: true,
}
log.SetOutput(logger) // 设置 logrus 的 io.Writer
}
五.gin集成logrus
gin
框架提供的全局中间件可以很方便的传输回每次访问的信息和内容。
我们可以通过设置一个日志中间件来实现将每次访问信息都写入日志内。
实际是,gin
框架本身自带的Logger()
方法也是通过全局中间件来实现的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package Middleware
import (
"bytes"
"fmt"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
"path"
)
type myFormatter struct {
prefix string
}
func (f myFormatter) Format(entry *logrus.Entry) ([]byte, error) {
var b *bytes.Buffer
if entry.Buffer == nil {
b = &bytes.Buffer{}
} else {
b = entry.Buffer
}
funcname := entry.Caller.Func.Name()
fileVal := fmt.Sprintf("file:%s line:%d", path.Base(entry.Caller.File), entry.Caller.Line)
TimeForm := entry.Time.Format("2006-01-02")
_, _ = fmt.Fprintf(b, "[%s] Level:%s Infomation:%s time:[%s] %s Funcname:%s\n", f.prefix, entry.Level, entry.Message, TimeForm, fileVal, funcname)
return b.Bytes(), nil
}
func LogMiddleware() gin.HandlerFunc {
logrus.SetReportCaller(true)
logrus.SetFormatter(&myFormatter{prefix: "Gin"})
return func(c *gin.Context) {
logrus.Infof("%s | %s | %s ", c.ClientIP(), c.Request.Method, c.Request.RequestURI)
}
}
其中的内容可以自定义的进行更改。
通过在程序中使用全局中间件来写入日志。
1
2
3
4
5
6
7
8
9
func main() {
gin.SetMode(gin.ReleaseMode)
router := gin.New()
router.Use(Middleware.LogMiddleware())
router.GET("/", func(c *gin.Context) {
c.JSON(200, gin.H{"msg": "Hello World"})
})
router.Run(":8080")
}
通过上述代码,每次访问网页时都会向后端传入访问数据并写入日志中。
1
2
3
//访问localhost:8080/
[Gin] Level:info Infomation:::1 | GET | / time:[2024-06-16] file:middleware.go line:33
Funcname:bin/gin_logrus/Middleware.LogMiddleware.func1
上述代码只能实现将日志内容在控制台中输出,要想写入日志文件还需要一定的操作。
通过在LogMiddleware
函数中加入下述语句,将日志写入gin.log
内
1
2
3
4
5
6
file, _ := os.OpenFile("gin_logrus/log/gin.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
writers := []io.Writer{
file,
os.Stdout,
}
logrus.SetOutput(io.MultiWriter(writers...))
此时在同目录的log
文件内将出现gin.log
文件,内容为:
1
2
3
4
5
6
7
//连续访问5次localhost:8080
//gin.log的内容为:
[Gin] Level:info msg:::1 | GET | / time:[2024-06-16] file:middleware.go line:41 Funcname:bin/gin_logrus/Middleware.LogMiddleware.func1
[Gin] Level:info msg:::1 | GET | / time:[2024-06-16] file:middleware.go line:41 Funcname:bin/gin_logrus/Middleware.LogMiddleware.func1
[Gin] Level:info msg:::1 | GET | / time:[2024-06-16] file:middleware.go line:41 Funcname:bin/gin_logrus/Middleware.LogMiddleware.func1
[Gin] Level:info msg:::1 | GET | / time:[2024-06-16] file:middleware.go line:41 Funcname:bin/gin_logrus/Middleware.LogMiddleware.func1
[Gin] Level:info msg:::1 | GET | / time:[2024-06-16] file:middleware.go line:41 Funcname:bin/gin_logrus/Middleware.LogMiddleware.func1
由此,我们可以十分方便的采用logrus
库来记录gin
框架的日志。
也可以结合之前的日期分割来搭建日志库,便于gin
框架日志的储存和查看。
例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package LogCode
import (
"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 := time.Now().Format("2006-01-02")
if Timer == hook.fileDate {
_, _ = hook.file.WriteString("Time:[" + entry.Time.Format("2006-01-02 15:04:05") + "] " + entry.Message + "\n")
return nil
}
hook.fileDate = Timer
err := hook.file.Close()
if err != nil {
logrus.Fatal(err)
}
err = os.MkdirAll(fmt.Sprintf("%s/%s", hook.logPath, hook.fileDate), os.ModePerm)
filename := fmt.Sprintf("%s/%s/%s.log", hook.logPath, hook.fileDate, hook.appName)
hook.file, _ = os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
_, _ = hook.file.WriteString("Time:[" + entry.Time.Format("2006-01-02 15:04:05") + "] " + entry.Message + "\n")
return nil
}
func InitHook(logPath string, appName string) {
fileDate := time.Now().Format("2006-01-02")
err := os.MkdirAll(fmt.Sprintf("%s/%s", logPath, fileDate), os.ModePerm)
if err != nil {
logrus.Fatal(err)
}
filename := fmt.Sprintf("%s/%s/%s.log", logPath, fileDate, appName)
file, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
logrus.Fatal(err)
}
hook := FileDateHook{file, logPath, fileDate, appName}
logrus.AddHook(&hook)
}
基本思路同之前的日期分割一致。
此时便只需要在主程序中写入InitHook
便可以创建Hook
,实现Hook
的初始化。
接下来写入中间件函数
1
2
3
4
5
func LogMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
logrus.Infof("[GIN] %s | %s | %s", c.ClientIP(), c.Request.Method, c.Request.RequestURI)
}
}
注意,此处Infof
的内容决定了输出时Entry.Message
的内容
将上述两个函数导入主程序中,引用其对应的包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main
import (
"bin/gin_logrus/LogCode"
"bin/gin_logrus/Middleware"
"github.com/gin-gonic/gin"
)
func main() {
gin.SetMode(gin.ReleaseMode)
router := gin.New()
LogCode.InitHook("log", "GIN")
router.Use(Middleware.LogMiddleware())
router.GET("/", func(c *gin.Context) {
c.JSON(200, gin.H{"msg": "Hello World"})
})
router.Run(":8080")
}
这样便可以获得一个根据日期来分割的记录gin
框架日志的文件了。
通过logrus
库,可以很方便的实现日志的记录和分割,便于网站管理和查询访问记录。