golang logrus配合file-rotatelogs,lfshook日志切割实践

package config

import (

    "bufio"

    "fmt"

    "os"

    "path"

    "strings"

    "time"

    rotatelogs "github.com/lestrrat-go/file-rotatelogs"

    "github.com/rifflock/lfshook"

    "github.com/sirupsen/logrus"

)

const FileDir = "./runtime/logs/"

const FileSuffix = ".log"

const TimeFormat = "2006-01-02 15:04:05"

const LogFileNums = 5

type MineFormatter struct{}

func (s *MineFormatter) Format(entry *logrus.Entry) ([]byte, error) {

    msg := fmt.Sprintf("[%s] [%s] %s\n", time.Now().Local().Format(TimeFormat), strings.ToUpper(entry.Level.String()), entry.Message)

    return []byte(msg), nil

}

var Log = logrus.New()

// 初始化日志

func InitLog() {

    /* 禁止日志打印到标准输出stdout */

    devnull, err := os.OpenFile(os.DevNull, os.O_APPEND|os.O_WRONLY, os.ModeAppend)

    if err != nil {

        Log.Panicf("LoggerToFile open os.DevNull failed: %v", err)

    }

    writernull := bufio.NewWriter(devnull)

    Log.SetOutput(writernull)

    //设置日志级别

    var loglevel logrus.Level

    err = loglevel.UnmarshalText([]byte("info"))

    if err != nil {

        Log.Panicf("设置log级别失败:%v", err)

    }

    Log.SetLevel(loglevel)

    NewSimpleLogger(Log, FileDir, LogFileNums)

}

// 文件日志

func NewSimpleLogger(log *logrus.Logger, logPath string, save uint) {

    lfHook := lfshook.NewHook(lfshook.WriterMap{

        logrus.DebugLevel: writer(logPath, "debug", save), // 为不同级别设置不同的输出目的

        logrus.InfoLevel:  writer(logPath, "info", save),

        logrus.WarnLevel:  writer(logPath, "warn", save),

        logrus.ErrorLevel: writer(logPath, "error", save),

        logrus.FatalLevel: writer(logPath, "fatal", save),

        logrus.PanicLevel: writer(logPath, "panic", save),

    }, &MineFormatter{})

    log.AddHook(lfHook)

}

/*

*

文件设置

*/

func writer(logPath string, level string, save uint) *rotatelogs.RotateLogs {

    logFullPath := path.Join(logPath, level)

    var cstSh, _ = time.LoadLocation("Asia/Shanghai") //上海

    fileSuffix := time.Now().In(cstSh).Format("2006-01-02") + FileSuffix

    loggerWriter, err := rotatelogs.New(

        logFullPath+"-"+fileSuffix,

        rotatelogs.WithLinkName(logFullPath),      // 生成软链,指向最新日志文件

        rotatelogs.WithRotationCount(save),        // 文件最大保存份数

        rotatelogs.WithRotationTime(time.Hour*24), // 日志切割时间间隔

        rotatelogs.WithRotationSize(1024*1024),    //1M回滚

    )

    if err != nil {

        panic(err)

    }

    return loggerWriter

}

package main

import (

    "gin/config"

)

func main() {

    config.InitLog()

    for {

        config.Log.Info("提示信息")

        config.Log.Debug("测试信息")

    }

}

/**************************************************************/

1. Windows 中 file-rotatelogs 遇到的错误及解决方法

1.1. 错误现象

failed to rotate: failed to create new symlink: symlink server.log.20210608 server.log.20210608_symlink: A required privilege is not held by the client.

用管理员 CMD 去跑下代码就知道了,此时不会出现报错

1.2. 原因

日志切割组件将文件新建软链接时候在 Windows 默认环境中会没有这个权限:

https://github.com/golang/go/issues/22874

1.3. 解决

windows10 在设置中将开发者模式打开即可,因为开发出来的代码一般跑在 Linux, 因此不用担心:

打开后

此时测试,就不见了:

2. 将 logrus 封装下与 gin 配套使用

xxx/xxx/log.go

package log

import (
	"fmt"
	"io"
	"os"
	"path"
	"time"

	"github.com/gin-gonic/gin"
	rotatelogs "github.com/lestrrat-go/file-rotatelogs"
	"github.com/rifflock/lfshook"
	"github.com/sirupsen/logrus"
)

var (
	logFilePath    = "./"
	logFileName    = "server.log"
	rotateLogsFile = "server." + "%Y%m%d" + ".log"
	Log            *logrus.Logger
)

func LoggerMiddleware() gin.HandlerFunc {
	// 日志文件
	logFile := path.Join(logFilePath, logFileName)
	// 写入文件
	src, err := os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		fmt.Println("Open log file err:", err)
	}
	// 实例化
	Log = logrus.New()
	//设置日志级别
	Log.SetLevel(logrus.DebugLevel)
	logrus.SetReportCaller(true)
	//设置输出
	writers := []io.Writer{
		src,
		os.Stdout}
	// Log.Out = src
	fileAndStdoutWriter := io.MultiWriter(writers...)
	if err == nil {
		Log.SetOutput(fileAndStdoutWriter)
	} else {
		Log.Info("failed to log to file.")
	}

	// 设置 rotatelogs
	logWriter, err := rotatelogs.New(
		rotateLogsFile, // 分割后的文件名称
		rotatelogs.WithMaxAge(7*24*time.Hour),     // 设置最大保存时间(7天)
		rotatelogs.WithRotationTime(24*time.Hour), // 设置日志切割时间间隔(1天)
	)
	if err != nil {
		fmt.Printf("Failed to create rotatelogs: %s", err)
	}

	writeMap := lfshook.WriterMap{
		logrus.InfoLevel:  logWriter,
		logrus.FatalLevel: logWriter,
		logrus.DebugLevel: logWriter,
		logrus.WarnLevel:  logWriter,
		logrus.ErrorLevel: logWriter,
		logrus.PanicLevel: logWriter,
	}

	Log.AddHook(lfshook.NewHook(writeMap, &logrus.JSONFormatter{
		TimestampFormat: "2006-01-02 15:04:05.000",
	}))

	return func(c *gin.Context) {
		startTime := time.Now()               //开始时间
		c.Next()                              //处理请求
		endTime := time.Now()                 //结束时间
		latencyTime := endTime.Sub(startTime) // 执行时间
		reqMethod := c.Request.Method         //请求方式
		reqUrl := c.Request.RequestURI        //请求路由
		statusCode := c.Writer.Status()       //状态码
		remoteIP, _ := c.RemoteIP()           //请求IP
		// 日志格式
		Log.WithFields(logrus.Fields{
			"status_code":  statusCode,
			"latency_time": latencyTime,
			"remote_ip":    remoteIP,
			"req_method":   reqMethod,
			"req_uri":      reqUrl,
		}).Info()
	}
}

测试 gin 框架下使用

package main

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

func main() {
	router := gin.Default()
	router.Use(log.LoggerMiddleware()) // 日志中间件
	router.GET("/", func(c *gin.Context) {
		c.String(http.StatusOK, "Hello World")
		log.Log.Info("测试")
	})
	router.Run(":8080")

}

3. 常规使用场景下

很多情况下,自己需要写一些小工具,也需要去记录日志,为此,准备了如下模块:

测试

package main

import (
	"fmt"
	"time"

	rotatelogs "github.com/lestrrat-go/file-rotatelogs"
	log "github.com/sirupsen/logrus"
)

func init() {
	serverName := "server"
	path := serverName+".log"
	rotateLogsFile := serverName+".%Y%m%d%H%M.log" //【测试时】
	// 下面配置日志每隔 7 天轮转一个新文件,保留最近 30 天的日志文件,多余的自动清理掉
	writer, _ := rotatelogs.New(
		rotateLogsFile,
		rotatelogs.WithLinkName(path),  // 为最新的日志建立软连接
		rotatelogs.WithMaxAge(time.Duration(30)*time.Second),  // 【测试时】 设置文件清理前的最长保存时间
		rotatelogs.WithRotationTime(time.Duration(10)*time.Second),  // 【测试时】 设置日志分割的时间,隔多久分割一次
	)
	log.SetOutput(writer)
	//log.SetFormatter(&log.JSONFormatter{})
}

func main() {
	for {
		log.Info("hello, world!")
		time.Sleep(time.Duration(2) * time.Second)
		fmt.Println(time.Now().Round(time.Second), "写入日志")
	}
}

后面开发小工具时候都可以直接拿来用

package main

import (
	"fmt"
	"time"

	rotatelogs "github.com/lestrrat-go/file-rotatelogs"
	log "github.com/sirupsen/logrus"
)

func init() {
	serverName := "server"
	path := serverName + ".log"
	rotateLogsFile := serverName+".%Y%m%d.log"
	// 下面配置日志每隔 7 天轮转一个新文件,保留最近 30 天的日志文件,多余的自动清理掉
	writer, _ := rotatelogs.New(
		rotateLogsFile,
		rotatelogs.WithLinkName(path), // 为最新的日志建立软连接
		rotatelogs.WithMaxAge(time.Hour*24*30),  // 设置文件清理前的最长保存时间
		rotatelogs.WithRotationTime(time.Hour*24*7),  // 设置日志分割的时间,隔多久分割一次
	)
	log.SetOutput(writer)
	//log.SetFormatter(&log.JSONFormatter{})
}

func main() {
	for {
		log.Info("hello, world!")
		time.Sleep(time.Duration(2) * time.Second)
		fmt.Println(time.Now().Round(time.Second), "写入日志")
	}
}

/*************************************************************/

Go软件包      

需要引入的软件包如下:

"github.com/sirupsen/logrus"

"github.com/lestrrat-go/file-rotatelogs"

"github.com/rifflock/lfshook"

其中 logrus 是基本的软件包,可以提供多种级别日志接口,可以通过其hook机制,将日志进行分发,定制文件输出格式等。

通过file-rotatelogs包,可以对本地日志文件进行分割,可以按照时间,也可以按照文件大小来实现。lfshook是专门为logrus

定制的本地文件系统钩子,帮助开发者直接把日志写入到本地文件系统的文件中。

另外,还需要几个辅助包,否则编译不通过。

"github.com/lestrrat-go/strftime"

"github.com/pkg/errors"

"golang.org/x/sys"

循环日志示例代码:

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

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

package main

import (

    "fmt"

    "path"

    "os"

    "bufio"

    "time"

     

   rotatelogs "github.com/lestrrat-go/file-rotatelogs"  /* 引入日志回滚功能 */

    "github.com/rifflock/lfshook"   /* logrus本地文件系统钩子 */

    "github.com/sirupsen/logrus"    /* logrus日志包 */

)

/* 定义日志级别 */

const LOG_TRACE = 0

const LOG_DEBUG = 1

const LOG_INFO  = 2

const LOG_WARN  = 3

const LOG_ERROR = 4

const LOG_FATAL = 5

const LOG_PANIC = 6

/* 创建logrus日志实例 */

var Logger = logrus.New()

/* 使用闭包特性,初始化带回滚功能的logrus日志环境 */

func LoggerToFile() func(int, ...interface{}){   /* 日志路径和名称 */

    logFilePath := "/home/goproject/log"

    logFileName := "helloworld"

    partFileName := path.Join(logFilePath, logFileName)

  /* 禁止日志打印到标准输出stdout */

    devnull, err := os.OpenFile(os.DevNull, os.O_APPEND|os.O_WRONLY, os.ModeAppend)

    if err != nil {

        fmt.Printf("LoggerToFile open os.DevNull failed: ", err)

    }

    writernull := bufio.NewWriter(devnull)

    Logger.SetOutput(writernull)

   /* 设置默认日志级别为 INFO */

    Logger.SetLevel(logrus.InfoLevel)

   /* 创建日志回滚实例,日志名称格式,日志回滚模式(日志每20M回滚,保留10各日志文件) */

    logWriter, err := rotatelogs.New(

        partFileName+".%Y%m%d.log",

        rotatelogs.WithLinkName(logFileName+".log"),  /* 链接文件,链接到当前实际的日志文件  */

        rotatelogs.WithRotationSize(20*1024*1024),

        rotatelogs.WithRotationCount(10),

    )

   /* 日志输出到本地文件系统,不同级别都输出到相同的日志中 */

    writeMap := lfshook.WriterMap{

        logrus.InfoLevel:  logWriter,

        logrus.FatalLevel: logWriter,

        logrus.DebugLevel: logWriter,

        logrus.WarnLevel:  logWriter,

        logrus.ErrorLevel: logWriter,

        logrus.PanicLevel: logWriter,

    }

   /* 创建新的lfs钩子 */

    lfHook := lfshook.NewHook(writeMap, &logrus.JSONFormatter{

        TimestampFormat:"2006-01-02 15:04:05",

    })

   /* logrus实例添加lfshook钩子 */

    Logger.AddHook(lfHook)

   /* 返回日志函数实例,这里可以根据level参数,实现不同级别的日志输出控制 */

    return func(level int, args ...interface{}) {

        loginfo := fmt.Sprintf("%v", args)

        Logger.WithFields(logrus.Fields{

            "module""helloworld",

        }).Info(loginfo)

    }

}

/* 创建一个日志函数实例(闭包) */

var TestLog = LoggerToFile()

func main() {

    TestLog(LOG_DEBUG, "Hello, World!")

    fmt.Println("Hello, World!")

     

    count := 0

    for {

        TestLog(LOG_INFO, "No.", count, "sleep 2s......")

        fmt.Println("No.", count, "sleep 20s......")

        count++

        time.Sleep(2*time.Second)

    }

}

日志文件:

实际日志输出到/home/goproject/log目录下,日志文件名为helloworld.YYYYMMDD.log形式;helloworld.log为当前日志文件的链接

日志内容:

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

梅坞茶坊

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

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

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

打赏作者

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

抵扣说明:

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

余额充值