This is an automated email from the ASF dual-hosted git repository.
lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git
The following commit(s) were added to refs/heads/rocketmq-studio by this push:
new 9b0763cc fix: escape special characters in exported alert rules (#722)
9b0763cc is described below
commit 9b0763cc0987616ce0da1c19d2286b9d6d2062f4
Author: yx9o <[email protected]>
AuthorDate: Mon Aug 3 11:13:39 2026 +0800
fix: escape special characters in exported alert rules (#722)
---
.../rocketmq/studio/ops/alert/AlertService.java | 34 +++++++++++++---
.../studio/ops/alert/AlertServiceTest.java | 46 ++++++++++++++++++++++
2 files changed, 74 insertions(+), 6 deletions(-)
diff --git
a/server/src/main/java/org/apache/rocketmq/studio/ops/alert/AlertService.java
b/server/src/main/java/org/apache/rocketmq/studio/ops/alert/AlertService.java
index 59df2845..418a7fc0 100644
---
a/server/src/main/java/org/apache/rocketmq/studio/ops/alert/AlertService.java
+++
b/server/src/main/java/org/apache/rocketmq/studio/ops/alert/AlertService.java
@@ -58,8 +58,9 @@ public class AlertService {
yaml.append(" severity:
").append(rule.severity()).append('\n');
yaml.append(" team: ").append(rule.team()).append('\n');
yaml.append(" annotations:\n");
- yaml.append(" summary:
\"").append(escapeYaml(rule.summary())).append("\"\n");
- yaml.append(" description:
\"").append(escapeYaml(rule.description())).append("\"\n");
+ yaml.append(" summary:
\"").append(escapeDoubleQuotedValue(rule.summary())).append("\"\n");
+ yaml.append(" description:
\"").append(escapeDoubleQuotedValue(rule.description()))
+ .append("\"\n");
}
return yaml.toString();
}
@@ -188,7 +189,8 @@ public class AlertService {
}
private String alertName(AlertRuleVO rule) {
- return hasText(rule.getName()) ?
rule.getName().replaceAll("[^A-Za-z0-9_]", "") : "RocketMQAlert";
+ String alertName = hasText(rule.getName()) ?
rule.getName().replaceAll("[^A-Za-z0-9_]", "") : "";
+ return alertName.isEmpty() ? "RocketMQAlert" : alertName;
}
private String expression(AlertRuleVO rule) {
@@ -211,7 +213,7 @@ public class AlertService {
if (!selector.isEmpty()) {
selector.append(',');
}
- selector.append(label).append("=\"").append(value.trim()).append('"');
+
selector.append(label).append("=\"").append(escapeDoubleQuotedValue(value.trim())).append('"');
}
private String severity(AlertRuleVO rule) {
@@ -271,8 +273,28 @@ public class AlertService {
return hasText(description) ? description : "RocketMQ alert condition
matched.";
}
- private String escapeYaml(String value) {
- return value.replace("\\", "\\\\").replace("\"", "\\\"");
+ private String escapeDoubleQuotedValue(String value) {
+ StringBuilder escaped = new StringBuilder(value.length());
+ for (int index = 0; index < value.length(); index++) {
+ char character = value.charAt(index);
+ switch (character) {
+ case '\\' -> escaped.append("\\\\");
+ case '"' -> escaped.append("\\\"");
+ case '\b' -> escaped.append("\\b");
+ case '\f' -> escaped.append("\\f");
+ case '\n' -> escaped.append("\\n");
+ case '\r' -> escaped.append("\\r");
+ case '\t' -> escaped.append("\\t");
+ default -> {
+ if (character < 0x20) {
+ escaped.append(String.format("\\u%04x", (int)
character));
+ } else {
+ escaped.append(character);
+ }
+ }
+ }
+ }
+ return escaped.toString();
}
private boolean hasText(String value) {
diff --git
a/server/src/test/java/org/apache/rocketmq/studio/ops/alert/AlertServiceTest.java
b/server/src/test/java/org/apache/rocketmq/studio/ops/alert/AlertServiceTest.java
index 4c110c51..0e717c3f 100644
---
a/server/src/test/java/org/apache/rocketmq/studio/ops/alert/AlertServiceTest.java
+++
b/server/src/test/java/org/apache/rocketmq/studio/ops/alert/AlertServiceTest.java
@@ -16,6 +16,9 @@
*/
package org.apache.rocketmq.studio.ops.alert;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import org.apache.rocketmq.studio.common.domain.enums.AlertLevel;
import org.apache.rocketmq.studio.common.exception.BusinessException;
import org.junit.jupiter.api.Test;
@@ -152,6 +155,49 @@ class AlertServiceTest {
.contains("severity: warning");
}
+ @Test
+ void exportPrometheusRulesYamlShouldEscapeSpecialCharacters() throws
Exception {
+ AlertRuleVO rule = AlertRuleVO.builder()
+ .name("Scoped rule")
+ .metric("rocketmq_consumer_lag_messages")
+ .operator(">")
+ .threshold(1)
+ .clusterName("prod\"east\\dc\nline")
+ .brokerName("broker\tone")
+ .description("Summary \"quoted\" \\ path\nnext -
Details\twith\rline")
+ .build();
+ when(alertRepository.findAllRules()).thenReturn(List.of(rule));
+
+ String result = alertService.exportPrometheusRulesYaml();
+
+ assertThat(result).contains(
+ "expr:
rocketmq_consumer_lag_messages{cluster=\"prod\\\"east\\\\dc\\nline\","
+ + "broker=\"broker\\tone\"} > 1");
+ JsonNode exportedRule = new ObjectMapper(new
YAMLFactory()).readTree(result)
+ .path("groups").get(0).path("rules").get(0);
+ assertThat(exportedRule.path("annotations").path("summary").asText())
+ .isEqualTo("Summary \"quoted\" \\ path\nnext");
+
assertThat(exportedRule.path("annotations").path("description").asText())
+ .isEqualTo("Details\twith\rline");
+ }
+
+ @Test
+ void
exportPrometheusRulesYamlShouldUseFallbackWhenAlertNameHasNoValidCharacters()
throws Exception {
+ AlertRuleVO rule = AlertRuleVO.builder()
+ .name(" - !")
+ .metric("rocketmq_consumer_lag_messages")
+ .operator(">")
+ .threshold(1)
+ .build();
+ when(alertRepository.findAllRules()).thenReturn(List.of(rule));
+
+ String result = alertService.exportPrometheusRulesYaml();
+
+ JsonNode exportedRule = new ObjectMapper(new
YAMLFactory()).readTree(result)
+ .path("groups").get(0).path("rules").get(0);
+
assertThat(exportedRule.path("alert").asText()).isEqualTo("RocketMQAlert");
+ }
+
@Test
void createRuleShouldPreserveReplicationScopeFields() {
AlertRuleVO input = AlertRuleVO.builder()