巧用腾讯云CLS实现业务监控

1. 前言

1.1 为什么需要业务监控?

所有的软件或者系统,都无法保证100%的稳定运行,由于各种原因都会导致异常故障,如果发现太晚延误了解决问题,则会扩大线上影响。从故障出现到问题修复之间的每一分钟都是值得优化的,监控的目的就是为了快速发现问题,协助开发或者产品分析业务状态。

项目中一般常用的监控有基础设施监控、用户行为监控、前端监控、后台服务监控,这些监控的衡量指标缺乏业务语意,无法直观地体现出来,比如当日下单平均响应时长、成功率,比如有哪些文章拉取失败了,失败的文章请求量有多少等。

1.2 为什么需要开发自己来做监控?

  • 最早发现问题:开发是需求实现的第一线角色,编码实现逻辑由开发同学掌控,只有开发能最早地发现可能存在的问题,由开发设计监控,能够最快地发现问题。
  • 成本低,效率高: 如果由数据分析同学来实现,则需要数据同学也对需求实现充分理解,然后与开发约定上报规则,开发加上埋点,数据同学通过数据流转,聚合数据后才能进行分析展示。沟通和研发成本都是比较高的。而如果由研发自己完成监控,则可以省去沟通的成本和数据流转的成本。

1.3 业务监控关注什么?

  1. 适用的场景有哪些?
    • 一些业务状态分析:下单、搜索等关键路径的行为访问分析等。
    • 错误拆解分析: 对一些接口的错误进行统计分析。
    • 接口成功率监控等手段不能监控的地方。
  2. 如何做?
    • 不要影响业务流程,旁路完成。
    • 每一个监控是带有目的的,实现前需要想好以下两个问题:想要发现什么问题?需要哪些指标?

2. 案例展示

2.1 主题

文章拉取失败统计与分析

2.2 背景,为什么做?

项目中的文章服务由第三方合作伙伴提供,业务中保存了许多的文章ID,文章的内容需要调用合作伙伴的接口来获得,现在需要切换为带鉴权的新接口拉取,没有加入白名单的文章ID会拉取失败。

由于历史原因,有大量场景配置或者使用到了文章,现在把最重要的场景的文章提供给合作伙伴进行了加白。

那么,这里我们可能会遇到这些问题:

    1. 没有加白的文章里面是否有大量请求的(说明比较重要的)文章? 这些文章应该加白。
    1. 请求失败的文章里面,是否包含了已下架的文章? 这些文章应该删除配置。

2.2 需要统计哪些指标?

  • 文章ID: 以文章id为数据分析维度。
  • 失败次数:失败次数越多,说明越多用户请求,是有价值的文章。
  • 文章是否存在:文章已经下架,则应该取消配置。

2.3 上报与报表

  • 在文章列表拉取接口,检查请求参数与返回内容,将没有拉取到的文章打印到日志。
  • 文章是否存在,接口不带有这些信息,则由报表分析后人工判断top文章。
代码语言: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)
}

2.4 发现的问题

  1. 发现了大量已下架文章

    文章已下架.png
  2. 发现数篇需要授权的文章没有被授权
    授权遗漏.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中的字段与外层其他字段重名,同时方便检索分析。

  1. 将带有非常规日志的关键词丢弃
  2. 将msg字段按json格式展开到第一层
代码语言:txt
复制
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: 文章拉取失败统计

  1. 新建仪表盘

    新建仪表盘.png
  2. 添加图表
    新建图表.png
  3. 编辑规则语句并应用即可
    编辑图表.png
代码语言:txt
复制
note_keyword:"Monitor_Content_Fail" | select msg_str1 as docid, sum(msg_num1) as fail_count group by docid order by fail_count desc