Skip to main content

Writing routing conditions

Use Tera templates in routing rule conditions​

A routing rule condition is a Tera expression that must evaluate to true or false.

The system automatically wraps your input with {{ }}, so provide only the expression itself.

alertDef.name is starting_with("CPU")

This expression evaluates to true when the alert name starts with "CPU".

What a condition can reference​

Every routing rule carries its own entity type, and the notifications processor only evaluates a rule against notifications of that type. A cases rule never sees an alert, and an alerts rule never sees a case.

Entity typeObjects available
Alertsalert.*, alertDef.*, _context.*, and the flat shortcuts correlationKey, name, priority
Casescase.*, caseMetadata.*, _context.*, and the flat shortcuts correlationKey, name, priority

Each shortcut points at a different path on each entity type. Watch priority in particular: on an alert it is the highest priority across the firing's groups, not the priority set on the definition.

ShortcutOn an alertOn a case
correlationKeyalert.groupingKeycase.id
namealertDef.namecase.title
priorityalert.highestPrioritycase.priority

Referencing the other entity type's paths fails in two different ways. In a cases rule the condition is rejected when you save it, because cases conditions are checked against an allow-list. In an alerts rule it saves without complaint and then never matches, because alerts have no allow-list and a path that is not there passes validation. See Missing variables.

See the real payload for your own data

To list every field a given notification actually carries, put this in a preset template and send a test notification. It prints the whole object, so you can read the exact paths from your own data rather than working from a reference.

{{ get_context() | json_encode(pretty = true) }}

alertDef.*: the alert definition​

The alert as it is configured, which is what most routing rules match on.

PathHoldsExample value
alertDef.idAlert definition ID9d4c1a0e-...
alertDef.alertVersionIdVersion ID of the definitionver12345-...
alertDef.nameAlert nameCPU Usage Alert
alertDef.descriptionAlert descriptionFires when CPU exceeds 90%
alertDef.priorityPriority, P1 to P5P1
alertDef.typeAlert type
alertDef.entityLabelsLabels on the definition, read by keyalertDef.entityLabels.team
alertDef.groupByKeysKeys the alert groups byhost, region
alertDef.incidentSettings.notifyOnWhen the alert notifiesTriggered
alertDef.incidentSettings.retriggeringPeriod.minutesMinutes before the alert can retrigger. A number.10
alertDef.typeDefinitionThe per-type configuration, which differs by alertDef.type
alertDef.createdTime / alertDef.updatedTimeWhen the definition was created and last changed. Epoch numbers, not strings.1768377731

Confirm the exact form of alertDef.type from a test notification before you match on it, using the tip above. A rule written against the wrong form silently never matches.

The two alert identifiers are easy to confuse

alertDef.id is the alert's permanent definition ID, and alertDef.alertVersionId identifies one version of that definition. To match the same alert across edits, you almost certainly want alertDef.id.

alert.*: the firing instance​

The specific firing, rather than the definition.

PathHoldsExample value
alert.idID of this firing76c411be-...
alert.statusStatus of the firingTriggered
alert.highestPriorityHighest priority across the alert's groupsP1
alert.hasNoDataWhether the alert is in a no-data statefalse
alert.timestampWhen it triggered. An epoch number, not a string.1768377731
alert.statusBreakdown.totalGroupCountHow many groups the alert has12
alert.groupingKeyDeduplication keycheckout:eu-west-1
alert.alertDataUrlDeep link to the alert in Coralogixhttps://...
alert.groups[0].keyValues["key"]Group-by values for one groupalert.groups[0].keyValues["host"]

Read a group-by key with brackets. Dot notation reads each dot as a nested level, so a key that contains dots, as coralogix.metadata.subsystemName does, is not found and the rule does not match:

alert.groups[0].keyValues["coralogix.metadata.subsystemName"]

For the full alert payload, see Alerts notification source type schema.

case.* and caseMetadata.*​

Cases carry a larger object than alerts do, but a routing condition may reference only part of it. Saving a router checks the condition against an allow-list, and a condition that reads anything outside it is rejected as invalid.

PathHoldsValues
case.idCase ID
case.titleCase title
case.statusCase statusPENDING_ACTIVATION, ACTIVE, ACKNOWLEDGED, RESOLVED, CLOSED
case.priorityPriorityP1 to P5
case.categoryCategoryAVAILABILITY, SECURITY
caseMetadata.notificationReasonWhy this notification firedSee Cases entity type

Those paths, the flat shortcuts, and _context.* are the whole list. Everything else a case carries, including case.labels, case.groupings, and the rest of caseMetadata.*, works in a notification template but not in a routing condition: case.labels["team"][0] == "platform" is rejected when you save the router. The full template payload is listed in Cases template variable reference.

_context.*: the notification envelope​

Shared by every entity type. Full list in Dynamic templating.

PathHolds
_context.entityTypealerts or cases
_context.entitySubTypeThe subtype, such as metricThresholdMoreThanTriggered
_context.entityLabelsRouting labels on the notification. Each value is a list, so read it with [0], as in _context.entityLabels.team[0]
_context.system.nameYour Coralogix team name
_context.trigger.typemanual or automatic

Alerts​

Condition for alert name starting with certain text​

alertDef.name is starting_with("CPU")
Alert nameResult
CPU usage hightrue
CPU saturationtrue
Memory usage highfalse

Condition for alert name containing certain text​

alertDef.name is containing("CPU")
Alert nameResult
CPU usage hightrue
Database CPU spiketrue
Memory usage highfalse

Condition for alert name matching a regex​

alertDef.name is matching("(?i)^cpu")
  • ^cpu matches alert names starting with "cpu"
  • (?i) makes the match case-insensitive
Alert nameResult
CPU usage hightrue
cpu saturationtrue
Memory usage highfalse

Condition for alert containing a specific label​

alertDef.entityLabels.group is defined
LabelsResult
group: paymentstrue
label not presentfalse

This condition is useful when routing alerts based on ownership labels such as team, group, or service.

Condition for alert containing a specific value for a label​

alertDef.entityLabels.environment == "production"
Environment labelResult
productiontrue
stagingfalse

Use this rule to route production alerts separately from staging or development alerts.

Cases​

Routing rules can also evaluate properties from the Cases payload when the entity type is cases.

Cases represent incidents created from alerts and move through the lifecycle states PENDING_ACTIVATION, ACTIVE, ACKNOWLEDGED, RESOLVED, and CLOSED. The payload is accessible through the case object.

Condition for case status​

Route notifications only while a Case is live.

case.status == "ACTIVE"
Case statusResult
ACTIVEtrue
RESOLVEDfalse
CLOSEDfalse

Typical use: send notifications while an incident is still live, and stay quiet once it is resolved.

Condition for acknowledged cases​

Send notifications only when a case has been acknowledged.

case.status == "ACKNOWLEDGED"
Case statusResult
ACKNOWLEDGEDtrue
ACTIVEfalse

Typical use: notify stakeholders when an incident has been acknowledged by an engineer.

Combine case conditions​

Route notifications only for acknowledged critical cases.

case.status == "ACKNOWLEDGED"
and case.priority == "P1"
StatusPriorityResult
ACKNOWLEDGEDP1true
ACKNOWLEDGEDP3false
ACTIVEP1false

Typical use: escalate acknowledged critical incidents.

Entity-type specific routing​

A routing rule is already scoped to one entity type, and the notifications processor only evaluates it against notifications of that type. A cases rule never sees an alert notification, so a condition does not need to check for one:

_context.entityType == "cases"

In a routing rule this adds nothing to the filtering the rule's own entity type already does.

How conditions differ from other templates​

Boolean only​

Regular templates render text, while routing rule conditions must return a boolean value (true or false).

Invalid:

"CPU alert"

Valid:

alertDef.name is containing("CPU")

Do not include {{}}​

The system automatically wraps your expression.

Incorrect:

{{ alertDef.name is containing("CPU") }}

Correct:

alertDef.name is containing("CPU")

Missing variables​

If a condition references a path that does not exist, the condition fails to evaluate and the rule does not match. This applies to every operator and test, including ==, !=, in, is containing and is matching.

alertDef.entityLabels.team == "payments"

If the team label is missing, this rule does not match.

A missing path is not treated as false inside the condition, so negations and or do not behave as you might expect. With no team label, neither of these rules matches:

alertDef.entityLabels.team != "payments"
alertDef.entityLabels.team == "payments" or alertDef.name is containing("CPU")

Guard the path with is defined. and and or stop evaluating once the result is known, so put the guard first:

alertDef.entityLabels.team is defined
and alertDef.entityLabels.team is containing("pay")
alertDef.entityLabels.team is not defined
or alertDef.entityLabels.team != "payments"

Matching against a list is the shorter way to write several alternatives, but it needs the same guard:

alertDef.entityLabels.team is defined
and alertDef.entityLabels.team in ["payments", "checkout"]

Matching syntax​

Comparison operators​

OperatorMeansExample
==Equalcase.priority == "P1"
!=Not equalalertDef.priority != "P5"
> >= < <=Numeric comparisonalertDef.incidentSettings.retriggeringPeriod.minutes > 10
inValue is one of a listalertDef.priority in ["P1", "P2"]

Use == and != with any value type. The four ordering operators work only on fields that hold a number. Priority is a string ("P1" through "P5"), so compare it with == or !=, and list the ones you want rather than reaching for <=.

String comparison is case-sensitive and exact. To match part of a value, or to ignore case, use a test instead.

Logical operators​

OperatorMeansExample
andBoth sides truecase.status == "ACTIVE" and case.priority == "P1"
orEither side truealertDef.priority == "P1" or alertDef.priority == "P2"
notInvertsnot alertDef.name is containing("test")

and binds tighter than or, so group with parentheses when you mix them:

(case.priority == "P1" or case.priority == "P2") and case.status == "ACTIVE"

Without the parentheses that reads as P1, or P2 and active, which is not the same rule.

Tests​

A test is written value is test_name(argument) and returns a boolean.

TestTrue whenExample
definedThe path exists in the payloadalertDef.entityLabels.team is defined
undefinedThe path is absentalertDef.entityLabels.team is undefined
starting_withThe string starts with the argumentalertDef.name is starting_with("CPU")
ending_withThe string ends with the argumentalertDef.name is ending_with("prod")
containingThe string contains the argumentalertDef.name is containing("CPU")
matchingThe string matches a regular expressionalertDef.name is matching("(?i)^cpu")
stringThe value is a stringcase.title is string
numberThe value is a numberalertDef.incidentSettings.retriggeringPeriod.minutes is number
oddThe number is oddalert.statusBreakdown.totalGroupCount is odd
evenThe number is evenalert.statusBreakdown.totalGroupCount is even
divisiblebyThe number divides exactly by the argumentalert.statusBreakdown.totalGroupCount is divisibleby(5)
iterableThe value is an array or an objectalertDef.groupByKeys is iterable
objectThe value is an objectalertDef.entityLabels is object

That is the whole set. A name that is not on this list is not a test, and the rule is rejected when you save it.

starting_with, ending_with, and containing are case-sensitive. For a case-insensitive match, use matching with the (?i) flag.

defined and undefined are the only two tests that tolerate a missing path, which is what makes them usable as guards. Every other test on this list fails on one, and the rule is skipped. odd, even, and divisibleby fail the same way when the value is not a number.

Negate a test with is not:

alertDef.name is not containing("test")

Regular expressions​

is matching takes a regular expression, written as a string.

Backslashes and quotation marks​

A condition string carries no escape sequences. Everything between the opening and closing delimiter reaches the expression exactly as you typed it, so a backslash is an ordinary character. Write it once:

alertDef.name is matching("^prod-\d+$")

Doubling it sends a literal backslash to the regular expression, which then matches the two characters \d rather than a digit, and the rule silently never matches.

For the same reason a string cannot contain its own delimiter, and there is no way to escape one. A condition string may be wrapped in double quotation marks, single quotation marks, or backticks, so use a delimiter the value does not contain:

alertDef.name is containing('a "quoted" word')

Matching is partial unless you anchor it​

A pattern matches when it is found anywhere in the value. Anchor it to control that:

PatternMatches
"cpu"Any name containing cpu
"^cpu"Any name starting with cpu
"cpu$"Any name ending with cpu
"^cpu$"The exact name cpu

Supported syntax​

SyntaxMeaningExample
^ $Start and end of the string^prod-
.Any charactera.c
[abc] [^abc] [a-z]Character class, negated class, range[0-9]+
[0-9] [A-Za-z]Digit, letter[0-9]+
\d \w \sDigit, word character, whitespace^prod-\d+$
* + ?Repetitionprod-.*
{2} {2,} {2,5}Counted repetition[a-z]{3}
|Alternation^(prod|staging)-
(...)Group(prod)-api
(?:...)Non-capturing group(?:prod|staging)
(?i)Case-insensitive from this point(?i)^cpu
(?s)Dot matches newlines(?s).+
(?m)^ and $ match at line breaks(?m)^error

Not supported​

The following constructs are not supported:

Not supportedExample of what fails
Lookahead^(?!test-)
Lookbehind(?<=prod-)api
Backreferences([a-z]+)-\1

An expression using any of these fails to compile, so the rule is rejected when you save it. Rewrite it with the supported syntax: a negative lookahead is usually better expressed as not alertDef.name is starting_with("test-").

Next steps​

Create and manage connectors that link Coralogix to third-party services in Connectors.

Last updated on