Logstash conditionals let one pipeline choose different filter and output behavior for each event. They are useful when production errors, missing fields, parse-failure tags, and noisy debug records need different treatment before the event leaves Logstash.
Conditionals can run in filter and output blocks. They support if, else if, and else branches, nested field references such as [log][level], equality checks, regex matches, list membership with in or not in, and boolean operators such as and and or.
A temporary [@metadata][route] value keeps the routing decision out of the final event while still letting the output block act on it. The presence check if [log][level] returns false for a missing field, a false value, or null, so branch order matters when missing data needs its own route.
Related: How to use the Logstash mutate filter
Related: How to parse logs with grok in Logstash
$ mkdir -p /tmp/lc/p
{"log":{"level":"ERROR"},"env":"prod","message":"db failed"}
{"log":{"level":"INFO"},"env":"prod","message":"worker"}
{"log":{"level":"DEBUG"},"env":"prod","message":"healthcheck"}
{"env":"prod","message":"no level"}
{"tags":["_jsonparsefailure"],"message":"bad json"}
The records cover a production error branch, a default branch, a dropped debug branch, a missing-field branch, and an existing parse-failure tag.
Tool: JSON Validator
input {
file {
id => "conditional_source"
path => ["/tmp/lc/events.ndjson"]
mode => "read"
exit_after_read => true
start_position => "beginning"
sincedb_path => "/tmp/lc/sincedb"
file_completed_action => "log"
file_completed_log_path => "/tmp/lc/completed.log"
codec => json
}
}
filter {
if [log][level] {
mutate {
id => "normalize_level"
lowercase => ["[log][level]"]
}
}
if "_jsonparsefailure" in [tags] {
mutate {
id => "route_invalid_json"
add_tag => ["invalid_json"]
replace => { "[@metadata][route]" => "invalid-json" }
}
} else if ![log][level] {
mutate {
id => "route_missing_level"
add_tag => ["missing_level"]
replace => { "[@metadata][route]" => "missing-level" }
}
} else if [log][level] == "error" and [env] == "prod" {
mutate {
id => "route_production_error"
add_tag => ["production_error"]
replace => { "[@metadata][route]" => "production-error" }
}
} else if [log][level] in ["debug", "trace"] or [message] =~ /^healthcheck/ {
drop {
id => "drop_low_value_events"
}
} else {
mutate {
id => "route_default"
replace => { "[@metadata][route]" => "default" }
}
}
}
output {
if [@metadata][route] {
stdout {
id => "show_route"
codec => line {
format => "%{[@metadata][route]} %{message}"
}
}
}
}
Read mode processes the static sample once and exits after end of file. The stdout output prints only events that still have a route after filtering.
Related: How to configure a Logstash file input
Related: How to configure a Logstash stdout output
$ cd /usr/share/logstash
$ bin/logstash \ --path.data /tmp/lc/data-test \ --config.test_and_exit \ -f /tmp/lc/p Using bundled JDK: /usr/share/logstash/jdk ##### snipped ##### Configuration OK [2026-06-18T14:58:14,283][INFO ][logstash.runner ] Using config.test_and_exit mode. Config Validation Result: OK. Exiting Logstash
The json codec may warn about ECS compatibility because this sample keeps parsed fields at the event root. Set a codec target in production when parsed JSON keys can collide with Elastic Common Schema fields.
Related: How to test a Logstash pipeline configuration
$ bin/logstash --path.data /tmp/lc/data-run --log.level error -f /tmp/lc/p missing-level no level default worker invalid-json bad json production-error db failed
The debug healthcheck record has no output because the drop branch removed it. Route output order can vary when Logstash uses multiple pipeline workers, but every non-dropped branch should appear once.
$ rm -rf /tmp/lc