How to check in one minute
Paste one real line into /var/ossec/bin/wazuh-logtest and read phase 1 and 2:
- Phase 2
name: 'json'with your keys listed: decoding works; the problem is in the rule. - Phase 1 shows a
program_nameand phase 2 saysNo decoder matched: the syslog-header case below. - Phase 2
name: 'windows-date-format'and none of your keys: the plain-date case below.
What we measured
The same JSON object in four line shapes, before and after adding the decoders below:
| Line shape | Stock 4.14.7 | With the decoders below |
|---|---|---|
{"app":"payments","event":"login_failed",…} | json, keys read | same |
Oct 2 21:00:00 web01 payments: {…} | pre-decoding finds program_name: 'payments', then No decoder matched | payments-syslog-json, keys read |
2026-10-02T21:00:00.123Z INFO payments {…} | json, keys read | same |
2026-10-02 21:00:00 payments - {…} | windows-date-format, no keys | keys read (decoder reported as windows-date-format, its parent) |
The stock json decoder (0006-json_decoders.xml) has a <prematch>^{\s*" and no <program_name>. In our runs, once the syslog header gave the event a program name, that decoder was not used, and nothing else matched. The plain-date shape starts with exactly what windows-date-format (0380-windows_decoders.xml) looks for, ^\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d , so it took the line first.
Fix
Save as /var/ossec/etc/decoders/payments_decoders.xml, replacing payments with your program's name:
<!-- case 2: syslog header + JSON. The header gives a program name, so give the decoder one too. -->
<decoder name="payments-syslog-json">
<program_name>^payments$</program_name>
<plugin_decoder>JSON_Decoder</plugin_decoder>
</decoder>
<!-- case 4: plain date + name + dash + JSON. The stock windows-date-format decoder claims this
date shape first, so hang the JSON decoder under it as a child. -->
<decoder name="payments-dash-json">
<parent>windows-date-format</parent>
<prematch offset="after_parent">^payments - </prematch>
<plugin_decoder offset="after_prematch">JSON_Decoder</plugin_decoder>
</decoder>
A rule on the JSON keys, in /var/ossec/etc/rules/:
<group name="local,payments,">
<rule id="100950" level="5">
<field name="app">^payments$</field>
<field name="event">^login_failed$</field>
<description>payments: login failed for $(user) from $(src_ip).</description>
</rule>
</group>
Then wazuh-analysisd -t (clean on 4.14.7) and restart. With both files, all four line shapes above hit rule 100950. If you control the application, logging plain JSON lines, or forwarding them without a header, avoids the problem entirely.
One more thing we saw: a JSON key named user came out as dstuser, while src_ip stayed src_ip, not the static srcip. Rules and active responses that need srcip will not see an address stored under another name.
Limits of what we measured
Measured in wazuh-logtest on a Wazuh 4.14.7 manager with lines we wrote, one JSON object per line. We did not test events arriving through <localfile> with log_format json, multi-line JSON, nested objects in rules, or other versions. The statement about program names describes what we observed, not a reading of the decoder engine's source.