风靡全球的小说人物夏洛克·福尔摩斯,善于用观察与演绎推理和法学知识来解决问题,无论大小案件他总能快速的找到线索,并顺藤摸瓜找到幕后凶手。
而在现实的软件开发过程中,程序员会在代码里打上”线索”,在程序出现错误时,通过查看这些”线索”来快速定位到问题所在并继而解决——这些”线索”就是日志。
日志是开发为了跟踪用户行为和代码异常而打的记录,而文件日志则是指将日志存入到指定文件这一功能。
在开发环境中,我们会在程序中设置断点或打印日志的方式进行调试。有人只用设置断点的方式,也有人会结合两种方法,通过分析日志文件缩小断点范围来快速定位问题代码。但在生产环境里,我们无法设置断点来debug,只能通过日志信息来捕捉错误。
GOKU文件日志的实现考虑
golang实现的日志框架有很多,包括logrus、zap、zerolog、seelog等,其中logrus是目前go生态最流行的日志框架。logrus兼容golang标准库log,囊括了标准库的所有日志等级,并且具有可扩展的hook机制、能选择日志的格式、内置JSONFormatter和TextFormatter。当然开发者也能自己实现接口Formatter,同时还有Field机制能够进行精细化、结构化的日志记录。美中不足的是,logrus自身没有实现日志分割的功能,而是通过hook机制间接实现。
虽然logrus框架功能丰富,性能也不错,但每个项目都有自身的需求,即使是再好的日志框架也不能生搬硬套到项目里。于是GOKU在logrus框架的基础上进行二次开发,只保留基本功能。
我们比较常见的日志框架以及根据自己的需求,归纳了以下需求点:
- 日志分级
事有轻重缓急,日志信息也有重要与不重要之分。设置合理的日志等级能够帮助我们过滤不重要的日志信息,快速排查问题。
按照重要程度,将日志等级分成了五个等级:
日志等级 | 含义 |
fatal | 会导致程序退出的严重错误 |
error | 一般的错误信息 |
warn | 警告信息 |
info | 一般信息 |
debug | 调试信息 |
当文件日志设置了某个日志等级,那么程序只会输出不低于当前等级的日志信息到文件中。
在产品上线时只需要开启error等级的日志,在程序出现了不知名的错误时,则启动debug模式对程序进行排查调试,可以避免程序运行时的日志冗余。
- 日志分割
日志分割即每隔一段时间产生一个新的日志文件,防止所有日志记录全部堆积在一个日志文件里。
实行分割的好处有两个:
- 避免单个日志文件占用磁盘空间过大,查看时影响服务器IO;
- 能够根据时间间隔更快地定位日志;
常见的日志分割的周期有小时和天,而月、年可能会造成单个日志数据量过大,增加排查难度,所以一般不会使用。
- 日志定时清理
随着时间的推移,旧日志文件会越积越多,而磁盘空间有限,一旦空间消耗完,可能会导致某些应用奔溃,影响生产。
定时清理日志的优点:
- 无需手动删除旧日志文件,减少人力维护成本;
- 避免过期日志文件的堆积,既减少磁盘占用又可更快定位所需的日志文件;
文件日志的配置参数
参考功能的设计,需要有个字段来确定日志等级,同时日志文件的分割周期和保存时间也需要两个参数来配置。
接下来对文件日志功能需要配置的参数进行说明:
参数名 | 参数类型 | 参数取值范围 | 说明 |
dir | 字符串 | 任意字符串 | 日志文件的目录路径(支持绝对路径和相对路径) |
file | 字符串 | 任意字符串 | 日志文件的文件名 |
level | 枚举 | [“fatal”,”error”,”warn”,”info”,”debug”] | 日志等级 |
perio | 枚举 | [“day”,”hour”] | 日志文件的分割周期 |
expire | 整型 | 大于0的整数 | 旧日志文件的保存时间,单位为天 |
period参数:用来设置日志文件的分割周期,即每隔一段时间就生成新的日志文件,对日志记录进行分流,以此来实现日志分割功能。可以是一天生成一个新日志文件,也可以是每小时。
expire参数:设定旧日志文件的保存时间,可以设置若干天。保存时间从新日志文件生成的那一刻开始生效,旧文件名后缀加上时间戳。程序定时检查旧文件是否过期,以此达到日志定时清理的目的。
下图以文件分割周期:day,旧文件保存时间:3天为例,对日志文件的分割及清理流程进行解释说明。
文件日志的核心代码实现
日志分级
在调用打印日志接口后最终会进入到这个方法里,entry入参是日志记录的结构体变量,而transporter是日志输出器:
//Transport 能将判断日志记录entry的等级并格式化输出
func (t *Transporter) Transport(entry *Entry) error {
output := t.output
if output == nil {
return nil
}
//当配置的日志等级大于或等于日志记录的等级时进行输出
if t.Level() >= entry.Level {
//对日志记录格式化
data, err := t.formatter.Format(entry)
if err != nil {
return err
}
//输出日志记录
_, err = output.Write(data)
return err
}
return nil
}
日志定时清理
日志定时清理的逻辑很简单:获取日志目录下的旧日志文件,逐个判断是否过期。
在文件分割时会执行以下的方法:
//dropHistory 检查日志目录下的历史日志文件,若过期则删除
func (w *FileWriterByPeriod) dropHistory() {
expire := w.getExpire()
expireTime := time.Now().Add(-expire)
pathPatten := filepath.Join(w.dir, fmt.Sprintf(“%s-*”, w.file))
files, err := filepath.Glob(pathPatten)
if err == nil {
for _, f := range files {
if info, e := os.Stat(f); e == nil {
if expireTime.After(info.ModTime()) {
_ = os.Remove(f)
}
}
}
}
}
日志分割
实现日志分割的重点在于处理的时机。定义一个时间戳变量,并且设置一个定时器,定时获取当前时间戳,当新旧时间戳不一致时才进行文件处理。核心代码如下:
case <-t.C:
{
if buf.Buffered() > 0 {
buf.Flush()
tflusth.Reset(time.Second)
}
//获取新时间戳,并与旧时间戳进行比较,若不一致则进行日志分割
if lastTag != w.timeTag(time.Now()) {
//关闭旧日志文件
f.Close()
//对旧日志文件重命名
w.history(lastTag)
//创建新日志文件
fnew, tag, err := w.openFile()
if err != nil {
return
}
//保存新时间戳
lastTag = tag
f = fnew
buf.Reset(f)
go w.dropHistory()
}
}
那么时间戳是怎样的,又是如何获取的呢?
事实上,上面的时间戳是由完整时间戳格式化得到的,而具体的格式化字符串又是依据period的参数而不同。这样就能轻易获取截止到小时或天的时间戳,以此做到隔天或间隔小时生成新日志文件。
代码如下:
func (w *FileWriterByPeriod) timeTag(t time.Time) string {
w.locker.Lock()
//w.period.FormatLayout 获取period的格式化字符串
tag := t.Format(w.period.FormatLayout())
w.locker.Unlock()
return tag
}func (period LogPeriodType) FormatLayout() string {
switch period {
case PeriodHour:
{
return “2006-01-02-15”
}
case PeriodDay:
{
return “2006-01-02”
}
default:
return “2006-01-02-15”
}
}
最后
综上所述,goku网关基于logrus框架进行二次开发,对日志分割功能进行了完善,同时提供了日志定时清理、日志分级等操作,满足了goku网关文件日志打印的需求。另外,除了文件日志,goku还支持httplog、syslog、stdlog等多样的日志输出,这些离不开我们goku自己实现的日志框架,对多样的日志输出进行分流,下一篇我们再给大家介绍我们的日志框架的实现细节及相关考虑。
什么是GoKu网关?
Goku API Gateway (中文名:悟空 API 网关)是一个基于 Golang开发的微服务网关,能够实现高性能 HTTP API 转发、服务编排、多租户管理、API 访问权限控制等目的,拥有强大的自定义插件系统可以自行扩展,并且提供友好的图形化配置界面,能够快速帮助企业进行 API 服务治理、提高 API 服务的稳定性和安全性。
GoKu Github地址:https://github.com/eolinker/goku
💡关于 Eolinker
- Eolinker(Easy & Open Linker)是国内 API 接口全生命周期管理解决方案的领军者,是国内最大的在线 API 接口管理平台,也是唯一为工信部ITSS协会制定API研发管理与测试规范的企业。
- Eolinker 旗下拥有 API 研发管理、API 自动化测试、API 微服务网关、API 网络监控、API 快速生成、API 开放平台等多个标准化产品。
- Eolinker 为全球超过3万家企业提供专业的API相关解决方案,客户遍布互联网、金融、安全、人工智能、企业服务、制造业、物联网、政府等数十个行业。
📞联系我们
- 官方网站:https://eolinker.com
- 市场合作:market@eolinker.com
- 购买咨询:sales@eolinker.com
- 中国大陆支持电话:400-616-0330
- 电话接听时间:工作日 9:30-18:00
🏠部分客户
💎投资机构