What a HAProxy HTTP line looks like
The syslog sample below is fed verbatim into the engine to produce every parser on this page.
Jul 3 14:22:15 lb01 haproxy[990]: 192.0.2.10:51234 [03/Jul/2026:14:22:15.123] https-in~ api/srv2 0/0/1/12/13 200 512 - - ---- 5/5/0/1/0 0/0 "GET /api/health HTTP/1.1"
Jul 3 14:22:19 lb01 haproxy[990]: 198.51.100.23:44002 [03/Jul/2026:14:22:19.880] https-in~ api/srv1 0/0/0/44/45 503 217 - - sH-- 7/7/1/2/0 0/0 "POST /api/checkout HTTP/1.1" Detected fields
The engine classified this sample as syslog3164 and consolidated 17 fields across 2 lines. Fields marked literal were identical on every sample line, so they are baked into the pattern as anchors rather than captured.
- timestamp : timestamp
- hostname : hostname · literal
- program : literal · literal
- pid : number · literal
- ipv4_port : ipv4_port
- literal : literal
- _lit1 : literal · literal
- literal2 : literal
- path : path
- number : number
- number2 : number
- _lit2 : literal · literal
- _lit3 : literal · literal
- literal3 : literal
- path2 : path
- _lit4 : literal · literal
- quoted_string : quoted_string
Regex (named capture groups)
# sample: Jul 3 14:22:15 lb01 haproxy[990]: 192.0.2.10:51234 [03/Jul/2026:14:22:15.123] https-in~ api/srv2 0/0/1/12/13 200 512 - - ---- 5/5/0/1/0 0/0 "GET /api/health HTTP/1.1"
# groups: timestamp=Jul 3 14:22:15, ipv4_port=192.0.2.10:51234, literal=03/Jul/2026:14:22:15.123, literal2=api/srv2, path=0/0/1/12/13, number=200, number2=512, literal3=----, path2=5/5/0/1/0, quoted_string=GET /api/health HTTP/1.1
^(?<timestamp>[A-Za-z]+ \d+ \d+:\d+:\d+) lb01 haproxy\[990\]: (?<ipv4_port>\d{1,3}(?:\.\d{1,3}){3}:\d{1,5}) \[(?<literal>\d+/[A-Za-z]+/\d+:\d+:\d+:\d+\.\d+)\] https-in~ (?<literal2>[A-Za-z]+/[A-Za-z]+\d+) (?<path>\d+/\d+/\d+/\d+/\d+) (?<number>-?\d+(?:\.\d+)?) (?<number2>-?\d+(?:\.\d+)?) - - (?<literal3>(?:----|[A-Za-z]+--)) (?<path2>\d+/\d+/\d+/\d+/\d+) 0/0 "(?<quoted_string>[^"]*)"$ Grok pattern (Logstash / Elastic)
# custom patterns
HAPROXY_NOTDQUOTE [^"]*
%{SYSLOGTIMESTAMP:timestamp} lb01 haproxy\[990\]: %{HOSTPORT:ipv4_port} \[%{DATA:literal}\] https-in~ %{NOTSPACE:literal2} %{NOTSPACE:path} %{NUMBER:number} %{NUMBER:number2} - - %{NOTSPACE:literal3} %{NOTSPACE:path2} 0/0 "%{HAPROXY_NOTDQUOTE:quoted_string} - note constant field "hostname" embedded as literal anchor "lb01" (varying=false)
- note constant field "pid" embedded as literal anchor "990" (varying=false)
- note field "path" (path): samples do not all match %{PATH}; using %{NOTSPACE} instead
- note field "path2" (path): samples do not all match %{PATH}; using %{NOTSPACE} instead
- note custom patterns emitted — save the '# custom patterns' block to a file in your patterns_dir
Wazuh decoder (OS_Regex XML)
<!--
Generated by LogForge - Wazuh decoder (OS_Regex dialect, not PCRE)
sample: Jul 3 14:22:15 lb01 haproxy[990]: 192.0.2.10:51234 [03/Jul/2026:14:22:15.123] https-in~ api/srv2 0/0/1/12/13 200 512 - - - -- - 5/5/0/1/0 0/0 "GET /api/health HT
test with: /var/ossec/bin/wazuh-logtest
-->
<decoder name="haproxy-syslog3164">
<program_name>^haproxy$</program_name>
</decoder>
<decoder name="haproxy-syslog3164">
<parent>haproxy-syslog3164</parent>
<regex offset="after_parent">^(\d+.\d+.\d+.\d+:\d+)</regex>
<order>ipv4_port</order>
</decoder>
<!-- ============================================================
ALERT RULE (starter) — put this in a RULES file, e.g.
/var/ossec/etc/rules/local_rules.xml. Decoders and rules live
in SEPARATE files. The rule matches the decoder above through
<decoded_as>; set <level> and add <field>/<match> conditions so
it alerts only on the events you care about. Rule ids 100000+
are the user range — change them if they collide with yours.
============================================================ -->
<group name="haproxy,">
<rule id="100000" level="3">
<decoded_as>haproxy-syslog3164</decoded_as>
<description>haproxy: ipv4_port=$(ipv4_port)</description>
</rule>
<!-- Example — a higher-level alert gated on one field (uncomment and edit):
<rule id="100001" level="10">
<if_sid>100000</if_sid>
<field name="ipv4_port">^CHANGE_ME$</field>
<description>haproxy: a value you care about from $(ipv4_port)</description>
</rule>
-->
</group>
- note syslog header fields handled by Wazuh pre-decoding (not re-parsed): timestamp, hostname, program, pid
- note parent matches by <program_name> (1 program(s) seen in the samples) — extend the alternation for other programs
- note field "literal" has no safe OS_Regex pattern before the "]" terminator — template truncated; field(s) omitted: literal, _lit1, literal2, path, number, number2, _lit2, _lit3, literal3, path2, _lit4, quoted_string
- note added a starter alert <rule> (level 3, matched to the decoder via <decoded_as>) — put it in a RULES file (not the decoders file), set the level, and add <field>/<match> conditions; the commented example child rule shows the pattern
- note decoder order and prematch specificity may need site-specific tuning (other decoders in your ruleset can shadow these) — validate with /var/ossec/bin/wazuh-logtest
Wazuh's OS_Regex is not PCRE — a bare . is a literal dot and \. matches any character.
Test Wazuh OS_Regex patterns →
rsyslog template / liblognorm rulebase
# rsyslog template — put in /etc/rsyslog.d/haproxy.conf
# Emits the detected RFC 3164 header fields plus the raw
# message body as JSON-ish text using standard rsyslog properties.
# Bind the template to an action, e.g.:
# action(type="omfile" file="/var/log/haproxy.log" template="haproxy")
# A literal "%" inside a template string must be escaped as "%%".
template(name="haproxy" type="string" string="{\"timestamp\":\"%timereported%\",\"host\":\"%hostname%\",\"program\":\"%programname%\",\"procid\":\"%procid%\",\"msg\":\"%msg:::json%\"}\n")
# --- mmnormalize rulebase for the message body ----------------------
# rsyslog properties cover only the syslog header. To extract the fields
# inside the message body, save the lines below (starting at 'version=2',
# which must be the VERY FIRST line of the file) as /etc/rsyslog.d/haproxy.rb
# and load them with:
# module(load="mmnormalize")
# action(type="mmnormalize" rulebase="/etc/rsyslog.d/haproxy.rb")
version=2
rule=haproxy_msg: %ipv4_port:word% [%literal:char-to{"extradata":"]"}%] https-in~ %literal2:word% %path:word% %number:number% %number2:number% - - %literal3:word% %path2:word% 0/0 "%quoted_string:rest%
- note header property mapping: timestamp -> %timereported%, hostname -> %hostname%, program -> %programname%, pid -> %procid%
- note message-body fields cannot be extracted by rsyslog properties alone — an mmnormalize (liblognorm v2) rulebase for the msg part is appended below the template; save it as its own .rb file
- note dropped header separator "]:" before the first message field (it belongs to the syslog tag, not the msg property)
- note chosen parser types: ipv4_port=word, literal=char-to(]), literal2=word, path=word, number=number, number2=number, literal3=word, path2=word, quoted_string=rest
Splunk
# props.conf (search-time extraction)
[<REPLACE_WITH_SOURCETYPE>]
EXTRACT-logforge = (?<timestamp>[A-Za-z]+ \d+ \d+:\d+:\d+) lb01 haproxy\[990\]: (?<ipv4_port>\d{1,3}(?:\.\d{1,3}){3}:\d{1,5}) \[(?<literal>\d+/[A-Za-z]+/\d+:\d+:\d+:\d+\.\d+)\] https-in~ (?<literal2>[A-Za-z]+/[A-Za-z]+\d+) (?<path>\d+/\d+/\d+/\d+/\d+) (?<number>-?\d+(?:\.\d+)?) (?<number2>-?\d+(?:\.\d+)?) - - (?<literal3>(?:----|[A-Za-z]+--)) (?<path2>\d+/\d+/\d+/\d+/\d+) 0/0 "(?<quoted_string>[^"]*)"
# Quick search-time test in SPL:
# | rex field=_raw "(?<timestamp>[A-Za-z]+ \\d+ \\d+:\\d+:\\d+) lb01 haproxy\\[990\\]: (?<ipv4_port>\\d{1,3}(?:\\.\\d{1,3}){3}:\\d{1,5}) \\[(?<literal>\\d+/[A-Za-z]+/\\d+:\\d+:\\d+:\\d+\\.\\d+)\\] https-in~ (?<literal2>[A-Za-z]+/[A-Za-z]+\\d+) (?<path>\\d+/\\d+/\\d+/\\d+/\\d+) (?<number>-?\\d+(?:\\.\\d+)?) (?<number2>-?\\d+(?:\\.\\d+)?) - - (?<literal3>(?:----|[A-Za-z]+--)) (?<path2>\\d+/\\d+/\\d+/\\d+/\\d+) 0/0 \"(?<quoted_string>[^\"]*)\"" - note EXTRACT-<class> names must be unique within a sourcetype stanza — rename EXTRACT-logforge if you already use that class for this sourcetype
- note a timestamp field was detected: this EXTRACT only makes it a searchable field. To set the event _time at index time, add TIME_PREFIX and TIME_FORMAT to this props.conf stanza (TIME_FORMAT uses Splunk strptime, e.g. %Y-%m-%dT%H:%M:%S) — this generator does not guess the strptime format.
ES ingest
PUT _ingest/pipeline/haproxy
{
"description": "LogForge-generated ingest pipeline for haproxy",
"processors": [
{
"grok": {
"field": "message",
"patterns": [
"%{SYSLOGTIMESTAMP:timestamp} lb01 haproxy\\[990\\]: %{HOSTPORT:ipv4_port} \\[%{DATA:literal}\\] https-in~ %{NOTSPACE:literal2} %{NOTSPACE:path} %{NUMBER:number} %{NUMBER:number2} - - %{NOTSPACE:literal3} %{NOTSPACE:path2} 0/0 \"%{HAPROXY_NOTDQUOTE:quoted_string}"
],
"pattern_definitions": {
"HAPROXY_NOTDQUOTE": "[^\"]*"
}
}
}
]
} - note grok: constant field "hostname" embedded as literal anchor "lb01" (varying=false)
- note grok: constant field "pid" embedded as literal anchor "990" (varying=false)
- note grok: field "path" (path): samples do not all match %{PATH}; using %{NOTSPACE} instead
- note grok: field "path2" (path): samples do not all match %{PATH}; using %{NOTSPACE} instead
- note grok: custom patterns emitted — save the '# custom patterns' block to a file in your patterns_dir
- note test in Kibana Dev Tools with: POST _ingest/pipeline/haproxy/_simulate (supply a docs[] array whose _source.message holds a sample line)
Graylog
# Grok patterns to add under System > Grok Patterns:
# (Graylog needs these custom patterns installed globally BEFORE the rule/extractor below will work.)
# HAPROXY_NOTDQUOTE [^"]*
# --- Graylog processing pipeline rule (primary) ---
# Paste under System > Pipelines > Manage rules, then attach the rule to a pipeline stage.
rule "haproxy-parse"
when
has_field("message")
then
let gp = grok(pattern: "%{SYSLOGTIMESTAMP:timestamp} lb01 haproxy\\[990\\]: %{HOSTPORT:ipv4_port} \\[%{DATA:literal}\\] https-in~ %{NOTSPACE:literal2} %{NOTSPACE:path} %{NUMBER:number} %{NUMBER:number2} - - %{NOTSPACE:literal3} %{NOTSPACE:path2} 0/0 \"%{HAPROXY_NOTDQUOTE:quoted_string}", value: to_string($message.message), only_named_captures: true);
set_fields(gp);
end
# --- Graylog import-ready extractor JSON (secondary) ---
# Save as a .json file and import under System > Inputs > (input) > Manage extractors > Actions > Import extractors.
{
"extractors": [
{
"title": "haproxy",
"extractor_type": "grok",
"converters": [],
"order": 0,
"cursor_strategy": "copy",
"source_field": "message",
"target_field": "",
"extractor_config": {
"grok_pattern": "%{SYSLOGTIMESTAMP:timestamp} lb01 haproxy\\[990\\]: %{HOSTPORT:ipv4_port} \\[%{DATA:literal}\\] https-in~ %{NOTSPACE:literal2} %{NOTSPACE:path} %{NUMBER:number} %{NUMBER:number2} - - %{NOTSPACE:literal3} %{NOTSPACE:path2} 0/0 \"%{HAPROXY_NOTDQUOTE:quoted_string}",
"named_captures_only": true
},
"condition_type": "none",
"condition_value": ""
}
],
"version": "5.0.0"
} - note grok: constant field "hostname" embedded as literal anchor "lb01" (varying=false)
- note grok: constant field "pid" embedded as literal anchor "990" (varying=false)
- note grok: field "path" (path): samples do not all match %{PATH}; using %{NOTSPACE} instead
- note grok: field "path2" (path): samples do not all match %{PATH}; using %{NOTSPACE} instead
- note grok: custom patterns emitted — save the '# custom patterns' block to a file in your patterns_dir
- note 1 custom grok pattern(s) (HAPROXY_NOTDQUOTE) must be installed globally first under System > Grok Patterns — see the block at the top of the output
- note primary artifact is the processing-pipeline rule; the extractor JSON is an equivalent import-ready alternative for the classic extractor UI
Datadog
logforge_rule %{date("MMM dd HH:mm:ss"):timestamp} lb01 haproxy\[990\]: %{ipv4:ipv4_port}:%{integer:ipv4_port_port} \[%{notSpace:literal}\] https-in~ %{notSpace:literal2} %{notSpace:path} %{number:number} %{number:number2} - - %{notSpace:literal3} %{notSpace:path2} 0/0 "%{quotedString:quoted_string} - note emitted rule name is "logforge_rule"; rename it to match your "haproxy" convention if desired
- note constant field "hostname" embedded as literal anchor "lb01" (varying=false)
- note constant field "pid" embedded as literal anchor "990" (varying=false)
- note field "ipv4_port" (ipv4_port) split into %{ipv4:…} and %{integer:…_port}
- note paste this line into a Grok Parser processor in a Datadog Log Pipeline; matchers are anchored left-to-right and rule whitespace matches log whitespace. Complex or multi-shape logs may need Helper Rules.
Fluent Bit
[PARSER]
Name haproxy
Format regex
Regex ^(?<timestamp>[A-Za-z]+ \d+ \d+:\d+:\d+) lb01 haproxy\[990\]: (?<ipv4_port>\d{1,3}(?:\.\d{1,3}){3}:\d{1,5}) \[(?<literal>\d+/[A-Za-z]+/\d+:\d+:\d+:\d+\.\d+)\] https-in~ (?<literal2>[A-Za-z]+/[A-Za-z]+\d+) (?<path>\d+/\d+/\d+/\d+/\d+) (?<number>-?\d+(?:\.\d+)?) (?<number2>-?\d+(?:\.\d+)?) - - (?<literal3>(?:----|[A-Za-z]+--)) (?<path2>\d+/\d+/\d+/\d+/\d+) 0/0 "(?<quoted_string>[^"]*)"$
Time_Key timestamp
Time_Format %b %d %H:%M:%S
# Fluentd <parse> block:
# <parse>
# @type regexp
# expression /^(?<timestamp>[A-Za-z]+ \d+ \d+:\d+:\d+) lb01 haproxy\[990\]: (?<ipv4_port>\d{1,3}(?:\.\d{1,3}){3}:\d{1,5}) \[(?<literal>\d+\/[A-Za-z]+\/\d+:\d+:\d+:\d+\.\d+)\] https-in~ (?<literal2>[A-Za-z]+\/[A-Za-z]+\d+) (?<path>\d+\/\d+\/\d+\/\d+\/\d+) (?<number>-?\d+(?:\.\d+)?) (?<number2>-?\d+(?:\.\d+)?) - - (?<literal3>(?:----|[A-Za-z]+--)) (?<path2>\d+\/\d+\/\d+\/\d+\/\d+) 0\/0 "(?<quoted_string>[^"]*)"$/
# time_key timestamp
# time_format %b %d %H:%M:%S
# </parse>
- note Time_Key set to "timestamp"; Time_Format "%b %d %H:%M:%S" is a best-effort strptime derived from the sample shape — verify it against your data (Fluent Bit uses %L for fractional seconds and %z for numeric offsets)
Vector
[transforms.haproxy_parse]
type = "remap"
inputs = ["REPLACE_WITH_SOURCE"]
source = '''
. |= parse_regex!(.message, r'(?P<timestamp>[A-Za-z]+ \d+ \d+:\d+:\d+) lb01 haproxy\[990\]: (?P<ipv4_port>\d{1,3}(?:\.\d{1,3}){3}:\d{1,5}) \[(?P<literal>\d+/[A-Za-z]+/\d+:\d+:\d+:\d+\.\d+)\] https-in~ (?P<literal2>[A-Za-z]+/[A-Za-z]+\d+) (?P<path>\d+/\d+/\d+/\d+/\d+) (?P<number>-?\d+(?:\.\d+)?) (?P<number2>-?\d+(?:\.\d+)?) - - (?P<literal3>(?:----|[A-Za-z]+--)) (?P<path2>\d+/\d+/\d+/\d+/\d+) 0/0 "(?P<quoted_string>[^"]*)"')
''' Loki
# promtail pipeline for "haproxy" (generated by LogForge)
# Add these stages under a scrape_config in your promtail config:
# scrape_configs:
# - job_name: haproxy
# pipeline_stages:
# (the stages below are indented to sit under pipeline_stages)
pipeline_stages:
- regex:
expression: '^(?P<timestamp>[A-Za-z]+ \d+ \d+:\d+:\d+) lb01 haproxy\[990\]: (?P<ipv4_port>\d{1,3}(?:\.\d{1,3}){3}:\d{1,5}) \[(?P<literal>\d+/[A-Za-z]+/\d+:\d+:\d+:\d+\.\d+)\] https-in~ (?P<literal2>[A-Za-z]+/[A-Za-z]+\d+) (?P<path>\d+/\d+/\d+/\d+/\d+) (?P<number>-?\d+(?:\.\d+)?) (?P<number2>-?\d+(?:\.\d+)?) - - (?P<literal3>(?:----|[A-Za-z]+--)) (?P<path2>\d+/\d+/\d+/\d+/\d+) 0/0 "(?P<quoted_string>[^"]*)"$'
- note no low-cardinality field found to promote to a Loki label — omitted the `- labels:` stage; every captured field stays in the extracted map for later stages
- note left in the extracted map (NOT promoted to labels — high cardinality would explode Loki streams): timestamp, ipv4_port, literal, literal2, path, number, number2, literal3, path2, quoted_string
syslog-ng
parser p_haproxy {
regexp-parser(
prefix(".haproxy.")
patterns("(?<timestamp>[A-Za-z]+ \\d+ \\d+:\\d+:\\d+) lb01 haproxy\\[990\\]: (?<ipv4_port>\\d{1,3}(?:\\.\\d{1,3}){3}:\\d{1,5}) \\[(?<literal>\\d+/[A-Za-z]+/\\d+:\\d+:\\d+:\\d+\\.\\d+)\\] https-in~ (?<literal2>[A-Za-z]+/[A-Za-z]+\\d+) (?<path>\\d+/\\d+/\\d+/\\d+/\\d+) (?<number>-?\\d+(?:\\.\\d+)?) (?<number2>-?\\d+(?:\\.\\d+)?) - - (?<literal3>(?:----|[A-Za-z]+--)) (?<path2>\\d+/\\d+/\\d+/\\d+/\\d+) 0/0 \"(?<quoted_string>[^\"]*)\"")
);
}; - note captured fields are stored as name-value pairs under the prefix ".haproxy." (e.g. a group (?<srcip>…) becomes ".haproxy.srcip")
FAQ
- What do the slash-separated numbers in an HAProxy log mean?
- There are three such groups in the HTTP log. The timers Tq/Tw/Tc/Tr/Tt (e.g. 0/0/1/12/13) are request, queue, connect, response, and total time in milliseconds. The connection counts actconn/feconn/beconn/srv_conn/retries track concurrency and retries. srv_queue/backend_queue give queue positions. Each token must be split on its slashes into sub-fields.
- What is the HAProxy termination state field?
- It is the ---- style four-character code (e.g. sD--, PR--, CD--). The first character is the session-termination cause and the second is the TCP/HTTP state when it ended. It is the primary field for diagnosing why a request failed — timeouts, client or server aborts, and proxy-side denials each have distinct codes. A clean request shows ----.
- Why does HAProxy log to syslog instead of a file?
- HAProxy has no native file logging by design; it sends logs to a syslog endpoint (a local /dev/log socket or a remote server) via the "log" directive. This keeps HAProxy fast and non-blocking and delegates rotation and storage to syslog. To capture the HTTP format shown here you also need "option httplog" on the frontend.
- How do I read HAProxy request latency from the log?
- Use the timers group Tq/Tw/Tc/Tr/Tt. Tt is the total time from accept to close; Tr is the server response time; Tc is the connect time; Tw is time queued; Tq is time spent receiving the request. A high Tr points at a slow backend, a high Tw at saturation/queueing, and a high Tc at connection problems.
Try it on your own HAProxy HTTP lines
Paste a few real lines, review the detected fields, and copy whichever format your stack needs. Free, no account, nothing uploaded.
Open this sample in LogForge →