1. 前言
1.1 为什么需要业务监控?
所有的软件或者系统,都无法保证100%的稳定运行,由于各种原因都会导致异常故障,如果发现太晚延误了解决问题,则会扩大线上影响。从故障出现到问题修复之间的每一分钟都是值得优化的,监控的目的就是为了快速发现问题,协助开发或者产品分析业务状态。
项目中一般常用的监控有基础设施监控、用户行为监控、前端监控、后台服务监控,这些监控的衡量指标缺乏业务语意,无法直观地体现出来,比如当日下单平均响应时长、成功率,比如有哪些文章拉取失败了,失败的文章请求量有多少等。
1.2 为什么需要开发自己来做监控?
- 最早发现问题:开发是需求实现的第一线角色,编码实现逻辑由开发同学掌控,只有开发能最早地发现可能存在的问题,由开发设计监控,能够最快地发现问题。
- 成本低,效率高: 如果由数据分析同学来实现,则需要数据同学也对需求实现充分理解,然后与开发约定上报规则,开发加上埋点,数据同学通过数据流转,聚合数据后才能进行分析展示。沟通和研发成本都是比较高的。而如果由研发自己完成监控,则可以省去沟通的成本和数据流转的成本。
1.3 业务监控关注什么?
- 适用的场景有哪些?
- 一些业务状态分析:下单、搜索等关键路径的行为访问分析等。
- 错误拆解分析: 对一些接口的错误进行统计分析。
- 接口成功率监控等手段不能监控的地方。
- 如何做?
- 不要影响业务流程,旁路完成。
- 每一个监控是带有目的的,实现前需要想好以下两个问题:想要发现什么问题?需要哪些指标?
2. 案例展示
2.1 主题
文章拉取失败统计与分析
2.2 背景,为什么做?
项目中的文章服务由第三方合作伙伴提供,业务中保存了许多的文章ID,文章的内容需要调用合作伙伴的接口来获得,现在需要切换为带鉴权的新接口拉取,没有加入白名单的文章ID会拉取失败。
由于历史原因,有大量场景配置或者使用到了文章,现在把最重要的场景的文章提供给合作伙伴进行了加白。
那么,这里我们可能会遇到这些问题:
- 没有加白的文章里面是否有大量请求的(说明比较重要的)文章? 这些文章应该加白。
- 请求失败的文章里面,是否包含了已下架的文章? 这些文章应该删除配置。
2.2 需要统计哪些指标?
- 文章ID: 以文章id为数据分析维度。
- 失败次数:失败次数越多,说明越多用户请求,是有价值的文章。
- 文章是否存在:文章已经下架,则应该取消配置。
2.3 上报与报表
- 在文章列表拉取接口,检查请求参数与返回内容,将没有拉取到的文章打印到日志。
- 文章是否存在,接口不带有这些信息,则由报表分析后人工判断top文章。
// LogContentFail 文章拉取失败上报
func LogContentFail(ctx context.Context, docId string, title string, err error) {
info := Monitor{Str1: docId, Str2: title, Num1: 1}
if err != nil {
info.Str3 = err.Error()
}
Log(ctx, KeyContentFail, &info)
}
2.4 发现的问题
- 发现了大量已下架文章 文章已下架.png
- 发现数篇需要授权的文章没有被授权 授权遗漏.png
3. 具体实现
3.1 日志指定关键词
在日志库中新增了一个接口,支持指定关键词,在日志中打印note_keyword字段。
代码语言:txt复制// Log 答应关键词为noteKey的日志
func Log(ctx context.Context, noteKey string, msg *Monitor) {
if len(noteKey) == 0 || msg == nil || len(msg.Str1) == 0 {
log.Fatalf(ctx, "monitor log fail, noteKey or msg is empty.")
return
}
jsonStr, err := json.Marshal(msg)
if err != nil {
log.Fatalf(ctx, "monitor log fail, msg marshal fail, err: %s", err.Error())
return
}
log.InfoK(ctx, noteKey, string(jsonStr))
}
// InfoK logs to INFO log with custom keyword.
func (l *logger) InfoK(ctx context.Context, keyword string, args ...interface{}) {
l.logK(ctx, LevelInfo, keyword, "", args...)
}
3.2 固定一套日志结构
固定几个字符串类型字段,和几个数字类型字段,这样的好处是所有的业务监控可以复用这样的结构,方便CLS索引字段的设置。
代码语言:txt复制// Monitor 监控日志
type Monitor struct {
Str1 string `json:"str1"`
Str2 string `json:"str2"`
Str3 string `json:"str3"`
Str4 string `json:"str4"`
Str5 string `json:"str5"`
Str6 string `json:"str6"`
Num1 int `json:"num1"`
Num2 int `json:"num2"`
Num3 int `json:"num3"`
}
3.3 CLS日志加工分流
通过数据加工,把监控类型的日志转存到指定的日志集,将监控类日志独立存储方便单独设置存储规则,并且检索会更快。将msg中字段带上msg_
展开到外层,带上前缀可以有效避免msg中的字段与外层其他字段重名,同时方便检索分析。
- 将带有非常规日志的关键词丢弃
- 将msg字段按json格式展开到第一层
log_drop(regex_match(v("note_keyword"),regex="deug|info|error|fatal|Info|Error|Fatal|Debug"))
ext_json("msg", prefix="msg_")
3.4 打印业务日志
代码语言:txt复制// LogContentFail 文章拉取失败上报
func LogContentFail(ctx context.Context, docId string, title string, err error) {
info := Monitor{Str1: docId, Str2: title, Num1: 1}
if err != nil {
info.Str3 = err.Error()
}
Log(ctx, KeyContentFail, &info)
}
3.5 报表分析
eg: 文章拉取失败统计
- 新建仪表盘 新建仪表盘.png
- 添加图表 新建图表.png
- 编辑规则语句并应用即可 编辑图表.png
note_keyword:"Monitor_Content_Fail" | select msg_str1 as docid, sum(msg_num1) as fail_count group by docid order by fail_count desc