summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdam Schultz <adam.schultz@live.com>2026-01-24 09:55:14 -0500
committerAdam Schultz <adam.schultz@live.com>2026-02-09 22:59:37 -0500
commit696df602557dd7b70b353c3b9cfb110c0908a2fe (patch)
treed7ec98633981069f21b0af09575bf1e53cf79f56
parent9707b90b9ddacd27b74ef235a6f5a8f585c2bc50 (diff)
downloadvyos-1x-696df602557dd7b70b353c3b9cfb110c0908a2fe.tar.gz
vyos-1x-696df602557dd7b70b353c3b9cfb110c0908a2fe.zip
T7756: Fix rsyslog configuration generation
1.) Fix both syslog and logrotate writing /etc/logrotate.d/vyos-rsyslog - system_syslog.py generates /etc/logrotate.d/vyos-rsyslog-user for user file logrotate - system_logs.py generates /etc/logrotate.d/vyos-rsyslog using configured rotation parameters 2.) Fix /run/rsyslog/rsyslog.conf ignoring configured size for /var/log/messages - system_syslog.py retrieves "system logs" config to set rotation limits - default rotation size is now based on configuration defaults 3.) prifilt strings for rsyslog are modified to use override syntax (e.g. ";facility.none") when all is combined with other facilities to honor per facility user intent Smoke test changes: Commonize configuration of syslog facilities Check that prifilt is sane based on configured facilities (i.e. overides only present when required) Test wildcard (all) only, specific facility only, and wildcard + specific facility
-rw-r--r--data/config-mode-dependencies/vyos-1x.json3
-rw-r--r--data/templates/logs/logrotate/vyos-rsyslog.j22
-rw-r--r--data/templates/rsyslog/logrotate.j28
-rw-r--r--data/templates/rsyslog/rsyslog.conf.j254
-rwxr-xr-xsmoketest/scripts/cli/test_system_logs.py12
-rwxr-xr-xsmoketest/scripts/cli/test_system_syslog.py148
-rwxr-xr-xsrc/conf_mode/system_logs.py8
-rwxr-xr-xsrc/conf_mode/system_syslog.py20
8 files changed, 176 insertions, 79 deletions
diff --git a/data/config-mode-dependencies/vyos-1x.json b/data/config-mode-dependencies/vyos-1x.json
index 47a83ca0a..2337cb74b 100644
--- a/data/config-mode-dependencies/vyos-1x.json
+++ b/data/config-mode-dependencies/vyos-1x.json
@@ -110,6 +110,9 @@
"ip_ipv6": ["system_ip", "system_ipv6"],
"sysctl": ["system_sysctl"]
},
+ "system_logs": {
+ "syslog": ["system_syslog"]
+ },
"system_sflow": {
"vpp_sflow": ["vpp_sflow"]
}
diff --git a/data/templates/logs/logrotate/vyos-rsyslog.j2 b/data/templates/logs/logrotate/vyos-rsyslog.j2
index f2e4d2ab2..23e5810cd 100644
--- a/data/templates/logs/logrotate/vyos-rsyslog.j2
+++ b/data/templates/logs/logrotate/vyos-rsyslog.j2
@@ -1,3 +1,4 @@
+### Autogenerated by system_logs.py ###
/var/log/messages {
create
missingok
@@ -10,4 +11,3 @@
/usr/lib/rsyslog/rsyslog-rotate
endscript
}
-
diff --git a/data/templates/rsyslog/logrotate.j2 b/data/templates/rsyslog/logrotate.j2
index b9689a1cf..4434a3a32 100644
--- a/data/templates/rsyslog/logrotate.j2
+++ b/data/templates/rsyslog/logrotate.j2
@@ -1,12 +1,4 @@
### Autogenerated by system_syslog.py ###
-/var/log/messages {
- missingok
- notifempty
- create
- rotate 5
- size=256k
-}
-
{% if file is vyos_defined %}
{% for file_name, file_options in file.items() %}
/var/log/user/{{ file_name }} {
diff --git a/data/templates/rsyslog/rsyslog.conf.j2 b/data/templates/rsyslog/rsyslog.conf.j2
index 714788df0..3eb6f258a 100644
--- a/data/templates/rsyslog/rsyslog.conf.j2
+++ b/data/templates/rsyslog/rsyslog.conf.j2
@@ -52,49 +52,61 @@ global(localHostname="{{ preserve_fqdn.host_name }}.{{ preserve_fqdn.domain_name
{% endif %}
{% endif %}
+{# Build a prifilt selector list with "all" excluding any explicitly-set facilities #}
+{% macro prifilt_selectors(facility_map) %}
+{# pass 1: collect explicit facilities #}
+{% set selectors = [] %}
+{% set keys = facility_map.keys() | list | sort %}
+{% set specific = [] %}
+{% for facility in keys %}
+{% if facility != 'all' %}
+{% set _ = specific.append(facility) %}
+{% endif %}
+{% endfor %}
+{# pass 2: build selectors; add ;fac.none exclusions to wildcard #}
+{% for facility in keys %}
+{% set opts = facility_map[facility] %}
+{% set level = opts.level.replace('all', 'debug') %}
+{% if facility == 'all' %}
+{% set ns = namespace(sel="*." ~ level) %}
+{% for sf in specific %}
+{% set ns.sel = ns.sel ~ ";" ~ sf ~ ".none" %}
+{% endfor %}
+{% set sel = ns.sel %}
+{% else %}
+{% set sel = facility ~ "." ~ level %}
+{% endif %}
+{% set _ = selectors.append(sel) %}
+{% endfor %}
+{{ selectors | join(',') }}
+{% endmacro %}
+
#### GLOBAL LOGGING ####
{% if local.facility is vyos_defined %}
-{% set tmp = [] %}
-{% if local.facility is vyos_defined %}
-{% for facility, facility_options in local.facility.items() %}
-{% set _ = tmp.append(facility.replace('all', '*') ~ "." ~ facility_options.level.replace('all', 'debug')) %}
-{% endfor %}
-if prifilt("{{ tmp | join(',') }}") then {
+if prifilt("{{ prifilt_selectors(local.facility) | trim }}") then {
action(
type="omfile"
file="/var/log/messages"
- rotation.sizeLimit="524288" # 512Kib - maximum filesize before rotation
+ rotation.sizeLimit="{{ logrotate_size_limit }}" # maximum filesize before rotation
rotation.sizeLimitCommand="/usr/sbin/logrotate {{ logrotate }}"
)
}
-{% endif %}
{% endif %}
#### CONSOLE LOGGING ####
{% if console.facility is vyos_defined %}
-{% set tmp = [] %}
-{% if console.facility is vyos_defined %}
-{% for facility, facility_options in console.facility.items() %}
-{% set _ = tmp.append(facility.replace('all', '*') ~ "." ~ facility_options.level.replace('all', 'debug')) %}
-{% endfor %}
-if prifilt("{{ tmp | join(',') }}") then {
+if prifilt("{{ prifilt_selectors(console.facility) | trim }}") then {
action(type="omfile" file="/dev/console")
}
-{% endif %}
{% endif %}
#### REMOTE LOGGING ####
{% if remote is vyos_defined %}
{% for remote_name, remote_options in remote.items() %}
-{% set tmp = [] %}
{% if remote_options.facility is vyos_defined %}
-{% for facility, facility_options in remote_options.facility.items() %}
-{% set _ = tmp.append(facility.replace('all', '*') ~ "." ~ facility_options.level.replace('all', 'debug')) %}
-{% endfor %}
-{% set _ = tmp.sort() %}
{% set tls = remote_options.tls %}
# Remote syslog to {{ remote_name }}
-if prifilt("{{ tmp | join(',') }}") then {
+if prifilt("{{ prifilt_selectors(remote_options.facility) | trim }}") then {
action(
type="omfwd"
# Remote syslog server where we send our logs to
diff --git a/smoketest/scripts/cli/test_system_logs.py b/smoketest/scripts/cli/test_system_logs.py
index 81fbf8048..ccddcdec6 100755
--- a/smoketest/scripts/cli/test_system_logs.py
+++ b/smoketest/scripts/cli/test_system_logs.py
@@ -18,18 +18,20 @@ import re
import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.utils.file import read_file
+from vyos.xml_ref import default_value
# path to logrotate configs
logrotate_atop_file = '/etc/logrotate.d/vyos-atop'
logrotate_rsyslog_file = '/etc/logrotate.d/vyos-rsyslog'
-# default values
-default_atop_maxsize = '10M'
-default_atop_rotate = '10'
-default_rsyslog_size = '1M'
-default_rsyslog_rotate = '10'
base_path = ['system', 'logs']
+# default values
+default_atop_maxsize = f"{default_value(base_path + ['logrotate', 'atop', 'max-size'])}M"
+default_atop_rotate = default_value(base_path + ['logrotate', 'atop', 'rotate'])
+default_rsyslog_size = f"{default_value(base_path + ['logrotate', 'messages', 'max-size'])}M"
+default_rsyslog_rotate = default_value(base_path + ['logrotate', 'messages', 'rotate'])
+
def logrotate_config_parse(file_path):
# read the file
diff --git a/smoketest/scripts/cli/test_system_syslog.py b/smoketest/scripts/cli/test_system_syslog.py
index 79fbe27ec..2d1e0aef4 100755
--- a/smoketest/scripts/cli/test_system_syslog.py
+++ b/smoketest/scripts/cli/test_system_syslog.py
@@ -30,6 +30,7 @@ RSYSLOG_CONF = '/run/rsyslog/rsyslog.conf'
CERT_DIR = '/etc/rsyslog.d/certs'
base_path = ['system', 'syslog']
+base_logs_path = ['system', 'logs']
pki_base = ['pki']
dummy_interface = 'dum372874'
@@ -94,6 +95,7 @@ class TestRSYSLOGService(VyOSUnitTestSHIM.TestCase):
# delete testing SYSLOG config
self.cli_delete(base_path)
+ self.cli_delete(base_logs_path)
self.cli_commit()
# The default syslog implementation should make syslog.service a
@@ -130,23 +132,115 @@ class TestRSYSLOGService(VyOSUnitTestSHIM.TestCase):
]
)
+ def _set_facilities(self, base, facility_map):
+ for facility, facility_options in facility_map.items():
+ level = facility_options['level']
+ self.cli_set(base + ['facility', facility, 'level'], value=level)
+
+ # Build prifilt selector strings the same way as rsyslog.conf.j2, e.g.
+ # "auth.info,*.notice;auth.none" when specific facilities coexist with "all".
+ def _prifilt_selectors(self, facility_map):
+ keys = sorted(facility_map)
+ specific = []
+ for facility in keys:
+ if facility != 'all':
+ specific.append(facility)
+
+ selectors = []
+ for facility in keys:
+ opts = facility_map[facility]
+ level = opts['level'].replace('all', 'debug')
+ if facility == 'all':
+ sel = f'*.{level}'
+ for sf in specific:
+ sel += f';{sf}.none'
+ else:
+ sel = f'{facility}.{level}'
+ selectors.append(sel)
+
+ prifilt = ','.join(selectors)
+ self._assert_prifilt_sane(prifilt, facility_map)
+ return prifilt
+
+ def _assert_prifilt_sane(self, prifilt, facility_map):
+ has_all = 'all' in facility_map
+ specific_facilities = sorted(f for f in facility_map if f != 'all')
+ self.assertTrue(prifilt)
+ self.assertNotIn(' ', prifilt)
+ parts = prifilt.split(',')
+ self.assertTrue(all(parts))
+ wildcard_parts = [p for p in parts if p.startswith('*.')]
+ if has_all:
+ self.assertEqual(len(wildcard_parts), 1)
+ wildcard = wildcard_parts[0]
+ if specific_facilities:
+ base, *exclusions = wildcard.split(';')
+ self.assertTrue(base.startswith('*.'))
+ expected = {f'{fac}.none' for fac in specific_facilities}
+ self.assertEqual(set(exclusions), expected)
+ else:
+ self.assertNotIn(';', wildcard)
+ else:
+ self.assertEqual(len(wildcard_parts), 0)
+ self.assertNotIn(';', prifilt)
+
+ for part in parts:
+ if ';' in part:
+ base, *exclusions = part.split(';')
+ self.assertTrue(base.startswith('*.'))
+ self.assertNotIn('*.none', base)
+ for ex in exclusions:
+ self.assertTrue(ex.endswith('.none'))
+ self.assertFalse(ex.startswith('*.'))
+ self.assertNotIn(',', ex)
+ else:
+ if part.startswith('*.'):
+ self.assertTrue(has_all)
+ continue
+ self.assertEqual(part.count('.'), 1)
+ self.assertFalse(part.startswith('*.'))
+
def test_console(self):
- level = 'warning'
- self.cli_set(base_path + ['console', 'facility', 'all', 'level'], value=level)
+ facility = {
+ 'all': {'level': 'warning'},
+ }
+ self._set_facilities(base_path + ['console'], facility)
self.cli_commit()
rsyslog_conf = get_config()
- config = [
- f'if prifilt("*.{level}") then {{', # {{ required to escape { in f-string
- 'action(type="omfile" file="/dev/console")',
- ]
- for tmp in config:
- self.assertIn(tmp, rsyslog_conf)
+ expected_prifilt = self._prifilt_selectors(facility)
+ self.assertIn(f'if prifilt("{expected_prifilt}") then {{', rsyslog_conf)
+ self.assertIn('action(type="omfile" file="/dev/console")', rsyslog_conf)
+
+ self.cli_delete(base_path + ['console'])
+ facility = {
+ 'auth': {'level': 'info'},
+ 'kern': {'level': 'debug'},
+ }
+ self._set_facilities(base_path + ['console'], facility)
+ self.cli_commit()
+
+ rsyslog_conf = get_config()
+ expected_prifilt = self._prifilt_selectors(facility)
+ self.assertIn(f'if prifilt("{expected_prifilt}") then {{', rsyslog_conf)
+ self.assertIn('action(type="omfile" file="/dev/console")', rsyslog_conf)
+
+ facility['all'] = {'level': 'notice'}
+ self._set_facilities(base_path + ['console'], facility)
+ self.cli_commit()
+
+ rsyslog_conf = get_config()
+ expected_prifilt = self._prifilt_selectors(facility)
+ self.assertIn(f'if prifilt("{expected_prifilt}") then {{', rsyslog_conf)
+ self.assertIn('action(type="omfile" file="/dev/console")', rsyslog_conf)
def test_basic(self):
hostname = 'vyos123'
domain_name = 'example.local'
default_marker_interval = default_value(base_path + ['marker', 'interval'])
+ default_rsyslog_max_size = default_value(
+ base_logs_path + ['logrotate', 'messages', 'max-size']
+ )
facility = {
'auth': {'level': 'info'},
@@ -158,9 +252,7 @@ class TestRSYSLOGService(VyOSUnitTestSHIM.TestCase):
self.cli_set(['system', 'domain-name'], value=domain_name)
self.cli_set(base_path + ['preserve-fqdn'])
- for tmp, tmp_options in facility.items():
- level = tmp_options['level']
- self.cli_set(base_path + ['local', 'facility', tmp, 'level'], value=level)
+ self._set_facilities(base_path + ['local'], facility)
self.cli_commit()
@@ -174,21 +266,13 @@ class TestRSYSLOGService(VyOSUnitTestSHIM.TestCase):
self.assertIn(e, config)
config = get_config('#### GLOBAL LOGGING ####')
- prifilt = []
- for tmp, tmp_options in facility.items():
- if tmp == 'all':
- tmp = '*'
- level = tmp_options['level']
- prifilt.append(f'{tmp}.{level}')
-
- prifilt.sort()
- prifilt = ','.join(prifilt)
-
- self.assertIn(f'if prifilt("{prifilt}") then {{', config)
+ expected_prifilt = self._prifilt_selectors(facility)
+ self.assertIn(f'if prifilt("{expected_prifilt}") then {{', config)
self.assertIn( ' action(', config)
self.assertIn( ' type="omfile"', config)
self.assertIn( ' file="/var/log/messages"', config)
- self.assertIn( ' rotation.sizeLimit="524288"', config)
+ size_limit = int(default_rsyslog_max_size) * 1024 * 1024
+ self.assertIn(f' rotation.sizeLimit="{size_limit}"', config)
self.assertIn( ' rotation.sizeLimitCommand="/usr/sbin/logrotate /etc/logrotate.d/vyos-rsyslog"', config)
self.cli_set(base_path + ['marker', 'disable'])
@@ -234,10 +318,7 @@ class TestRSYSLOGService(VyOSUnitTestSHIM.TestCase):
self.cli_set(remote_base + ['port'], value=remote_options['port'])
if 'facility' in remote_options:
- for facility, facility_options in remote_options['facility'].items():
- level = facility_options['level']
- self.cli_set(remote_base + ['facility', facility, 'level'],
- value=level)
+ self._set_facilities(remote_base, remote_options['facility'])
if 'format' in remote_options:
for format in remote_options['format']:
@@ -261,16 +342,9 @@ class TestRSYSLOGService(VyOSUnitTestSHIM.TestCase):
config = read_file(RSYSLOG_CONF)
for remote, remote_options in rhosts.items():
config = get_config(f'# Remote syslog to {remote}')
- prifilt = []
+ prifilt = ''
if 'facility' in remote_options:
- for facility, facility_options in remote_options['facility'].items():
- level = facility_options['level']
- if facility == 'all':
- facility = '*'
- prifilt.append(f'{facility}.{level}')
-
- prifilt.sort()
- prifilt = ','.join(prifilt)
+ prifilt = self._prifilt_selectors(remote_options['facility'])
if not prifilt:
# Skip test - as we do not render anything if no facility is set
continue
@@ -429,7 +503,7 @@ class TestRSYSLOGService(VyOSUnitTestSHIM.TestCase):
self.assertIn(f'StreamDriverPermittedPeers="{value}"', config)
if not tls:
- self.assertIn(f'StreamDriverAuthMode="anon"', config)
+ self.assertIn('StreamDriverAuthMode="anon"', config)
def test_remote_tls_protocol_udp(self):
remote_base = base_path + ['remote', '172.11.0.1']
diff --git a/src/conf_mode/system_logs.py b/src/conf_mode/system_logs.py
index f0e742d1b..f31986034 100755
--- a/src/conf_mode/system_logs.py
+++ b/src/conf_mode/system_logs.py
@@ -19,6 +19,8 @@ from sys import exit
from vyos import ConfigError
from vyos import airbag
from vyos.config import Config
+from vyos.configdep import set_dependents
+from vyos.configdep import call_dependents
from vyos.logger import syslog
from vyos.template import render
from vyos.utils.dict import dict_search
@@ -35,6 +37,8 @@ def get_config(config=None):
else:
conf = Config()
+ set_dependents('syslog', conf)
+
base = ['system', 'logs']
logs_config = conf.get_config_dict(base, key_mangling=('-', '_'),
get_first_key=True,
@@ -64,8 +68,8 @@ def generate(logs_config):
def apply(logs_config):
- # No further actions needed
- pass
+ # Ensure dependent config scripts (e.g., syslog) are re-run
+ call_dependents()
if __name__ == '__main__':
diff --git a/src/conf_mode/system_syslog.py b/src/conf_mode/system_syslog.py
index 90bb45c51..7e5b4f90c 100755
--- a/src/conf_mode/system_syslog.py
+++ b/src/conf_mode/system_syslog.py
@@ -40,7 +40,8 @@ airbag.enable()
cert_dir = '/etc/rsyslog.d/certs'
rsyslog_conf = '/run/rsyslog/rsyslog.conf'
-logrotate_conf = '/etc/logrotate.d/vyos-rsyslog'
+logrotate_user_conf = '/etc/logrotate.d/vyos-rsyslog-user'
+logrotate_messages_conf = '/etc/logrotate.d/vyos-rsyslog'
systemd_socket = 'syslog.socket'
systemd_service = systemd_services['syslog']
@@ -124,7 +125,16 @@ def get_config(config=None):
with_pki=True,
)
- syslog.update({ 'logrotate' : logrotate_conf })
+ syslog.update({ 'logrotate' : logrotate_messages_conf })
+
+ logs_config = conf.get_config_dict(
+ ['system', 'logs'],
+ key_mangling=('-', '_'),
+ get_first_key=True,
+ with_recursive_defaults=True,
+ )
+ max_size_mb = dict_search('logrotate.messages.max_size', logs_config)
+ syslog['logrotate_size_limit'] = int(max_size_mb) * 1024 * 1024
syslog = conf.merge_defaults(syslog, recursive=True)
if syslog.from_defaults(['local']):
@@ -192,8 +202,8 @@ def generate(syslog):
if not syslog:
if os.path.exists(rsyslog_conf):
os.unlink(rsyslog_conf)
- if os.path.exists(logrotate_conf):
- os.unlink(logrotate_conf)
+ if os.path.exists(logrotate_user_conf):
+ os.unlink(logrotate_user_conf)
return None
@@ -203,7 +213,7 @@ def generate(syslog):
_save_tls_certificates_for_remote(syslog, remote_options)
render(rsyslog_conf, 'rsyslog/rsyslog.conf.j2', syslog)
- render(logrotate_conf, 'rsyslog/logrotate.j2', syslog)
+ render(logrotate_user_conf, 'rsyslog/logrotate.j2', syslog)
return None
def apply(syslog):