stamparm/maltrail · warning

invalid 'ALERT_FORMAT

Error message

invalid 'ALERT_FORMAT' ('%s')

What it means

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.

Solutions

  1. Fix ALERT_FORMAT to reference only fields that exist in the event dict (e.g. %(dst_ip)s, plus %(json)s).
  2. Escape literal percent signs as %% in ALERT_FORMAT.
  3. Run config.ALERT_FORMAT against a sample event dict offline (template % dict) to validate before deploying.
  4. Switch to the fields['json'] placeholder if you want the full event payload in one token.

Example fix

# before
ALERT_FORMAT = "Alert on sensor %(sensor)s - uptime 99% - %(unknown_field)s"

# after
ALERT_FORMAT = "Alert on sensor %(sensor)s - uptime 99%% - %(json)s"
Defensive patterns

Strategy: validation

Validate before calling

fields = dict(event); fields["json"] = json_line(event)
try:
    (config.ALERT_FORMAT or "") % fields
except (KeyError, TypeError, ValueError):
    raise ValueError("ALERT_FORMAT references missing fields or has bad % placeholders")

Type guard

def valid_alert_format(tpl, fields):
    if not isinstance(tpl, str): return False
    try: tpl % fields; return True
    except (KeyError, TypeError, ValueError): return False

Try / catch

try:
    body = alert.body(event)
except Exception:
    body = None  # body() already logged; fall back to json_line(event) raw payload

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of stamparm/maltrail@77cfb06d76 (2026-09-13). Data as JSON: /api/errors/8af652b3503ed898. Report an issue: GitHub.

Appendix: source

Thrown at core/alert.py:136

        _throttle[key] = now
        return False


def body(event):
    """The request body, from ALERT_FORMAT.

    A format string rather than a fixed structure, for the same reason CEF_FORMAT is one: there is
    no webhook standard. Slack, Mattermost, Rocket.Chat and Google Chat take {"text": ...}, Discord
    takes {"content": ...}, Teams wants an Adaptive Card, and a SIEM wants the event itself.
    """

    template = config.ALERT_FORMAT or ""
    fields = dict(event)
    fields["json"] = json_line(event)
    try:
        return template % fields
    except (KeyError, TypeError, ValueError) as ex:
        log_error("invalid 'ALERT_FORMAT' ('%s')" % ex, single=True)
        return None


def json_line(event):
    """The event as LOGSTASH_SERVER sends it, so anything already parsing that keeps working."""

    from collections import OrderedDict
    return json.dumps(OrderedDict((key, event.get(key, "")) for key in
                                  ("timestamp", "sensor", "severity", "src_ip", "src_port", "dst_ip",
                                   "dst_port", "proto", "type", "trail", "info", "reference")))


def send(event):
    """POST one event. Never raises: a webhook outage must not stop the server or the tailer."""

    payload = body(event)
    if payload is None:
        return False

View on GitHub (pinned to 77cfb06d76)