logrus必须自己实现企业微信发送逻辑,因其官方hook列表无wecomhook,第三方库年久失修、不兼容新版且缺token刷新与限流控制;手写hook需严格遵循企业微信接口规范,包括http超时、json字段校验、错误重试、环境变量读取webhook key、按级别过滤(仅error/fatal/panic)、内存级限流防刷屏,并手动组装msgtype小写、content严格校验的json payload。

Logrus Hook 为什么必须自己实现企业微信发送逻辑
Logrus 本身不内置企业微信支持,官方 Hook 列表里没有 WecomHook,第三方库如 logrus-wecom-hook 普遍年久失修、不兼容 Logrus v1.9+、缺少 token 自动刷新或消息限流控制。直接用会导致告警发不出、重复发送、或被企业微信接口限流封禁。
真正可行的方式是手写一个符合 logrus.Hook 接口的结构体,自己调用企业微信 /cgi-bin/webhook/send 接口。关键点在于:必须做 HTTP 超时控制、JSON 序列化字段校验、错误重试(但不能无限重试)、以及避免敏感信息(如 webhook key)硬编码。
- 企业微信 webhook 地址形如
https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx,key必须从环境变量或配置文件读取,绝不能写死 - Logrus 的
Fire()方法会在每次日志触发时同步调用,若网络抖动或接口返回 403/429,需记录本地 error 日志而非 panic - 企业微信对同一 webhook 每分钟最多 20 条文本消息,超出会返回
{"errcode":45009,"errmsg":"reach max api freq limit"},需在 Hook 内做简单计数或退避,不能靠外部调度
如何构造符合企业微信要求的 JSON Payload
企业微信机器人只接受特定格式的 JSON,且字段名大小写敏感、必填项严格。Logrus 的 Entry 字段(如 entry.Data)默认不会自动映射成企业微信需要的 text.content,必须手动提取并组装。
最简可用的文本消息结构如下,注意:msgtype 必须小写,content 不能含控制字符,且长度不能超 2048 字节:
由于微信的大热,为了更好的方便使用微信的用户查询一些信息,这篇文章是入门级的微信公众平台开发教程,需要的朋友可以参考下 这篇入门教程将引导你完成如下任务: 创建百度云平台应用启用微信公众平台开发模式获取订阅、文字、图片、语音、视频消息回复文本、图文及音乐消息程序开发
{
"msgtype": "text",
"text": {
"content": "[ALERT] service=order-service level=error msg=\"timeout after 5s\" trace_id=abc123"
}
}
- 推荐从
entry.Data中提取关键键,如entry.Data["service"]、entry.Data["trace_id"],拼成可读性强的告警正文 - 不要直接用
entry.String(),它包含大量调试字段(如 file/line),易超长且泄露路径信息 - 若需 @ 某人,用
"mentioned_mobile_list": ["13800138000"],但必须确保手机号已录入企业微信通讯录,否则无效
Hook 实现中必须设置的 HTTP 客户端参数
企业微信接口不是内部服务,公网调用失败率高,用默认 http.DefaultClient 会卡死 goroutine 或堆积连接。必须自定义 http.Client 并设以下参数:
-
Timeout: 5 * time.Second—— 避免单次告警阻塞主业务日志输出 -
Transport: &http.Transport{MaxIdleConns: 10, MaxIdleConnsPerHost: 10, IdleConnTimeout: 30 * time.Second}—— 控制连接复用,防止 fd 耗尽 - 务必检查响应状态码:仅当
resp.StatusCode == 200且响应 JSON 中errcode == 0才算成功,其余都应记为发送失败
示例片段:
client := &http.Client{
Timeout: 5 * time.Second,
Transport: &http.Transport{
MaxIdleConns: 10,
MaxIdleConnsPerHost: 10,
IdleConnTimeout: 30 * time.Second,
},
}
resp, err := client.Post(webhookURL, "application/json", bytes.NewReader(payload))
if err != nil {
log.Printf("wecom hook post failed: %v", err) // 注意:这里用 std lib log,避免递归调用自身 Hook
return
}
如何让告警只发 ERROR 及以上级别,且避免刷屏
微服务日志量大,若所有 Info 级别日志都走企业微信,几分钟就触发频率限制。Logrus Hook 的 Levels() 方法必须精确返回要拦截的级别列表,不能写 logrus.AllLevels。
- 线上环境建议只设
[]logrus.Level{logrus.ErrorLevel, logrus.FatalLevel, logrus.PanicLevel} - 若需区分“关键 Error”和普通 Error,可在
Fire()内加判断:比如检查entry.Data["alert"] == true或匹配特定entry.Message正则(如regexp.MustCompile(`(?i)timeout|deadlock|panic`)) - 加内存级限流:用
time.Now().UnixMinute()作 key 做简易滑动窗口,每分钟最多发 3 条同类型告警,避免雪崩式重复通知
容易被忽略的是:Logrus 的 WithField 和 WithError 产生的字段,不会自动进入 entry.Message,必须显式读取 entry.Data 并处理,否则告警正文为空或缺失 trace_id。










