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内置了两种日志格式,JSONFormatter和TextFormatter,如果这两个格式不满足需求,可以自己动手实现接口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
}
每一个属性的作用如下:
-
Out io.Writer:指定日志输出的目的地。可以将日志写入文件、终端、网络等不同的io.Writer实例。默认为os.Stderr,即标准错误输出。 -
Hooks LevelHooks:用于将钩子函数附加到日志记录器,根据不同的日志级别和日志条目触发事件。可以用于执行自定义的逻辑,如将错误发送到错误跟踪服务、统计数据等。 -
Formatter Formatter:指定日志的格式化方式,可以是TextFormatter或JSONFormatter等。默认使用TextFormatter,可以自定义实现符合Formatter接口的格式化器。 -
ReportCaller bool:控制是否记录调用者信息(文件名、函数名等)。默认为false,即不记录调用者信息。 -
Level Level:设置日志记录的级别,常见的有DebugLevel、InfoLevel、WarnLevel、ErrorLevel、FatalLevel和PanicLevel。只有达到指定级别或更高级别的日志才会被记录。 -
mu MutexWrap:用于保护并发写入日志的互斥锁。确保多个协程同时写入日志时不会产生竞态条件。 -
entryPool sync.Pool:用于从池中复用日志条目的实例,避免频繁地创建和销毁日志条目,提高性能。 -
ExitFunc exitFunc:用于退出应用程序的函数,如果发生致命错误,日志记录器可以调用该函数进行退出。默认为os.Exit()。 -
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
}