欢迎关注「全栈工程师修炼指南」公众号
点击 下方卡片 即可关注我哟!
设为「星标⭐」每天带你 基础入门 到 进阶实践 再到 放弃学习!
“ 花开堪折直须折,莫待无花空折枝。 ”
作者主页:[ https://www.weiyigeek.top ]
博客:[ https://blog.weiyigeek.top ]
作者安全运维学习答疑交流群:请关注公众号回复【学习交流群】
文章目录:
0x00 前言简述
sirupsen/logrus 模块 - 日志记录
lestrrat-go/file-rotatelogs 模块 - 日志分隔
rifflock/lfshook 模块 - 本地文件系统挂钩
描述: 日志是现代编程中必不可少的手段,除了处理基本的错误之外,通过记录日志,也可以帮助我们完成一些基本的功能,比如开发及测试期间的Debug,记录请求的上下文,排除故障原因,数据统计及分析等等。
所以本节将主要分享 Go 语言中常用的日志记录库(包)即相关依赖包的下载使用,当前Go语言常用的日志库模块有 logrus , Zerolog, Zap, and Apex
等。
描述: Logrus 是一个结构化、可插拔的Go日志库,并且完全兼容官方的log库,具有很强的灵活性,有 TEXT 和 JSON 两种可选的日志输出格式,同时还提供了自定义格式的插件功能,支持 Feild 机制和可扩展的 Hook 机制。
项目地址: https://github.com/sirupsen/logrus
项目文档: https://pkg.go.dev/github.com/sirupsen/logrus
logrus 不推荐使用冗长的消息来记录运行信息,它推荐使用Fields来进行精细化的结构化的信息记录,例如:
# 不建议此种方式
log.Fatalf("Failed to send event %s to topic %s with key %d", event, topic, key)
# 鼓励使用以下方式替代
log.WithFields(log.Fields{
"event": event,
"topic": topic,
"key": key,
}).Fatal("Failed to send event")
日志等级: 其中 Trace 优先级最低,Panic 优先级最高。
Panic:恐慌级别,也是最高级别的日志,会打印出错误堆栈。
Fatal:致命错误,输出日志后,执行 exit(1) 退出
Error:错误日志,必须记录与跟踪的日志
Warn:警告日志,主要记录需要提醒开发者的日志
Info:主要是提供一些必要的日志信息,在业务出现问题时,可以结合error日志快速定位问题,一般会默认使用该级别的日志。
Debug:调试信息,方便开发测试阶段的问题定位
Trace:比 debug 级别还低,一般很少用。
log.Trace("Something very low level.")
log.Debug("Useful debugging information.")
log.Info("Something noteworthy happened!")
log.Warn("You should probably take a look at this.")
log.Error("Something failed but I'm not quitting.")
log.Fatal("Bye.") // Calls os.Exit(1) after logging
log.Panic("I'm bailing.") // Calls panic() after logging
默认字段:
除了使用WithField或WithFields添加的字段外,还会自动将一些字段添加到所有日志事件中:
time : The timestamp when the entry was created.
msg :The logging message passed to {Info,Warn,Error,Fatal,Panic} after the AddFields call. E.g. Failed to send event.
level :The logging level. E.g. info.
日志格式
TEXTFormatter 属性说明:https://pkg.go.dev/github.com/sirupsen/logrus#TEXTFormatter (支持tty终端颜色显示)
JSONFormatter 属性说明: https://pkg.go.dev/github.com/sirupsen/logrus#JSONFormatter
模块下载:
go get -v -u github.com/sirupsen/logrus
示例演示:
示例1.简单使用快速上手
package main
import (
"os"
log "github.com/sirupsen/logrus"
)
func main() {
// logrus 标准库使用输出方法
log.Println("标准输出!")
// logrus 级别使用输出方法
log.Traceln("Trace 级别")
log.Infoln("Info 级别")
log.Errorln("Error 级别")
// 注意Fatal和Panic类型的日志会中断程序的运行。
// log.Panicln("Panic 级别")
// logrus 输出日志时可以附带参数
log.WithFields(log.Fields{
"flag": true,
"name": "WeiyiGeek",
"site": "https://blog.weiyigeek.top",
}).Info("Info 级别信息")
// logrus 日志输出的格式及颜色控制
log.SetFormatter(&log.JSONFormatter{
TimestampFormat: "2006-01-02 15:04:05",
})
log.SetFormatter(&log.TextFormatter{
TimestampFormat: "2006-01-02 15:04:05",
ForceColors: true,
FullTimestamp: true,
})
// 设置标准记录器输出。
log.SetOutput(os.Stdout)
// 设置最低的日志等级
log.SetLevel(log.WarnLevel)
}
执行结果:
$ go run .\logging.go
time="2023-04-14T13:18:10+08:00" level=info msg="标准输出!"
time="2023-04-14T13:18:10+08:00" level=info msg="Info 级别"
time="2023-04-14T13:18:10+08:00" level=error msg="Error 级别"
time="2023-04-14T13:18:10+08:00" level=info msg="Info 级别信息" flag=true name=WeiyiGeek site="https://blog.weiyigeek.top"
示例2.同时将日志输出到终端和日志文件中。
package main
import (
"io"
"os"
"path"
"github.com/sirupsen/logrus"
)
// 实现输出日志文件到多个位置
func main() {
// 实例化logrus
var logger = logrus.New()
// 格式设置
logger.SetFormatter(&logrus.JSONFormatter{
TimestampFormat: "2006-01-02 15:04:05",
})
// 显示行号等信息
logger.SetReportCaller(true)
// 日志目录与名称
logPath := "D:/Study/Project/Go/hello-gin/logs"
logName := "app"
logFile := path.Join(logPath, logName+".log")
// 创建并打开文件
file, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, os.ModeAppend)
fileAndStdout := io.MultiWriter(file, os.Stdout)
if err == nil {
logger.SetOutput(fileAndStdout)
} else {
logger.Errorf("Failed to log to file, using default stderr", err)
}
defer file.Close()
// 设置最低显示日志
logger.SetLevel(logrus.DebugLevel)
// 各类别日志输出
logger.Debugln("我是Debug信息!")
logger.Info("我是普通信息!")
logger.Errorln("我是Error信息!")
// 生产环境中实践
logger.WithFields(logrus.Fields{
"request_id": "887B4F43-D330-5213-94DF-1B14FA3388C",
"ip": "110.110.110.110",
"user_id": 1001,
}).Info("Info Level")
// 或者
logger.WithField("request_id", "887B4F43-D330-5213-94DF-1B14FA3388C").WithField("ip", "110.110.110.110").WithField("user_id", 1001).Info("Info Level");
}
执行结果:
$ go run .\logging.go
{"level":"debug","msg":"我是Debug信息!","time":"2023-04-14 14:39:44"}
{"level":"info","msg":"我是普通信息!","time":"2023-04-14 14:39:44"}
{"level":"error","msg":"我是Error信息!","time":"2023-04-14 14:39:44"}
温馨提示:我们还可以创建一个logrus.Entry实例,为这个实例设置默认Fields,把logrus.Entry实例设置到记录器Logger,再记录日志时每次都会附带上这些默认的字段。
logger := log.WithFields(log.Fields{"request_id": "887B4F43-D330-5213-94DF-1B14FA3388C"})
logger.Info("Info Level") // 后续输出也会记录 request_id
logger.Warn("Warn Levle")
温馨提示:默认情况下,logrus的api都是线程安全的,其内部通过互斥锁来保护并发写。互斥锁工作于调用hooks或者写日志的时候。如果不需要锁,可以调用logger.SetNoLock()来关闭之,通常在没有设置hook,或者所有的hook都是线程安全的实现,再或者写日志到logger.Out已经是线程安全的了
。
扩展学习:
自定义 HOOK :
logrus最令人心动的功能就是其可扩展的HOOK机制了,通过在初始化时为logrus添加hook,便可以实现各种扩展功能.
logrus的hook接口定义如下,其原理是每此写入日志时拦截修改logrus.Entry.
// logrus在记录Levels()返回的日志级别的消息时会触发HOOK,
// 按照Fire方法定义的内容修改logrus.Entry.
type Hook interface {
Levels() []Level
Fire(*Entry) error
}
例如,自定义一个DefaultFieldHook,它在所有级别的日志消息中加入默认字段appName="weiyigeek".
type DefaultFieldHook struct {
}
func (hook *DefaultFieldHook) Fire(entry *log.Entry) error {
entry.Data["appName"] = "weiyigeek"
return nil
}
func (hook *DefaultFieldHook) Levels() []log.Level {
return log.AllLevels
}
偷偷的告诉你哟?【极客全栈修炼】微信小程序已经上线了,
可直接在微信里面直接浏览博主博客了哟,后续将上线更多有趣的小工具。
lestrrat-go/file-rotatelogs 模块 - 日志分隔
描述: 由于logrus并不自带日志本地文件分割功能,所以我们使用file-rotatelogs模块进行分隔,它是提供一个 io.Writer 那定期转录文件在应用程序中, 注意 file-rotatelogs 项目已于 Jul 19, 2021 停止更新维护。
官网地址: https://github.com/lestrrat-go/file-rotatelogs
模块属性
Pattern : 日志文件名称模式。
WithLinkName("/path/to/log"):实际日志文件的符号链接所在的路径。
Main log file name -> Link name -> Linked path
/path/to/log.%Y%m%d -> /path/to/log -> log.YYYYMMDD
WithRotationTime(24*time.Hour):默认24小时, 文件旋转之间的间隔。
WithMaxAge(-1):默认每7天清除之前旧日志。
WithRotationCount(7): 设置应保留文件的数量。
ForceNewFile() :确保每次调用new()时都会创建一个新文件,如果基本文件名已经存在,则执行隐式旋转。
温馨提示: WithMaxAge 和 WithRotationCount 二者只能设置一个。
示例演示:
// 使用rotatelogs完成日志分割、日志定期清理、生成软链文件指向最新日志
InfologWriter, err := rotatelogs.New(
// 分割后的文件名称
infologFile+".%Y%m%d",
// 生成软链,指向最新日志文件
rotatelogs.WithLinkName(infologFile),
// 设置最大保存时间(7天)
rotatelogs.WithMaxAge(7*24*time.Hour),
// 设置日志切割时间间隔(1天)
rotatelogs.WithRotationTime(24*time.Hour),
)
if err != nil {
fmt.Println("Failed to create logfile" + errorlogFile)
panic(err)
}
描述: 有时开发人员喜欢直接写入文件系统上的文件, 它是logrus的一个钩子,旨在允许用户这样做, 日志级别在钩子的实例化时是动态的,因此它能够在某些或所有级别进行日志记录。
项目地址: https://github.com/rifflock/lfshook
亲,文章就要看完了,不关注一下作者吗?
综合演示:
package main
import (
"io"
"os"
"path"
"time"
rotatelogs "github.com/lestrrat-go/file-rotatelogs"
"github.com/rifflock/lfshook"
"github.com/sirupsen/logrus"
)
func NewLogger() *logrus.Logger {
var logger = logrus.New()
// 日志目录与名称
logPath := "D:/Study/Project/Go/hello-gin/logs"
logName := "app"
logFile := path.Join(logPath, logName)
// 创建并打开文件
file, err := os.OpenFile(logFile+".log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, os.ModeAppend)
fileAndStdout := io.MultiWriter(file, os.Stdout)
if err == nil {
logger.SetOutput(fileAndStdout)
} else {
logger.Errorf("Failed to log to file, using default stderr", err)
}
// defer file.Close()
// 分别针对INFO/DEBUG/WARN/ERROR等日志等级,进行日志分割、日志定期清理、生成软链文件指向最新日志
InfologWriter, _ := rotatelogs.New(
logFile+"-info.%Y%m%d",
rotatelogs.WithLinkName(logFile+".log"),
rotatelogs.WithMaxAge(7*time.Duration(86400)*time.Second),
rotatelogs.WithRotationTime(time.Duration(86400)*time.Second),
)
DebuglogWriter, _ := rotatelogs.New(
logFile+"-debug.%Y%m%d",
rotatelogs.WithMaxAge(7*time.Duration(86400)*time.Second),
rotatelogs.WithRotationTime(time.Duration(86400)*time.Second),
)
WarnlogWriter, _ := rotatelogs.New(
logFile+"-warn.%Y%m%d",
rotatelogs.WithMaxAge(7*time.Duration(86400)*time.Second),
rotatelogs.WithRotationTime(time.Duration(86400)*time.Second),
)
ErrorlogWriter, _ := rotatelogs.New(
logFile+"-error.%Y%m%d",
rotatelogs.WithMaxAge(7*time.Duration(86400)*time.Second),
rotatelogs.WithRotationTime(time.Duration(86400)*time.Second),
)
// 使用lfshook决定哪些级别的日志可以使用rotatelogs的切割设置,并决定输出格式(TEXT / JSON)。
lfHook := lfshook.NewHook(lfshook.WriterMap{
logrus.DebugLevel: DebuglogWriter,
logrus.InfoLevel: InfologWriter,
logrus.WarnLevel: WarnlogWriter,
logrus.ErrorLevel: ErrorlogWriter,
}, &logrus.JSONFormatter{
TimestampFormat: "2006-01-02 15:04:05",
})
// 为 logurs 新增 Hook
logger.AddHook(lfHook)
// 日志输出格式设置
logger.SetFormatter(&logrus.JSONFormatter{
TimestampFormat: "2006-01-02 15:04:05",
})
// 设置最低显示日志
logger.SetLevel(logrus.DebugLevel)
return logger
}
func main() {
log := NewLogger()
log.Debug("Debug - Useful debugging information.")
log.Info("Info - Something noteworthy happened!")
log.Warn("Warn - You should probably take a look at this.")
log.Error("Error - Something failed but I'm not quitting.")
}
执行结果:
$ go run .\logrotate.go
{"level":"debug","msg":"Debug - Useful debugging information.","time":"2023-04-14 16:04:25"}
failed to rotate: failed to rename new symlink: rename D:/Study/Project/Go/hello-gin/logs/app-info.20230414_symlink D:/Study/Project/Go/hello-gin/logs/app.log: Access is denied.
{"level":"info","msg":"Info - Something noteworthy happened!","time":"2023-04-14 16:04:25"}
{"level":"warning","msg":"Warn - You should probably take a look at this.","time":"2023-04-14 16:04:25"}
{"level":"error","msg":"Error - Something failed but I'm not quitting.","time":"2023-04-14 16:04:25"}
温馨提示: 在执行时如果出现 failed to rotate: failed to create new symlink: symlink m A required privilege is not held by the client.
表示执行的终端没有管理员权限,如果你是WINDOWS此处你需要在开始菜单中右键以管理员运行Shell终端或者在Powershell中执行Start-Process powershell -Verb runAs
命令。
本文至此完毕,更多技术文章,尽情等待下篇好文!
原文地址: https://blog.weiyigeek.top/2023/4-15-729.html
如果此篇文章对你有帮助,请你将它分享给更多的人!
学习书籍推荐 往期发布文章
公众号回复【0008】获取【Ubuntu22.04安装与加固建脚本】
公众号回复【10001】获取【WinServer安全加固脚本】
公众号回复【1000】获取【PowerShell操作FTP脚本】
公众号回复【0015】获取【Jenkins学习之路汇总】
热文推荐
容灾恢复 | 记一次K8S集群中etcd数据快照的备份恢复实践
企业实践 | 如何从VMWare ESXi Shell中挂载以及拷贝NTFS或者FAT32分区格式的USB闪存驱动器
奇技淫巧 | 快速提升网站权重,巧用GPT4自动化大数据生成关键字指数进行SEO排名优化
网安等保-国产Linux操作系统银河麒麟KylinOS-V10SP3常规配置、系统优化与安全加固基线实践文档
欢迎长按(扫描)二维码 获取更多渠道哟!
欢迎关注 【全栈工程师修炼指南】(^U^)ノ~YO
== 全栈工程师修炼指南 ==
微信沟通交流: weiyigeeker
关注回复【学习交流群】即可加入【安全运维沟通交流小群】
温馨提示: 由于作者水平有限,本章错漏缺点在所难免,希望读者批评指正,若有问题或建议请在文章末尾留下您宝贵的经验知识,或联系邮箱地址
[email protected] 或 关注公众号 [全栈工程师修炼指南] 留言。
[全栈工程师修炼指南] 关注 企业运维实践、网络安全、系统运维、应用开发、物联网实战、全栈文章,尽在博客站点,谢谢支持!
点个【 赞 + 在 】看吧!
点击【"阅读原文"】获取更多有趣的知识!