- plugins/syslog: RFC 5424 and RFC 3164 parsing, RFC 6587 TCP framing. - Lines are queued as they arrive and stored every 5 s through a Holding, so a storage stall delays them rather than losing them. - A new sender is an offered source, counted and not kept, until an admin adds it on the Syslog page (name, host); it can be ignored instead. - The listener follows the plugin's settings (port, UDP, TCP), rebinding within seconds of a save; a port it can't open is said on the page. - Hourly ring per source: age window and byte cap from Settings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
62 lines
2.6 KiB
Python
62 lines
2.6 KiB
Python
"""Syslog messages read into their parts (#5999): RFC 5424, RFC 3164 with and
|
|
without a hostname, a message with no PRI, and TCP framing (RFC 6587)."""
|
|
from datetime import datetime, timezone
|
|
|
|
from plugins.syslog.parse import MAX_MESSAGE, facility_name, frames, parse, severity_name
|
|
|
|
|
|
def test_rfc5424_with_structured_data_and_a_zone():
|
|
m = parse(b'<165>1 2026-10-19T01:12:03.5+00:00 synnas kernel 1234 ID47 '
|
|
b'[exampleSDID@32473 iut="3" eventSource="App\\"lication"] nfsd: server not responding')
|
|
assert (facility_name(m.facility), severity_name(m.severity)) == ("local4", "notice")
|
|
assert m.sent_at == datetime(2026, 10, 19, 1, 12, 3, 500000, tzinfo=timezone.utc)
|
|
assert (m.hostname, m.app, m.text) == ("synnas", "kernel", "nfsd: server not responding")
|
|
|
|
|
|
def test_rfc5424_nil_fields_and_no_zone_keep_no_sent_time():
|
|
m = parse("<14>1 2026-10-19T01:12:03 - - - - - plain text")
|
|
assert m.sent_at is None and (m.hostname, m.app, m.text) == ("", "", "plain text")
|
|
|
|
|
|
def test_bsd_with_hostname_and_tag():
|
|
m = parse(b"<38>Oct 9 22:14:15 adm-nas sshd[4121]: Accepted publickey for admin")
|
|
assert (m.facility, m.severity) == (4, 6)
|
|
assert m.sent_at is None # no year, no zone: never trusted
|
|
assert (m.hostname, m.app, m.text) == ("adm-nas", "sshd", "Accepted publickey for admin")
|
|
|
|
|
|
def test_bsd_without_hostname_reads_the_tag():
|
|
m = parse("<30>Oct 19 01:12:03 rsyncd[881]: connect from 192.168.0.4")
|
|
assert (m.hostname, m.app, m.text) == ("", "rsyncd", "connect from 192.168.0.4")
|
|
|
|
|
|
def test_no_pri_is_user_notice_and_an_unknown_shape_is_kept_whole():
|
|
m = parse(b"something the device made up\n")
|
|
assert (m.facility, m.severity) == (1, 5)
|
|
assert m.text == "something the device made up"
|
|
assert parse(b"<999>not a pri").text == "<999>not a pri"
|
|
|
|
|
|
def test_a_long_message_is_cut_and_bad_bytes_kept_readable():
|
|
m = parse(b"<13>" + b"x" * (MAX_MESSAGE + 50))
|
|
assert len(m.text) == MAX_MESSAGE
|
|
assert "�" in parse(b"<13>caf\xe9").text
|
|
|
|
|
|
def test_frames_octet_counted_and_newline_ended_with_a_partial_left():
|
|
buf = bytearray(b"11 <13>hello 1<14>second line\n<15>third")
|
|
assert frames(buf) == [b"<13>hello 1", b"<14>second line"]
|
|
assert buf == bytearray(b"<15>third")
|
|
buf += b" part\n"
|
|
assert frames(buf) == [b"<15>third part"] and not buf
|
|
|
|
|
|
def test_frames_waits_for_the_rest_of_a_counted_message():
|
|
buf = bytearray(b"20 <13>only part")
|
|
assert frames(buf) == [] and buf == bytearray(b"20 <13>only part")
|
|
|
|
|
|
def test_a_count_too_large_is_read_as_text():
|
|
buf = bytearray(b"9999999 is not a count\n")
|
|
assert frames(buf) == [b"9999999 is not a count"]
|