{"record":{"id":"8af652b3503ed898","repo":"stamparm/maltrail","slug":"invalid-alert-format-s","errorCode":null,"errorMessage":"invalid 'ALERT_FORMAT' ('%s')","messagePattern":"invalid 'ALERT_FORMAT' \\('(.+?)'\\)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"core/alert.py","lineNumber":136,"sourceCode":"        _throttle[key] = now\n        return False\n\n\ndef body(event):\n    \"\"\"The request body, from ALERT_FORMAT.\n\n    A format string rather than a fixed structure, for the same reason CEF_FORMAT is one: there is\n    no webhook standard. Slack, Mattermost, Rocket.Chat and Google Chat take {\"text\": ...}, Discord\n    takes {\"content\": ...}, Teams wants an Adaptive Card, and a SIEM wants the event itself.\n    \"\"\"\n\n    template = config.ALERT_FORMAT or \"\"\n    fields = dict(event)\n    fields[\"json\"] = json_line(event)\n    try:\n        return template % fields\n    except (KeyError, TypeError, ValueError) as ex:\n        log_error(\"invalid 'ALERT_FORMAT' ('%s')\" % ex, single=True)\n        return None\n\n\ndef json_line(event):\n    \"\"\"The event as LOGSTASH_SERVER sends it, so anything already parsing that keeps working.\"\"\"\n\n    from collections import OrderedDict\n    return json.dumps(OrderedDict((key, event.get(key, \"\")) for key in\n                                  (\"timestamp\", \"sensor\", \"severity\", \"src_ip\", \"src_port\", \"dst_ip\",\n                                   \"dst_port\", \"proto\", \"type\", \"trail\", \"info\", \"reference\")))\n\n\ndef send(event):\n    \"\"\"POST one event. Never raises: a webhook outage must not stop the server or the tailer.\"\"\"\n\n    payload = body(event)\n    if payload is None:\n        return False","sourceCodeStart":118,"sourceCodeEnd":154,"githubUrl":"https://github.com/stamparm/maltrail/blob/77cfb06d7606506d101bbcec0786c77166c4255e/core/alert.py#L118-L154","documentation":"Maltrail's alert body formatter applies the ALERT_FORMAT config value as a printf-style template against the event fields. When the template references missing keys or malformed placeholders, the % formatting raises KeyError/TypeError/ValueError; the code catches it and logs \"invalid 'ALERT_FORMAT' ...\" via log_error, returning None so no alert body is produced.","triggerScenarios":"ALERT_FORMAT contains a placeholder like %(missing_key)s with no matching field in the event dict, or uses a % character not intended as a placeholder (e.g. '99%'), causing ValueError 'incomplete format' or a TypeError on bad substitution types.","commonSituations":"Users copying templates from other alerting tools that use $VAR or {} syntax instead of Python %()s; embedding a literal percent sign in a URL or percentage without escaping it as %%; renaming event fields in a fork while old templates reference them.","solutions":["Fix ALERT_FORMAT to reference only fields that exist in the event dict (e.g. %(dst_ip)s, plus %(json)s).","Escape literal percent signs as %% in ALERT_FORMAT.","Run config.ALERT_FORMAT against a sample event dict offline (template % dict) to validate before deploying.","Switch to the fields['json'] placeholder if you want the full event payload in one token."],"exampleFix":"# before\nALERT_FORMAT = \"Alert on sensor %(sensor)s - uptime 99% - %(unknown_field)s\"\n\n# after\nALERT_FORMAT = \"Alert on sensor %(sensor)s - uptime 99%% - %(json)s\"","handlingStrategy":"validation","validationCode":"fields = dict(event); fields[\"json\"] = json_line(event)\ntry:\n    (config.ALERT_FORMAT or \"\") % fields\nexcept (KeyError, TypeError, ValueError):\n    raise ValueError(\"ALERT_FORMAT references missing fields or has bad % placeholders\")","typeGuard":"def valid_alert_format(tpl, fields):\n    if not isinstance(tpl, str): return False\n    try: tpl % fields; return True\n    except (KeyError, TypeError, ValueError): return False","tryCatchPattern":"try:\n    body = alert.body(event)\nexcept Exception:\n    body = None  # body() already logged; fall back to json_line(event) raw payload","preventionTips":["Only use placeholders that exist in the event dict (or fields['json'])","Escape literal % as %% in ALERT_FORMAT","Test new ALERT_FORMAT values against a sample event before deploying","Prefer the %(json)s placeholder for full-payload alerts"],"tags":["python","config","formatting","alerts"],"backgroundTag":"invalid-config-value","analyzedSha":"77cfb06d7606506d101bbcec0786c77166c4255e","analyzedAt":"2026-09-13T03:50:16.010Z","contentChangedAt":"2026-09-13T03:50:16.010Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}