一、开通机器人权限

先找公司行政部门开通企业微信机器人权限。开通相关权限后,会发现此时就可以往公司企业微信群里添加相关机器人,如下图所示:
WX20210605-091742.png

创建一个机器人,叫日志监控机器人,专门用来进行 Sentry 的错误告警上报,如下图所示。
企业微信20210605092723.png

进入Sentry服务器,找到相应项目,并点击设置按钮。
企业微信20210605093858.png

将刚才生成的企业微信机器人的 Webhook 复制到 Sentry 服务器的相应项目中,点击保存,至此大功告成,是不是很激动!!!
:Users:bgl:企业微信20210605094342.jpg
点击测试插件按钮,发现企业微信机器人并没有收到相关日志提醒,咦,这是怎么回事,难道是复制的姿势不对?

注:如果找不到 WebHooks 这个操作菜单栏,则需在遗留集成中找到 WebHooks,并开启 WebHooks。随后刷新页面,即可出现WebHooks操作菜单栏。

造成这一问题的主要原因是 Sentry 调用 webhook 发送的数据报文格式和企业微信机器人要求的数据报文格式不一致造成的,如下所示:

企业微信机器人要求的数据报文格式:

  1. {
  2. "msgtype": "text",
  3. "text": {
  4. "content": "广州今日天气:29度,大部分多云,降雨概率:60%",
  5. "mentioned_list":["wangqing","@all"],
  6. "mentioned_mobile_list":["13800001111","@all"]
  7. }
  8. }

Sentry发送的数据报文格式:

  1. {
  2. id: '7',
  3. project: 'ii-admin-pro',
  4. project_name: 'ii-admin-pro',
  5. project_slug: 'ii-admin-pro',
  6. logger: null,
  7. level: 'error',
  8. culprit: 'raven.scripts.runner in main',
  9. message: 'This is an example Python exception',
  10. url: 'http://sentry.xxxxxxx.com/organizations/sentry/issues/7/?referrer=webhooks_plugin',
  11. triggering_rules: [],
  12. event: {
  13. event_id: 'f602ac321ee04bc28a20c9f4d446ef48',
  14. level: 'error',
  15. version: '5',
  16. type: 'default',
  17. logentry: {
  18. formatted: 'This is an example Python exception',
  19. message: null,
  20. params: null
  21. },
  22. logger: '',
  23. modules: { 'my.package': '1.0.0' },
  24. platform: 'python',
  25. timestamp: 1622734089.769465,
  26. received: 1622734089.7702,
  27. environment: 'prod',
  28. user: {
  29. id: '1',
  30. email: 'sentry@example.com',
  31. ip_address: '127.0.0.1',
  32. username: 'sentry',
  33. name: 'Sentry',
  34. geo: [Object]
  35. },
  36. request: {
  37. url: 'http://example.com/foo',
  38. method: 'GET',
  39. data: [Object],
  40. query_string: [Array],
  41. cookies: [Array],
  42. headers: [Array],
  43. env: [Object],
  44. inferred_content_type: 'application/json',
  45. fragment: null
  46. },
  47. ...
  48. }
  49. }

针对数据报文格式不一致的问题,此时就需要搭建一个 Node 服务,将 Sentry 的数据报文做下转换,并按照企业微信机器人的数据报文格式进行发送。

二、搭建 Node 服务

笔者基于 egg.js 快速搭建了一个 Node 服务,路由配置如下:
企业微信20210605102704.png

app/controller/robot/sentry.js 文件中创建 SentryController,用来负责数据报文的转换,代码所示:

  1. 'use strict';
  2. const Controller = require('egg').Controller;
  3. const request = require('../../utils/request');
  4. const { SENTRY_HOOKS } = require('../../utils/const');
  5. const { fmtDateTime, genProjectOwners } = require('../../utils/utils');
  6. class SentryController extends Controller {
  7. /**
  8. * 接收Sentry发送过来的Webhook
  9. */
  10. async recvSentryWebhook() {
  11. const {
  12. params,
  13. request: { body },
  14. } = this.ctx;
  15. const ROBOT_DATA = {
  16. msgtype: 'markdown',
  17. markdown: {
  18. content: `!!!前端项目<font color=\"warning\">${body.project_name}</font>发生错误:
  19. > 错误原因: <font color=\"info\">${body.culprit}</font>
  20. > 错误时间: <font color=\"info\">${fmtDateTime()}</font>
  21. > 错误级别: <font color=\"info\">${body.level}</font>
  22. > 错误链接: [查看日志](${body.url})
  23. \n
  24. 请以下同事注意:${genProjectOwners(SENTRY_HOOKS[params.name].owners)}`,
  25. },
  26. };
  27. const result = await request({
  28. url: SENTRY_HOOKS[params.name].sentry_hook,
  29. method: 'POST',
  30. headers: {
  31. 'content-type': 'application/json',
  32. },
  33. data: JSON.stringify(ROBOT_DATA),
  34. });
  35. this.ctx.body = {
  36. code: '0',
  37. data: result,
  38. msg: '提醒成功',
  39. };
  40. }
  41. }
  42. module.exports = SentryController;

utils/utils.js文件

  1. /**
  2. * 对当前时间进行格式化
  3. */
  4. const fmtDateTime = () => {
  5. let date = new Date();
  6. let year = date.getFullYear();
  7. let month = date.getMonth() + 1;
  8. let hour = date.getHours();
  9. let min = date.getMinutes();
  10. month = month < 10 ? `0${month}` : month;
  11. hour = hour < 10 ? `0${hour}` : hour;
  12. min = min < 10 ? `0${min}` : min;
  13. return `${year}-${month}-${date.getDate()} ${hour}:${min}`;
  14. };
  15. /**
  16. * 生成项目负责人
  17. */
  18. const genProjectOwners = (owners) => {
  19. return owners.map((item) => `<@${item}> `).join('');
  20. };
  21. module.exports = {
  22. fmtDateTime,
  23. genProjectOwners,
  24. };

utils/const.js
主要用来存储配置常量,后续有新的项目,只需往常量文件中添加新的项目配置即可。
企业微信20210605103608.png
其中:

  • owners 存放的是企业微信用户的 userid,userid 即企业邮箱的前缀,举例:xiaoli@xxxx.com,userid 则是xiaoli;

代码编写完后,接下来就是将项目部署上线了,笔者采用的是 pm2 来管理线上 Node 服务,运行pm2 deploy ecosystem.yaml production,将本地代码同步到线上,并重启 Node 服务(关于项目部署,不是本文重点,此处略过)。

将线上接口地址复制到 Sentry 服务器相应的 Webhooks 地址中,保存后点击测试插件按钮,测试日志监控机器人是否生效,如下所示:
企业微信20210605110231.png
这时,企业微信群成功收到 Sentry 发送过来的日志提醒,如下所示:
企业微信20210605110342.png
至此,大功告成!

注意事项:
开发过程中如果你的 Sentry 服务是部署在线上,不在本地局域网内,那么你本地在进行 Node 服务调试时是收不到 Sentry 发送的日志告警的,此时需要做内网穿透或者将你的 Node 服务搭到线上,关于这一点要注意下!!!本人在这一问题曾卡壳一两天。

三、自动分配 Issue

3.1 Issue Owners

通过 Issue Owners 选项我们可以在 Issue 发生时自动建立 Issue 与责任人关联关系,与此同时直接发送邮件通知给对应负责的小伙伴。一共有两种设置方式,都是根据正则表达式进行匹配

  • 路径(path):通过指定源文件所在目录及owners
  • 页面路由(url):通过指定具体页面路由及owners

image.png
以上是通过邮件手段来通知相关责任人,但更多场景是当业务模块出问题时希望在飞书或企业微信等渠道立马通知到该成员,此时就需要在 Sentry 后台创建一个内部集成,用来监听 Sentry 的 assigned事件。当出现该事件时,就可以拿到被分配的人员信息,然后利用机器人来通知到团队成员。

3.2 创建内部集成

3.2.1 配置内部集成

进入 Sentry 后台,点击”设置 > 开发设置 > 新内部集成”,创建新的内部集成。针对不同的内部集成,可以赋予不同的权限。
image.png
image.png
其中:

  • “Webhook URL” 指向处理 Sentry 事件的服务器。
  • 勾选”issue”复选框,以便在创建、解决、分配问题时得到通知。

3.2.2 assigned事件

在之前搭建的 Node 服务中,添加assigned事件的处理,样例如下(注:以下是飞书机器人代码):

  1. /**
  2. * 分配Issue
  3. */
  4. async assignIssue() {
  5. const {
  6. request: { body },
  7. } = this.ctx;
  8. const genRobotData = (owners = []) => {
  9. return {
  10. msg_type: 'post', // 富文本
  11. content: {
  12. post: {
  13. zh_cn: {
  14. content: [
  15. [
  16. {
  17. tag: 'text',
  18. text: '请以下同事关注下: ',
  19. },
  20. ...owners,
  21. ],
  22. ],
  23. },
  24. },
  25. },
  26. };
  27. };
  28. if (body.action === 'assigned') {
  29. const { assignee, issue } = body.data;
  30. const pjt_name = issue.project.name;
  31. const owner = SENTRY_HOOKS[pjt_name].owners.find(
  32. item => item.email === assignee.email
  33. );
  34. const result = await request({
  35. url: SENTRY_HOOKS[pjt_name].hook,
  36. method: 'POST',
  37. headers: {
  38. 'content-type': 'application/json',
  39. },
  40. data: JSON.stringify(genRobotData(genProjectOwners([owner], 'Feishu'))),
  41. });
  42. this.ctx.body = {
  43. code: '0',
  44. data: result,
  45. msg: '提醒成功',
  46. };
  47. return;
  48. }
  49. this.ctx.body = {
  50. code: '0',
  51. msg: '暂时只支持类型为"assigned"的 web hook',
  52. };

当 Sentry 收到新的 Issue 时,会根据之前配置的规则,匹配到相应的责任人,然后给内部集成发送一个 assigned 事件。Node 服务收到该事件,会给飞书机器人发送消息,然后飞书机器人就会在监控群里 @ 相关责任人,达到实时通知的效果。
image.png

其他参考: