Skip to content

Commit

Permalink
修复分段消息行号错误问题 (#3)
Browse files Browse the repository at this point in the history
* 适应企业微信的换行规则

* 更新模板

* 移动模板的说明文件

* 更新对于模板的说明

* 修复分段编号逻辑

* bytes.Buffer > strings.Builder

* 企业微信中的空行

* 更新对于分段的说明

* 优化片段长度

* Update CHANGELOG.md

* 考虑第一次写入片段时的 emptyLine 长度
  • Loading branch information
rea1shane committed Jul 6, 2023
1 parent b8e11a9 commit ad720d1
Show file tree
Hide file tree
Showing 6 changed files with 56 additions and 61 deletions.
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,14 @@

本文件格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/),并且本项目遵守 [Semantic Versioning](https://semver.org/lang/zh-CN/) 版本更新规则。

## [0.1.5](https://github.com/rea1shane/a2w/compare/0.1.4...0.1.5) - 2023-07-06

### 已修复

- 修复分段消息序号问题。

## [0.1.4](https://github.com/rea1shane/a2w/compare/0.1.3...0.1.4) - 2023-07-05

### 已修复

- 修复发送给企业微信的消息内容过长导致请求失败 ([#1](https://github.com/rea1shane/a2w/issues/1))
- 修复发送给企业微信的消息内容过长导致请求失败([#1](https://github.com/rea1shane/a2w/issues/1))
21 changes: 5 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,24 +46,13 @@

消息模板决定了企业微信机器人发出的消息格式,修改 `Makefile` 中的 `TEMPLATE` 变量的值来选择模板。

因为企业微信机器人接口限制单条消息的最大长度为 4096,所以本软件会对大于此限制的长消息进行分段,如果你使用自定义模板,请让每个告警实例之间留有一行空行(表现为 tmpl 文件中 `range` 语句的 `{{ end }}` 为单独的一行,参考 [`base.tmpl`](https://github.com/rea1shane/a2w/blob/main/templates/base.tmpl#L14)),以便本软件对消息进行正确的分段
模板的使用注意事项请看同路径下的同名 Markdown 文件

### `base.tmpl`
因为企业微信机器人接口限制单条消息的最大长度为 4096,所以本软件会对大于此限制的长消息进行分段。如果你使用自定义模板,请在想要分段的地方留一个空行,以便本软件对消息进行正确的分段。

若要使用该消息模板,在告警规则定义中必须包含:

- `labels.level`:告警规则等级。

可以选择包含:

- `annotations.current`:当前状态的表达式结果值,可以通过 `{{ $value }}` 获取。
- `annotations.labels`:可以定位到该告警实例的标签列表。

### `multiple-cluster.tmpl`

使用该模板与使用 [`base.tmpl`](#basetmpl) 相比,多了一个必选标签:

- `labels.cluster`:集群名称。
> 注意:
>
> 在企业微信中,至少三个连续的 `\n` 才被认为是一个空行
## 构建项目

Expand Down
58 changes: 32 additions & 26 deletions a2w.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,10 @@ type Alert struct {
}

const (
webhookUrl = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key="
okMsg = `{"errcode":0,"errmsg":"ok"}`
webhookUrl = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key="
okMsg = `{"errcode":0,"errmsg":"ok"}`
markdownMaxLen = 4096 // markdownMaxLen 企业微信 Markdown 消息体最大长度为 4096
emptyLine = "\n\n\n" // emptyLine 在企业微信中,连续至少三个的换行符才被视为一个空行
)

var (
Expand Down Expand Up @@ -108,50 +110,54 @@ func send(c *gin.Context) {
}

// 消息分段
// 为了解决企业微信 Markdown 消息体 4096 长度限制问题
// 为了解决企业微信 Markdown 消息体长度限制问题
var msgs []string
if content.Len() <= 4096 {
if content.Len() <= markdownMaxLen {
msgs = append(msgs, content.String())
} else {
// 分段消息标识头
msgHeader := `<font color="comment">**(%d/%d)**</font>`
snippetHeader := `<font color="comment">**(%d/%d)**</font>`

// 单条分段最大长度,预留一些空间用于添加分段头和容错
msgMaxLen := 4096 - 128
// 分段条数
// 因为企业微信机器人接口每分钟频率是 20 条,当消息分段超过 20 条时可能会有部分消息发送失败
msgsLen := content.Len()/msgMaxLen + 1
snippetMaxLen := markdownMaxLen - len(snippetHeader)

// 消息切割
contentSnippets := bytes.Split(content.Bytes(), []byte("\n\n"))
fragments := strings.Split(content.String(), emptyLine)

// 消息构造器
var msgBuffer bytes.Buffer
msgIndex := 1
msgBuffer.Write([]byte(fmt.Sprintf(msgHeader, msgIndex, msgsLen)))
var snippetBuilder strings.Builder
snippetBuilder.Grow(snippetMaxLen)

// 拼接消息
for _, contentSnippet := range contentSnippets {
for _, fragment := range fragments {
// 切割后的单条消息都过长
if len(contentSnippet) > msgMaxLen {
e := c.Error(errors.New(fmt.Sprintf("单条告警消息长度 %d 仍超出片段长度限制 %d", len(contentSnippet), msgMaxLen)))
e.Meta = "消息分段失败"
if len(fragment)+len(emptyLine) > snippetMaxLen {
e := c.Error(errors.New(fmt.Sprintf("切割后的消息长度 %d 仍超出片段长度限制 %d", len(fragment), snippetMaxLen-len(emptyLine))))
e.Meta = "分段消息失败"
c.Writer.WriteHeader(http.StatusBadRequest)
return
}

// 拼接消息后超出限制长度
if msgBuffer.Len()+len(contentSnippet) > msgMaxLen {
msgs = append(msgs, msgBuffer.String())
msgBuffer.Reset()
msgIndex++
msgBuffer.Write([]byte(fmt.Sprintf(msgHeader, msgIndex, msgsLen)))
if snippetBuilder.Len()+len(fragment)+len(emptyLine) > snippetMaxLen {
msgs = append(msgs, snippetBuilder.String())
snippetBuilder.Reset()
snippetBuilder.Grow(snippetMaxLen)
}

msgBuffer.Write([]byte("\n\n"))
msgBuffer.Write(contentSnippet)
snippetBuilder.WriteString(emptyLine)
snippetBuilder.WriteString(fragment)
}

msgs = append(msgs, msgBuffer.String())
msgs = append(msgs, snippetBuilder.String())

// 添加分段头
for index, snippet := range msgs {
snippetBuilder.Reset()
snippetBuilder.Grow(markdownMaxLen)
snippetBuilder.WriteString(fmt.Sprintf(snippetHeader, index+1, len(msgs)))
snippetBuilder.WriteString(snippet)
msgs[index] = snippetBuilder.String()
}
}

for _, msg := range msgs {
Expand Down
10 changes: 10 additions & 0 deletions templates/base.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# `base.tmpl`

告警规则定义中必须包含:

- `labels.level`:告警规则等级。

可以选择包含:

- `annotations.current`:当前状态的表达式结果值,可以通过 `{{ $value }}` 获取。
- `annotations.labels`:可以定位到该告警实例的标签列表。
4 changes: 2 additions & 2 deletions templates/base.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
**警报等级**: {{ $alert.Labels.level }}
**触发时间**: {{ timeFormat ($alert.StartsAt) }}
**持续时长**: {{ timeFromNow ($alert.StartsAt) }}
{{ if $alert.Annotations.current }}**当前状态**: {{ $alert.Annotations.current }}{{ end }}
{{ else if eq $alert.Status "resolved"}}
<font color="info">**[resolved] {{ $alert.Labels.alertname }}**</font>
**触发时间**: {{ timeFormat ($alert.StartsAt) }}
**恢复时间**: {{ timeFormat ($alert.EndsAt) }}
**持续时长**: {{ timeDuration ($alert.StartsAt) ($alert.EndsAt) }}
{{ end }}{{ if $alert.Annotations.current }}**当前状态**: {{ $alert.Annotations.current }}{{ end }}
{{ if $alert.Annotations.labels }}**标签列表**: {{ $alert.Annotations.labels }}{{ end }}
{{ end }}{{ if $alert.Annotations.labels }}**标签列表**: {{ $alert.Annotations.labels }}{{ end }}
{{ end }}
16 changes: 0 additions & 16 deletions templates/multiple-cluster.tmpl

This file was deleted.

0 comments on commit ad720d1

Please sign in to comment.