diff --git a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AlibabaSmsClientImpl.java b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AlibabaSmsClientImpl.java index 1876d539401..2fdc05f8126 100644 --- a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AlibabaSmsClientImpl.java +++ b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AlibabaSmsClientImpl.java @@ -78,27 +78,30 @@ public AlibabaSmsClientImpl(AlibabaSmsProperties config) { @Override public void sendMessage(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert) { - // Extract alert info - String instance = null; - String priority = null; - String content = null; - if (alert.getCommonLabels() != null) { - instance = alert.getCommonLabels().get("instance"); - priority = alert.getCommonLabels().get("priority"); - content = alert.getCommonAnnotations().get("summary"); - content = content == null ? alert.getCommonAnnotations().get("description") : content; - if (content == null) { - content = alert.getCommonAnnotations().values().stream().findFirst().orElse(null); - } - } + sendSms(receiver.getPhone(), buildTemplateParam(alert)); + } + + // Aliyun rejects the whole request when any template variable is null or blank, + // so every value must fall back to non-blank text + String buildTemplateParam(GroupAlert alert) { + Map labels = alert.getCommonLabels() == null ? Map.of() : alert.getCommonLabels(); + Map annotations = alert.getCommonAnnotations() == null ? Map.of() : alert.getCommonAnnotations(); - // Build template parameters Map templateParam = new HashMap<>(); - templateParam.put("instance", instance == null ? alert.getGroupKey() : instance); - templateParam.put("priority", priority == null ? "unknown" : priority); - templateParam.put("content", content); + templateParam.put("instance", firstNonBlank(labels.get("instance"), alert.getGroupKey(), "unknown")); + templateParam.put("priority", firstNonBlank(labels.get("priority"), "unknown")); + templateParam.put("content", firstNonBlank(annotations.get("summary"), annotations.get("description"), + annotations.values().stream().findFirst().orElse(null), "alert triggered")); + return JsonUtil.toJson(templateParam); + } - sendSms(receiver.getPhone(), JsonUtil.toJson(templateParam)); + private static String firstNonBlank(String... values) { + for (String value : values) { + if (value != null && !value.isBlank()) { + return value; + } + } + return "unknown"; } private void sendSms(String phoneNumber, String templateParam) { diff --git a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/NoticeConfigServiceImpl.java b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/NoticeConfigServiceImpl.java index 8469315c5d9..73991466363 100644 --- a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/NoticeConfigServiceImpl.java +++ b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/NoticeConfigServiceImpl.java @@ -377,7 +377,8 @@ public boolean sendTestMsg(NoticeReceiver noticeReceiver) { .status("firing") .build(); GroupAlert groupAlert = GroupAlert.builder() - .commonLabels(Map.of(CommonConstants.LABEL_ALERT_NAME, "CPU Usage Alert")) + .commonLabels(Map.of(CommonConstants.LABEL_ALERT_NAME, "CPU Usage Alert", + CommonConstants.LABEL_INSTANCE, "127.0.0.1")) .commonAnnotations(annotations) .alerts(List.of(singleAlert1, singleAlert2)) .status("firing") diff --git a/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/service/impl/AlibabaSmsClientImplTest.java b/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/service/impl/AlibabaSmsClientImplTest.java new file mode 100644 index 00000000000..8ef633860f1 --- /dev/null +++ b/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/service/impl/AlibabaSmsClientImplTest.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hertzbeat.alert.service.impl; + +import java.util.Map; +import org.apache.hertzbeat.common.entity.alerter.GroupAlert; +import org.apache.hertzbeat.common.util.JsonUtil; +import org.junit.jupiter.api.Test; +import tools.jackson.core.type.TypeReference; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Test case for {@link AlibabaSmsClientImpl}: Aliyun rejects a template variable + * whose value is null or blank, so the built param JSON must never contain one. + */ +class AlibabaSmsClientImplTest { + + private final AlibabaSmsClientImpl client = new AlibabaSmsClientImpl(null); + + private Map params(GroupAlert alert) { + return JsonUtil.fromJson(client.buildTemplateParam(alert), new TypeReference<>() { }); + } + + @Test + void testAlertShapedLikeSendTestMsgHasNoNullVariables() { + GroupAlert alert = GroupAlert.builder() + .commonLabels(Map.of("alertname", "CPU Usage Alert")) + .commonAnnotations(Map.of("suggest", "Please check the CPU usage of the server")) + .build(); + + Map params = params(alert); + assertEquals(3, params.size()); + params.forEach((k, v) -> assertFalse(v == null || v.isBlank(), k + " must not be null/blank")); + assertEquals("unknown", params.get("instance")); + } + + @Test + void alertWithoutLabelsAndAnnotationsHasNoNullVariables() { + Map params = params(GroupAlert.builder().build()); + params.forEach((k, v) -> assertFalse(v == null || v.isBlank(), k + " must not be null/blank")); + } + + @Test + void realAlertValuesPassThrough() { + GroupAlert alert = GroupAlert.builder() + .commonLabels(Map.of("instance", "192.168.1.10:3306", "priority", "critical")) + .commonAnnotations(Map.of("summary", "mysql down")) + .build(); + + Map params = params(alert); + assertEquals("192.168.1.10:3306", params.get("instance")); + assertEquals("critical", params.get("priority")); + assertEquals("mysql down", params.get("content")); + assertTrue(params.values().stream().noneMatch(String::isBlank)); + } +} diff --git a/home/docs/help/alert_sms.md b/home/docs/help/alert_sms.md index cf2b0e8d46e..7c2ba190dc0 100644 --- a/home/docs/help/alert_sms.md +++ b/home/docs/help/alert_sms.md @@ -81,10 +81,10 @@ alerter: 3. Create a message template (template-code) - Go to "Template Management" page - Click "Add Template" - - Create a template with the following format: + - Create a template with the following content (Aliyun placeholders use the `${...}` syntax, and the template must declare exactly these three variables — no more, no less — otherwise sending fails with a "template variable/placeholder mismatch" error): ```text - Monitor: `instance`, Alert Level: `priority`. Content: `content` + Monitor: ${instance}, Alert Level: ${priority}. Content: ${content} ``` - Submit the template for review diff --git a/home/i18n/zh-cn/docusaurus-plugin-content-docs/current/help/alert_sms.md b/home/i18n/zh-cn/docusaurus-plugin-content-docs/current/help/alert_sms.md index c40a7b8e2b6..8ae126e4abc 100644 --- a/home/i18n/zh-cn/docusaurus-plugin-content-docs/current/help/alert_sms.md +++ b/home/i18n/zh-cn/docusaurus-plugin-content-docs/current/help/alert_sms.md @@ -80,10 +80,10 @@ alerter: 3. 创建短信模板(template-code) - 进入"模板管理"页面 - 点击"添加模板" - - 创建如下格式的模板: + - 创建如下内容的模板(阿里云占位符语法为 `${...}`,且模板必须恰好声明这三个变量——多一个少一个都会报"模板变量与占位符不一致"错误): ```text - 监控项:`instance`,告警级别:`priority`。内容:`content` + 监控项:${instance},告警级别:${priority}。内容:${content} ``` - 提交模板等待审核