Fix note · OpenSearch Alerting

OpenSearch Alerting sends one notification for three matching lines. Which monitor sends one per line?

What you see: several log lines match within a minute and you get one email or webhook call, not one per line. Why: a per query monitor runs one search per run and evaluates its trigger once, on the whole result, however many documents it found. In our test, 3 matching lines 5 seconds apart produced 1 notification. What to use: a per document monitor, with the action set to per_alert and actionable_alerts: ["NEW"]. It created one alert per matching document and sent 3 notifications for the 3 lines, each carrying that document's id, and none again in the next 3 minutes.

Dong Nguyen, ATK New Technology · 28 October 2026 · measured on OpenSearch 3.6.0, the indexer that ships with Wazuh 5.0.0-rc1, in our test containers

One indexer node, security plugin off, monitors created through the REST API, schedule 1 minute, one webhook listener, throttle off. Test documents were written by us, not real logs. The per document setup was also run on stock OpenSearch 3.8.0 with the same result; the comparison of the three monitor types was run on 3.6.0 only.

Stuck on a monitor right now? Email the monitor JSON (GET _plugins/_alerting/monitors/ID), two or three sample documents and your OpenSearch version to support@atkvn.com. We run it on a test index and reply within 24 hours with what we find. Free. Remove hostnames, IPs and usernames first.


This note comes from a question on the OpenSearch forum: alert on any log line whose message contains one of ten words (error, warning, alert, failed, unavailable, lost, wrong, unable, invalid, failure), except lines containing six exclusion strings and lines from the sshd-session service, with one notification for every matching line. An earlier reply in that thread, from this author, suggested a per query monitor because “you get one alert per run instead of one per matching line.” For this goal that was the wrong pick, and the table below shows why. We measured all three monitor types instead of guessing.

How to check your monitor

Both checks are read-only. Set these first:

IDX=http://localhost:9200   # your cluster. Ours had the security plugin off: plain HTTP, no login.
CURL="curl -s"              # with security on: CURL="curl -sk -u admin:YOUR_PASSWORD" and IDX=https://...

1. Which monitor type, and which execution policy on each action

$CURL "$IDX/_plugins/_alerting/monitors/MONITOR_ID" | python3 -m json.tool \
  | grep -E '"monitor_type"|"interval"|"unit"|per_alert|per_execution|actionable_alerts|throttle_enabled'

query_level_monitor is a per query monitor, bucket_level_monitor per bucket, doc_level_monitor per document. If yours is query_level_monitor and you want one notification per line, that is the cause.

2. How many alerts, and for which documents

$CURL "$IDX/_plugins/_alerting/monitors/alerts?size=200" | python3 -c '
import json,sys
for a in json.load(sys.stdin).get("alerts",[]):
    print(a["monitor_name"], a["id"], a["state"], a["start_time"], a.get("related_doc_ids"))'

In our test the per document monitor had one ACTIVE alert per matching document, each with one entry in related_doc_ids. The per query monitor kept no alert per document: it reports on the search as a whole.

What we measured

OpenSearch 3.6.0 (the indexer bundled with Wazuh 5.0.0-rc1, Alerting and Notifications plugins as shipped there), one node, security plugin off, our test containers, 28 October 2026. Times are UTC. A test index with message as text (plus a message.keyword subfield), service and host as keyword, and @timestamp as date. Three monitors, all created through POST /_plugins/_alerting/monitors, all on a 1-minute schedule, each with one webhook action to the same listener, "throttle_enabled": false.

Run 1: three matching lines, three monitor types

Three documents containing “error”, written at 04:04:07.7, 04:04:12.9 and 04:04:18.0, plus one control document with none of the words. All three monitors ran at 04:04:52. We counted what the webhook listener received, then kept watching until 04:07:46.

monitorhow it was set upnotificationsalerts listed at 04:07:46
per query (query_level_monitor)search over the last minute, match message: error; trigger ctx.results[0].hits.total.value > 0; action with no execution policy10
per document (doc_level_monitor)doc-level query message:error; trigger query[name=q-error]; action per_alert, actionable_alerts: ["NEW"]33 ACTIVE, one per document, each with that document's id
per bucket (bucket_level_monitor)same search as per query, terms on message.keyword; trigger _count > 0; action per_alert, ["NEW"]30

Run 2: the full condition, six documents

One per document monitor with the whole condition as its doc-level query (below, in how to set it up), the same per_alert / ["NEW"] action. Six documents, 5 seconds apart, from 04:10:27 to 04:10:53:

#servicemessageshould notify?notified?
1pvedaemonerror starting VM 105yesyes
2kernelata2: link is slow to respond, unable to resetyesyes
3pveproxyproxy detected vanished client connection, request failedyesyes
4pvedaemonerror: disabling inotify for this hostno (exclusion string)no
5sshd-sessionpam_unix: authentication failed for user rootno (excluded service)no
6pvestatdstatus update time 0.2 secondsno (no keyword)no

The run at 04:11:12 sent exactly 3 notifications, and the 3 alerts' related_doc_ids were exactly documents 1, 2 and 3. Nothing more was sent through 04:14:20.

Same run on stock OpenSearch 3.8.0. We repeated run 2 on the opensearchproject/opensearch:3.8.0 image (Alerting 3.8.0.0, security plugin off), same index mapping, same query and action, same six documents from 04:58:19 to 04:58:45. The run at 04:59:04 sent exactly 3 notifications, the 3 alerts' related_doc_ids were documents 1, 2 and 3, and nothing more was sent through 05:02:13. We did not repeat run 1 (the per query and per bucket comparison) on 3.8.0.

Why it happens

A per query monitor runs your search once per run and evaluates the trigger once, on the whole result. Three hits or three hundred, the trigger is either true or false for that run. In our test the run that saw all three lines sent one notification from an action with no execution policy. That is useful when you want one message per run, which is what the earlier forum reply optimised for, and the wrong shape when you want one message per line.

A per document monitor checks each new document against its queries and keeps an alert per matching document. With the action on per_alert and actionable_alerts: ["NEW"], each new alert sent one notification, and none was sent again in the 3 minutes we watched: 3 documents, 3 alerts, 3 notifications, 0 repeats.

One thing we did not expect to be true, so we checked it: the API accepted per_alert with ["NEW"] on a per query monitor too, and stored it. We did not run that monitor, so we cannot tell you what it does in practice, and we did not check what the Dashboards editor offers for a per query monitor.

How to set it up (tested)

The per document monitor from run 2, with the whole condition in one doc-level query. Names and the message template differ from our test run; the query and the action policy are the ones we ran. Replace the index, the channel id and the message template with yours:

$CURL -XPOST "$IDX/_plugins/_alerting/monitors" -H 'Content-Type: application/json' -d '{
  "type": "monitor",
  "monitor_type": "doc_level_monitor",
  "name": "critical-lines",
  "enabled": true,
  "schedule": { "period": { "interval": 1, "unit": "MINUTES" } },
  "inputs": [ { "doc_level_input": {
    "description": "",
    "indices": [ "YOUR-INDEX" ],
    "queries": [ {
      "id": "q-critical", "name": "q-critical", "tags": [],
      "query": "message:(error OR warning OR alert OR failed OR unavailable OR lost OR wrong OR unable OR invalid OR failure) AND NOT message:\"disabling inotify\" AND NOT message:\"user config - ignore\" AND NOT message:\"_put_session\" AND NOT message:\"PVE ticket\" AND NOT message:\"guest-ping\" AND NOT message:\"Cyberprotect\" AND NOT service:\"sshd-session\""
    } ]
  } } ],
  "triggers": [ { "document_level_trigger": {
    "name": "critical-line",
    "severity": "1",
    "condition": { "script": { "source": "query[name=q-critical]", "lang": "painless" } },
    "actions": [ {
      "name": "notify",
      "destination_id": "YOUR-CHANNEL-ID",
      "subject_template": { "source": "Critical log line", "lang": "mustache" },
      "message_template": { "source": "Critical log line, run ending {{ctx.periodEnd}}", "lang": "mustache" },
      "throttle_enabled": false,
      "action_execution_policy": { "action_execution_scope": { "per_alert": { "actionable_alerts": [ "NEW" ] } } }
    } ]
  } } ]
}'

Limits of what we measured

OpenSearch 3.6.0, the indexer bundled with Wazuh 5.0.0-rc1, one node, security plugin off, plain HTTP, in our test containers. Run 2 alone was repeated on stock OpenSearch 3.8.0 (one node, security plugin off) with the same result; run 1 and the per query probe were not. Not a cluster. Monitors were created through the REST API, not the Dashboards editor. One webhook listener; we did not test email, Slack, Telegram or other channel types. Throttle was off. Two runs: 3 matching documents plus a control, then 6 documents. We watched for repeats for about 3 minutes after each; the 3 alerts were still ACTIVE at the end, and we did not follow them until they were acknowledged or completed. Not measured: per bucket with identical message text, a per query monitor actually running with per_alert, four of the six exclusion strings (accepted, no test line), a service field mapped as text, a message field holding an array of strings, and heavy load.

Free check

Send us the monitor that is not doing what you expect. The monitor JSON, two or three sample documents with hostnames and IPs removed, and your OpenSearch version. We create it on a test index, push your samples through it and reply within 24 hours with what it actually did. Free.

Send it

Free answer within 24 hours.

Paste the monitor JSON and a few sample documents below, with hostnames and IPs removed, and say what you expected it to send. We reply within 24 hours with what it actually does.

Or email support@atkvn.com.