syslog format: PRI, RFC 3164 vs RFC 5424
How the syslog PRI value encodes facility and severity, the RFC 3164 and RFC 5424 header fields, and generating valid syslog messages in both formats with Eventum.
Testing a collector's parsing rules, an rsyslog or syslog-ng ingestion pipeline, or a SIEM's syslog listener requires messages shaped like what a real server, network appliance, or application actually sends. A live host can produce them, but its output arrives on its own schedule, and most environments run a mix of two incompatible message shapes at once: an older format still emitted by routers, firewalls, and most Unix daemons by default, and a newer one used by modern applications, cloud logging agents, and SIEMs that want structured metadata attached to a message. A parser built or tested against only one of them breaks silently the day a device speaking the other one gets connected.
Eventum renders both shapes directly from a template — the header fields in the exact order each format defines, the PRI value computed from a real facility and severity — and delivers the result over TCP or UDP to whatever the collector listens on, so the parsing rules for either format get tested with no real device plugged in to produce them.
The two syslog headers
syslog is standardized twice, and both versions remain in active use. RFC 3164 (2001) wrote down the format BSD Unix systems — and the network hardware that copied them — had already been using informally for years. RFC 5424 (2009) replaces it with a stricter, extensible message meant to fix that older format's loosest edges: an unspecified character encoding, a timestamp with no year or time zone, and no structured way to carry metadata beyond free text. Both wrap a single line of text in the same bracketed PRI value, but everything after it — field order, what is mandatory, what a field is allowed to contain — differs enough that a parser built for one will mis-split or reject a message from the other.
Every syslog message, either version, opens with the PRI value: a decimal number in angle brackets, <PRI>, encoding two things at once. Facility says what kind of process produced the message — the kernel, the mail system, an FTP daemon, one of eight generic slots reserved for local use — and severity says how urgent it is, from 0 (Emergency) down to 7 (Debug). The two combine through a single formula, shared by both RFCs:
PRI = Facility * 8 + Severity| Facility | Meaning |
|---|---|
| 0 | kernel messages |
| 1 | user-level messages |
| 2 | mail system |
| 3 | system daemons |
| 4 | security/authorization messages |
| 5 | messages generated internally by syslogd |
| 6 | line printer subsystem |
| 7 | network news subsystem |
| 8 | UUCP subsystem |
| 9 | clock daemon |
| 10 | security/authorization messages |
| 11 | FTP daemon |
| 12 | NTP subsystem |
| 13 | log audit |
| 14 | log alert |
| 15 | clock daemon |
| 16-23 | local use 0 through 7 |
Facilities 4 and 10, and 9 and 15, share the same description in the specification's own table — not a mistake in the table above, just two device classes the RFCs never fully separated.
| Severity | Level |
|---|---|
| 0 | Emergency: system is unusable |
| 1 | Alert: action must be taken immediately |
| 2 | Critical: critical conditions |
| 3 | Error: error conditions |
| 4 | Warning: warning conditions |
| 5 | Notice: normal but significant condition |
| 6 | Informational: informational messages |
| 7 | Debug: debug-level messages |
A failed SSH login is worth flagging but nowhere near an emergency: facility 4 (security/authorization messages) and severity 4 (Warning) give PRI = 4 * 8 + 4 = 36, written <36>. Both examples below reuse this exact combination.
RFC 3164 documents what's typically called BSD syslog: the format nearly every router, firewall, and long-lived Unix daemon still emits by default. A message has three parts — PRI, then a HEADER carrying just a timestamp and a hostname, then a MSG that carries everything else as free text, conventionally shaped as a TAG followed by CONTENT:
<PRI>Mmm dd hh:mm:ss HOSTNAME TAG: CONTENTTIMESTAMP uses a fixed English month abbreviation, no year, and no time zone — local time, implicitly. A day of the month under 10 is padded with a space rather than a zero, so the 1st of July is Jul 1, not Jul 01. HOSTNAME is the device's bare name, never its domain. Everything after it is the MSG part, which the specification itself leaves unstructured: convention, not the RFC, shapes its first token as a TAG of alphanumeric characters naming the process, cut off at the first character that isn't alphanumeric — commonly a : or a [ opening a PID — with everything from that character onward read as CONTENT.
Applied to a failed SSH login from a gateway host:
<36>Jul 11 12:00:00 web-gw-03 sshd[8419]: Failed password for admin from 203.0.113.7 port 51422 ssh2| Part | Value | Meaning |
|---|---|---|
| PRI | <36> | facility 4 (security/authorization messages) x8 + severity 4 (Warning) |
| TIMESTAMP | Jul 11 12:00:00 | Mmm dd hh:mm:ss, local time, no year, no zone |
| HOSTNAME | web-gw-03 | device name only, never a domain |
| TAG | sshd | alphanumeric process name; stops at the first [ |
| CONTENT | [8419]: Failed password for admin from 203.0.113.7 port 51422 ssh2 | PID convention plus the message text |
TAG and CONTENT together are the MSG part.
RFC 5424 replaces that free-text tail with a fully structured, positional header, plus an optional block of machine-parseable metadata ahead of the message:
<PRI>VERSION TIMESTAMP HOSTNAME APP-NAME PROCID MSGID STRUCTURED-DATA MSGVERSION is currently always 1. TIMESTAMP is a full RFC 3339 date and time — four-digit year, T as the date/time separator, up to six digits of sub-second precision, and an explicit offset (Z for UTC, or +hh:mm / -hh:mm) — a stricter, unambiguous replacement for RFC 3164's zoneless timestamp. HOSTNAME, APP-NAME, PROCID, and MSGID identify the device, the originating program, its process ID or instance, and an optional message-type identifier; the specification allows any of them, TIMESTAMP included, to be replaced with a single - — the NILVALUE — when the sender has nothing to put there.
STRUCTURED-DATA, itself replaceable with - when empty, holds zero or more bracketed elements of machine-parseable metadata, each an ID followed by space-separated key="value" pairs:
[SD-ID key1="value1" key2="value2"]Multiple elements concatenate directly, [id1 ...][id2 ...]. A literal ", \, or ] inside a value must be escaped as \", \\, or \], since left unescaped any of the three would be read as the end of the value or the element instead. The ID commonly carries a Private Enterprise Number after an @ — exampleSDID@32473 in the specification's own examples — so two vendors' element names never collide.
The same login failure, now as RFC 5424:
<36>1 2026-07-11T12:00:00+00:00 web-gw-03.example.com sshd 8419 - [sshAuth@32473 srcIp="203.0.113.7" user="admin" result="failed"] Failed password for admin from 203.0.113.7 port 51422 ssh2| Part | Value | Meaning |
|---|---|---|
| PRI | <36> | same facility and severity as the 3164 version |
| VERSION | 1 | fixed |
| TIMESTAMP | 2026-07-11T12:00:00+00:00 | RFC 3339 profile: date, T, time, explicit UTC offset |
| HOSTNAME | web-gw-03.example.com | FQDN is preferred here, unlike RFC 3164 |
| APP-NAME | sshd | originating program |
| PROCID | 8419 | OS process ID |
| MSGID | - | NILVALUE — no message-type identifier assigned |
| STRUCTURED-DATA | [sshAuth@32473 srcIp="203.0.113.7" user="admin" result="failed"] | one element, ID sshAuth@32473, three key-value pairs |
| MSG | Failed password for admin from 203.0.113.7 port 51422 ssh2 | free-text message, same content as the 3164 version |
RFC 3164 is still what most infrastructure emits by default — routers, firewalls, and the syslog daemons on most Linux distributions send it unless reconfigured — so a collector aimed at a real environment has to accept it. RFC 5424 is the better target for anything new: cloud logging agents, modern application frameworks, and any SIEM integration that needs a real home for key-value metadata instead of a private convention bolted onto free text.
Generate syslog with Eventum
The generator below simulates a gateway host's sshd reporting failed logins — the same underlying event, rendered as either an RFC 3164 line or an RFC 5424 line depending on which template is picked for a given timestamp.
The templates
Both templates share the same facility and severity — 4 and 4, the PRI <36> from the worked example above — computed inline rather than hardcoded, so changing either value in one place keeps the header correct. Fields are filled with module.rand, the template event plugin's built-in randomization module.
templates/auth-failure-3164.jinja renders the legacy header. Its timestamp uses Python's %e day-of-month directive, which pads a single-digit day with a space exactly as RFC 3164 requires, rather than %d, which would pad it with a zero:
{%- set pri = 4 * 8 + 4 -%}
{%- set host = "web-gw-03" -%}
{%- set pid = module.rand.number.integer(1000, 65535) -%}
{%- set user = module.rand.choice(["admin", "root", "deploy", "oracle"]) -%}
{%- set src_ip = module.rand.network.ip_v4_public() -%}
{%- set port = module.rand.number.integer(1024, 65535) -%}
<{{ pri }}>{{ timestamp.strftime('%b %e %H:%M:%S') }} {{ host }} sshd[{{ pid }}]: Failed password for {{ user }} from {{ src_ip }} port {{ port }} ssh2templates/auth-failure-5424.jinja renders the same event with the modern header. timestamp is already timezone-aware, so its own isoformat() is already a valid RFC 3339 TIMESTAMP — no manual formatting needed:
{%- set pri = 4 * 8 + 4 -%}
{%- set host = "web-gw-03.example.com" -%}
{%- set pid = module.rand.number.integer(1000, 65535) -%}
{%- set user = module.rand.choice(["admin", "root", "deploy", "oracle"]) -%}
{%- set src_ip = module.rand.network.ip_v4_public() -%}
{%- set port = module.rand.number.integer(1024, 65535) -%}
<{{ pri }}>1 {{ timestamp.isoformat() }} {{ host }} sshd {{ pid }} - [sshAuth@32473 srcIp="{{ src_ip }}" user="{{ user }}" result="failed"] Failed password for {{ user }} from {{ src_ip }} port {{ port }} ssh2The generator config
mode: chance picks one of the two templates per timestamp, weighted so the legacy format dominates — matching a real fleet where most devices still default to RFC 3164. A cron input ticks once a second, and a udp output ships each line to the collector's syslog port:
input:
- cron:
expression: "* * * * * *"
count: 1
event:
template:
mode: chance
templates:
- auth_failure_3164:
template: templates/auth-failure-3164.jinja
chance: 70
- auth_failure_5424:
template: templates/auth-failure-5424.jinja
chance: 30
output:
- udp:
host: siem.example.com
port: 514
separator: "\n"For reliable, ordered delivery instead of UDP's best-effort datagrams, replace udp with tcp and keep the same host and port — most rsyslog and syslog-ng installations listen on both by default. To inspect lines locally before pointing the generator at a real collector, replace the output with file or stdout: {}.
The result
Running the generator above produces a mix of both formats, each line independently valid:
<36>Jul 11 13:39:16 web-gw-03 sshd[10666]: Failed password for admin from 13.162.114.145 port 43966 ssh2<36>1 2026-07-11T13:39:22+00:00 web-gw-03.example.com sshd 15350 - [sshAuth@32473 srcIp="192.1.17.245" user="root" result="failed"] Failed password for root from 192.1.17.245 port 35952 ssh2Both carry the same PRI, <36> — facility 4 (security/authorization messages) x8 plus severity 4 (Warning) — because both describe the same kind of event; only the header shape around it differs. The first has no year or time zone and a bare hostname, true to RFC 3164. The second carries a version, an offset-qualified timestamp, an FQDN, and a structured-data element repeating the source IP and username the message text already states in prose — redundant here to show the syntax, but in a real integration this is exactly where fields a receiver should parse without scanning free text belong.
FAQ
Related
- The formats field guide covering every supported log and event shape
- The syslog over TCP/UDP delivery lesson for the transport, framing, and TLS choices behind actually shipping these lines to a collector
- The CEF lesson for wrapping a CEF line inside a syslog header
- The LEEF lesson for wrapping a LEEF line inside a syslog header
- The detection testing lesson for generating attack-shaped telemetry to test Sigma rules
LEEF format: header and delimiter
How LEEF structures its header and tab-delimited attributes, the 1.0-to-2.0 delimiter change, and generating valid LEEF events for QRadar with Eventum.
NDJSON format: newline-delimited JSON
How NDJSON (newline-delimited JSON, also called JSON Lines) differs from a JSON array, where log shippers and bulk-ingest APIs use it, and how to generate it with Eventum.