feat(alerting): silence matcher for notification-time dispatch
SilenceMatcherService.matches() evaluates AND semantics across ruleId, severity, appSlug, routeId, agentId constraints. Null fields are wildcards. Scope-based constraints (appSlug/routeId/agentId) return false when rule is null (deleted rule — scope cannot be verified). 17 unit tests. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
package com.cameleer.server.app.alerting.notify;
|
||||
|
||||
import com.cameleer.server.core.alerting.AlertInstance;
|
||||
import com.cameleer.server.core.alerting.AlertRule;
|
||||
import com.cameleer.server.core.alerting.SilenceMatcher;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Evaluates whether an active silence matches an alert instance at notification-dispatch time.
|
||||
* <p>
|
||||
* Each non-null field on the matcher is an additional AND constraint. A null field is a wildcard.
|
||||
* Matching is purely in-process — no I/O.
|
||||
*/
|
||||
@Component
|
||||
public class SilenceMatcherService {
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the silence covers this alert instance.
|
||||
*
|
||||
* @param matcher the silence's matching spec (never null)
|
||||
* @param instance the alert instance to test (never null)
|
||||
* @param rule the alert rule; may be null when the rule was deleted after instance creation.
|
||||
* Scope-based matchers (appSlug, routeId, agentId) return false when rule is null
|
||||
* because the scope cannot be verified.
|
||||
*/
|
||||
public boolean matches(SilenceMatcher matcher, AlertInstance instance, AlertRule rule) {
|
||||
// ruleId constraint
|
||||
if (matcher.ruleId() != null && !matcher.ruleId().equals(instance.ruleId())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// severity constraint
|
||||
if (matcher.severity() != null && matcher.severity() != instance.severity()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// scope-based constraints require the rule to derive scope from
|
||||
boolean needsScope = matcher.appSlug() != null || matcher.routeId() != null || matcher.agentId() != null;
|
||||
if (needsScope && rule == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (rule != null && rule.condition() != null) {
|
||||
var scope = rule.condition().scope();
|
||||
if (matcher.appSlug() != null && !matcher.appSlug().equals(scope.appSlug())) {
|
||||
return false;
|
||||
}
|
||||
if (matcher.routeId() != null && !matcher.routeId().equals(scope.routeId())) {
|
||||
return false;
|
||||
}
|
||||
if (matcher.agentId() != null && !matcher.agentId().equals(scope.agentId())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user