Say you run a fleet, and different teams landed on different tools. The SOC is on Splunk, the platform team ships everything to Elasticsearch, one product group likes Datadog, and the Kubernetes clusters forward through Fluent Bit and Vector. Now onboard a new log source. You extract the fields once, in your head — and then you get to write that same extraction eight different ways, because no two of these tools agree on how a field extraction is spelled.
Here is one ordinary nginx access line:
192.168.1.10 - - [30/Jun/2026:22:14:15 +0000] "GET /api/health HTTP/1.1" 200 1024 "-" "Mozilla/5.0"
The fields are obvious to a human: client IP, timestamp, method, path, status, bytes, user-agent. Getting a machine to pull them out is where the eight dialects diverge. Below is the same extraction in each, generated by LogForge from that one line.
Splunk — a props.conf field extraction
Splunk does search-time extraction with a PCRE regex and named groups, dropped into props.conf under a sourcetype stanza:
[my_sourcetype]
EXTRACT-logforge = (?<ip1>\d{1,3}(?:\.\d{1,3}){3}) - - \[(?<timestamp>\d+/[A-Za-z]+/\d+:\d+:\d+:\d+ \+\d+)\] "(?<method>[^"]*) (?<path>\S+) HTTP/1\.1" (?<status>\d{3}) (?<number>-?\d+) "-" "(?<user_agent>[^"]*)"
The named-group syntax is (?<name>…). There is a subtlety most people hit exactly once and never forget: the same pattern in an | rex search command lives inside a double-quoted string, so every backslash has to be doubled and every quote escaped, or the search-time version stops matching the same input the props.conf version does.
Elasticsearch — an ingest pipeline with a grok processor
Modern ELK skips Logstash entirely and parses in the cluster with an ingest pipeline. It is JSON, and it uses grok:
PUT _ingest/pipeline/logforge
{
"processors": [
{ "grok": { "field": "message",
"patterns": ["%{IPV4:ip1} - - \\[%{HTTPDATE:timestamp}\\] \"%{WORD:method} %{URIPATHPARAM:path} HTTP/1\\.1\" %{INT:status} %{NUMBER:bytes} \"-\" \"%{DATA:user_agent}\""] } }
]
}
Now the fields are named %{PATTERN:name} and the backslashes are JSON-escaped (\\[). Same idea, third spelling.
Datadog — a Grok Parser rule (but not Logstash grok)
Datadog’s log pipelines have a “Grok Parser,” and it looks like grok, but the matcher names are Datadog’s own:
logforge_rule %{ipv4:ip1} - - \[%{date("dd/MMM/yyyy:HH:mm:ss Z"):timestamp}\] "%{word:method} %{notSpace:path} HTTP/1\.1" %{integer:status} %{number:bytes} "-" "%{data:user_agent}"
%{ipv4:…}, %{word:…}, %{integer:…} — and the timestamp isn’t a stock pattern, it’s %{date("dd/MMM/yyyy:HH:mm:ss Z"):timestamp} with a Joda-Time format string. This is a real trap when porting from Logstash: %{IPV4} and %{ipv4} are different languages that happen to rhyme.
Vector and Loki — RE2, so watch the named groups
Vector (VRL) and Loki’s promtail agent both use RE2 engines (Rust and Go). Named groups become (?P<name>…):
# Vector
[transforms.logforge_parse]
type = "remap"
source = '''
. |= parse_regex!(.message, r'(?P<ip1>\d{1,3}(?:\.\d{1,3}){3}) - - \[(?P<timestamp>[^\]]+)\] "(?P<method>\w+) (?P<path>\S+) HTTP/1\.1" (?P<status>\d{3})')
'''
# promtail (Loki)
- regex:
expression: '^(?P<ip1>\d{1,3}(?:\.\d{1,3}){3}) .* "(?P<method>\w+) (?P<path>\S+) .* (?P<status>\d{3})'
- labels:
method:
status:
Two things worth calling out here. RE2 has no lookahead — if your log’s fields reorder between lines and the pattern needs a lookaround, RE2 refuses to compile it, and you have to reach for a structured parser instead (that’s its own post). And on Loki, notice the labels block only promotes method and status: turning a high-cardinality field like the IP or the timestamp into a label is the classic way to melt a Loki index, so it stays in the extracted map, not on a label.
The rest, briefly
- Graylog takes either a pipeline rule (
grok(pattern: "…", only_named_captures: true)) or an import-ready extractor JSON. Watchonly_named_captures— Graylog defaults it to false, and the stock patterns contain unnamed groups, so leaving it off sprays numeric junk fields onto every message. - Fluent Bit wants a
[PARSER]stanza withFormat regexand a single anchored Onigmo regex —(?<name>…), lookaround allowed, four-space indent mandatory. - syslog-ng uses
regexp-parser(prefix(".logforge.") patterns("…")), PCRE, with the pattern inside a double-quoted string (so, escaping again).
The point
One extraction, eight syntaxes, and the differences aren’t cosmetic: PCRE vs RE2 vs Oniguruma have genuinely different capabilities, %{IPV4} vs %{ipv4} are different vocabularies, and the escaping rules flip depending on whether the pattern sits raw, in JSON, or inside a quoted string. Getting one wrong usually fails quietly — the parser just doesn’t match, and you find out when a dashboard is empty.
LogForge generates all of these from one paste and handles the per-dialect escaping and named-group conversion for you. It runs entirely in your browser — nothing is uploaded. If you want the deep link for a specific tool, there are per-platform pages for Splunk, Elasticsearch, Datadog, Vector, Loki and the rest.
FAQ
Are these configs tested against a live Splunk/Datadog/etc.? The regex is round-tripped against your sample lines, and the platform scaffolding (props.conf, ingest JSON, VRL, the extractor export) is checked for valid syntax and escaping — but it is not executed against a live instance of each product. Sanity-check the generated config before you ship it to prod.
Why is the Datadog timestamp a date("…") and not a stock pattern? Datadog’s grok has no HTTPDATE-style timestamp macro; you give it an explicit Joda-Time format. The generator derives the format from the shape of your sample timestamp.
Which one should I actually use? Whichever your pipeline already speaks. The value here is not picking a winner — it’s not having to hand-translate the same extraction into a syntax you touch twice a year.