日志打印| 青训营

86 阅读4分钟

1 标准日志库

golang内置了log包,通过调用log包的函数,可以实现简单的日志打印功能。log包还定义了Logger类型,提供了一些格式化输出的方法。也提供了一个预定义的“标准”logger,可以通过调用函数Print(Print|Printf|Println)、Fatal(Fatal|Fatalf|Fatalln)、和Panic(Panic|Panicf|Panicln)来使用,比自行创建一个logger对象更容易使用,它们会将日志信息打印到终端界面。 在实现的过程中,想设置不同的日志级别以及将日志存储在文件中,使用标准日志库比较麻烦,所以采用封装好的日志框架。

2 日志框架logrus

2.1简介

logrus功能强大,性能高效,而且具有高度灵活性,提供了自定义插件的功能。logrus完全兼容golang标准库日志模块:logrus拥有六种日志级别:debug、info、warn、error、fatal和panic,这是golang标准库日志模块的API的超集;而且允许使用者通过hook的方式将日志分发到任意地方;同时logrus内置了两种日志格式,JSONFormatterTextFormatter,如果这两个格式不满足需求,可以自己动手实现接口Formatter,来定义自己的日志格式。

2.2 logrus配置

查看以下logger的定义:

type Logger struct {
	Out io.Writer

	Hooks LevelHooks

	Formatter Formatter

	ReportCaller bool

	Level Level

	mu MutexWrap
    
	entryPool sync.Pool

	ExitFunc exitFunc

	BufferPool BufferPool
}

每一个属性的作用如下:

  1. Out io.Writer:指定日志输出的目的地。可以将日志写入文件、终端、网络等不同的 io.Writer 实例。默认为 os.Stderr,即标准错误输出。

  2. Hooks LevelHooks:用于将钩子函数附加到日志记录器,根据不同的日志级别和日志条目触发事件。可以用于执行自定义的逻辑,如将错误发送到错误跟踪服务、统计数据等。

  3. Formatter Formatter:指定日志的格式化方式,可以是 TextFormatterJSONFormatter 等。默认使用 TextFormatter,可以自定义实现符合 Formatter 接口的格式化器。

  4. ReportCaller bool:控制是否记录调用者信息(文件名、函数名等)。默认为 false,即不记录调用者信息。

  5. Level Level:设置日志记录的级别,常见的有 DebugLevelInfoLevelWarnLevelErrorLevelFatalLevelPanicLevel。只有达到指定级别或更高级别的日志才会被记录。

  6. mu MutexWrap:用于保护并发写入日志的互斥锁。确保多个协程同时写入日志时不会产生竞态条件。

  7. entryPool sync.Pool:用于从池中复用日志条目的实例,避免频繁地创建和销毁日志条目,提高性能。

  8. ExitFunc exitFunc:用于退出应用程序的函数,如果发生致命错误,日志记录器可以调用该函数进行退出。默认为 os.Exit()

  9. BufferPool BufferPool:用于格式化日志的缓冲池,如果为 nil,将使用默认的全局缓冲池。

2.3 具体实现

只简单的实现了日志文件的输出和,日志的级别,其中日志文件的输出有日志本地文件分割设置

var Log *logrus.Logger

func init() {
	// 如果实例存在则不用新建
	if Log != nil {
		fileName := getFileDir()
		witter := rotateLog(fileName)
		Log.Out = witter
		return
	}

	logger := logrus.New()
	fileName := getFileDir()
	witter := rotateLog(fileName)

	// 设置输出文件
	logger.Out = witter
	// 设置日志级别
	logger.SetLevel(logrus.DebugLevel)

	Log = logger
}

3 日志本地文件分割

随着访问量的增加,单个日志文件会越来越大,所以会把日志文件分割成多个文件,但是logrus本身是不带有日志本地分割功能的,但是可以通过rotatelogs进行日志本地文件分割。每次写入日志的日后,rotatelogs会判断日志是否需要进行切分

3.1日志配置

rotatelogs已经帮我们封装好了,我们看一下包下的属性有哪些(以下截取代码的一部分)

func New(p string, options ...Option) (*RotateLogs, error) {
	globPattern := p
	for _, re := range patternConversionRegexps {
		globPattern = re.ReplaceAllString(globPattern, "*")
	}

	pattern, err := strftime.New(p)
	if err != nil {
		return nil, errors.Wrap(err, `invalid strftime pattern`)
	}

	var clock Clock = Local
	rotationTime := 24 * time.Hour
	var rotationSize int64
	var rotationCount uint
	var linkName string
	var maxAge time.Duration
	var handler Handler
	var forceNewFile bool

可以看到我们能设置的属性有以下几个:

  • rotationTime:日志文件轮转的时间间隔。默认为 24 小时。

  • rotationSize:单个日志文件的最大大小。如果文件大小超过此值,会生成新的日志文件。默认为 0,表示不限制文件大小。

  • rotationCount:保留的日志文件数量。当日志文件超过此数量时,最旧的日志文件会被删除。默认为 0,表示不限制文件数量。

  • linkName:链接文件的名称,链接文件会指向最新的日志文件。默认为空。

  • maxAge:日志文件保留的最长时间。超过该时间的日志文件会被删除。默认为 0,表示不限制保留时间。

  • handler:日志写入的处理器。默认为 nil,表示使用默认的日志写入处理器。

  • forceNewFile:强制生成新的日志文件。默认为 false,表示不强制生成

3.2 具体实现

在实现中,设置日志的最长保留时间为12小时,每过三个小时会对日志进行分割成新的日志文件

func rotateLog(fileName string) *rotatelogs.RotateLogs {
	witter, _ := rotatelogs.New(
		fileName+"%H%M",
		rotatelogs.WithLinkName(fileName),
		// 日志最长保留时间
		rotatelogs.WithMaxAge(time.Duration(12)*time.Hour),
		// 日志轮转的时间间隔
		rotatelogs.WithRotationTime(time.Duration(3)*time.Hour),
	)

	return witter
}