Repository: aristanetworks/anta (python)

Issue:
ANTA v1.x : VerifyLoggingTimestamp : RFE : Want user defined field for send cmd severity level
http://anta.arista.com/stable/api/tests.logging/#anta.tests.logging.VerifyLoggingTimestamp

As per the defaults EOS has debugging sev level and I have changed it to notifications

I notice in the code ANTA uses send cmd with hardcoded Sev = informational which is not generated on my EOS due to manual change to Notifications

I am not a dev, so cant raise PR
request this as new RFE where in where we have user defined field in catalog to set the logging sev to user's choice 


Pull request description:
fix(anta.tests): Updated Logging Module with Adding a user defined field for send cmd severity level
# Description
VerifyLoggingTimestamp: Added user defined filed for send command log severity level.

Fixes #922

# Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have run pre-commit for code linting and typing (`pre-commit run`)
- [x] I have made corresponding changes to the documentation
- [x] I have added tests that prove my fix is effective or that my feature works
- [x] New and existing unit tests pass locally with my changes (`tox -e testenv`)


Patch:
diff --git a/anta/custom_types.py b/anta/custom_types.py
index 3af27db9..5810620e 100644
--- a/anta/custom_types.py
+++ b/anta/custom_types.py
@@ -263,3 +263,4 @@ SnmpVersion = Literal["v1", "v2c", "v3"]
 SnmpHashingAlgorithm = Literal["MD5", "SHA", "SHA-224", "SHA-256", "SHA-384", "SHA-512"]
 SnmpEncryptionAlgorithm = Literal["AES-128", "AES-192", "AES-256", "DES"]
 DynamicVlanSource = Literal["dmf", "dot1x", "dynvtep", "evpn", "mlag", "mlagsync", "mvpn", "swfwd", "vccbfd"]
+LogSeverityLevel = Literal["alerts", "critical", "debugging", "emergencies", "errors", "informational", "notifications", "warnings"]


Test patch:
diff --git a/anta/tests/logging.py b/anta/tests/logging.py
index 4a1c594e..15ec3dd4 100644
--- a/anta/tests/logging.py
+++ b/anta/tests/logging.py
@@ -14,13 +14,12 @@ import re
 from ipaddress import IPv4Address
 from typing import TYPE_CHECKING, ClassVar
 
-from anta.models import AntaCommand, AntaTest
+from anta.custom_types import LogSeverityLevel
+from anta.models import AntaCommand, AntaTemplate, AntaTest
 
 if TYPE_CHECKING:
     import logging
 
-    from anta.models import AntaTemplate
-
 
 def _get_logging_states(logger: logging.Logger, command_output: str) -> str:
     """Parse `show logging` output and gets operational logging states used in the tests in this module.
@@ -201,35 +200,43 @@ class VerifyLoggingLogsGeneration(AntaTest):
 
     This test performs the following checks:
 
-      1. Sends a test log message at the **informational** level
-      2. Retrieves the most recent logs (last 30 seconds)
-      3. Verifies that the test message was successfully logged
-
-    !!! warning
-        EOS logging buffer should be set to severity level `informational` or higher for this test to work.
+      1. Sends a test log message at the specified severity log level.
+      2. Retrieves the most recent logs (last 30 seconds).
+      3. Verifies that the test message was successfully logged.
 
     Expected Results
     ----------------
     * Success: If logs are being generated and the test message is found in recent logs.
     * Failure: If any of the following occur:
-        - The test message is not found in recent logs
-        - The logging system is not capturing new messages
-        - No logs are being generated
+        - The test message is not found in recent logs.
+        - The logging system is not capturing new messages.
+        - No logs are being generated.
 
     Examples
     --------
     ```yaml
     anta.tests.logging:
       - VerifyLoggingLogsGeneration:
+          severity_level: informational
     ```
     """
 
     categories: ClassVar[list[str]] = ["logging"]
     commands: ClassVar[list[AntaCommand | AntaTemplate]] = [
-        AntaCommand(command="send log level informational message ANTA VerifyLoggingLogsGeneration validation", ofmt="text"),
-        AntaCommand(command="show logging informational last 30 seconds | grep ANTA", ofmt="text", use_cache=False),
+        AntaTemplate(template="send log level {severity_level} message ANTA VerifyLoggingLogsGeneration validation", ofmt="text"),
+        AntaTemplate(template="show logging {severity_level} last 30 seconds | grep ANTA", ofmt="text", use_cache=False),
     ]
 
+    class Input(AntaTest.Input):
+        """Input model for the VerifyLoggingLogsGeneration test."""
+
+        severity_level: LogSeverityLevel = "informational"
+        """Log severity level. Defaults to informational."""
+
+    def render(self, template: AntaTemplate) -> list[AntaCommand]:
+        """Render the template for log severity level in the input."""
+        return [template.render(severity_level=self.inputs.severity_level)]
+
     @AntaTest.anta_test
     def test(self) -> None:
         """Main test function for VerifyLoggingLogsGeneration."""
@@ -248,37 +255,45 @@ class VerifyLoggingHostname(AntaTest):
 
     This test performs the following checks:
 
-      1. Retrieves the device's configured FQDN
-      2. Sends a test log message at the **informational** level
-      3. Retrieves the most recent logs (last 30 seconds)
-      4. Verifies that the test message includes the complete FQDN of the device
-
-    !!! warning
-          EOS logging buffer should be set to severity level `informational` or higher for this test to work.
+      1. Retrieves the device's configured FQDN.
+      2. Sends a test log message at the specified severity log level.
+      3. Retrieves the most recent logs (last 30 seconds).
+      4. Verifies that the test message includes the complete FQDN of the device.
 
     Expected Results
     ----------------
     * Success: If logs are generated with the device's complete FQDN.
     * Failure: If any of the following occur:
-        - The test message is not found in recent logs
-        - The log message does not include the device's FQDN
-        - The FQDN in the log message doesn't match the configured FQDN
+        - The test message is not found in recent logs.
+        - The log message does not include the device's FQDN.
+        - The FQDN in the log message doesn't match the configured FQDN.
 
     Examples
     --------
     ```yaml
     anta.tests.logging:
       - VerifyLoggingHostname:
+          severity_level: informational
     ```
     """
 
     categories: ClassVar[list[str]] = ["logging"]
     commands: ClassVar[list[AntaCommand | AntaTemplate]] = [
         AntaCommand(command="show hostname", revision=1),
-        AntaCommand(command="send log level informational message ANTA VerifyLoggingHostname validation", ofmt="text"),
-        AntaCommand(command="show logging informational last 30 seconds | grep ANTA", ofmt="text", use_cache=False),
+        AntaTemplate(template="send log level {severity_level} message ANTA VerifyLoggingHostname validation", ofmt="text"),
+        AntaTemplate(template="show logging {severity_level} last 30 seconds | grep ANTA", ofmt="text", use_cache=False),
     ]
 
+    class Input(AntaTest.Input):
+        """Input model for the VerifyLoggingHostname test."""
+
+        severity_level: LogSeverityLevel = "informational"
+        """Log severity level. Defaults to informational."""
+
+    def render(self, template: AntaTemplate) -> list[AntaCommand]:
+        """Render the template for log severity level in the input."""
+        return [template.render(severity_level=self.inputs.severity_level)]
+
     @AntaTest.anta_test
     def test(self) -> None:
         """Main test function for VerifyLoggingHostname."""
@@ -303,37 +318,45 @@ class VerifyLoggingTimestamp(AntaTest):
 
     This test performs the following checks:
 
-      1. Sends a test log message at the **informational** level
-      2. Retrieves the most recent logs (last 30 seconds)
-      3. Verifies that the test message is present with a high-resolution RFC3339 timestamp format
-        - Example format: `2024-01-25T15:30:45.123456+00:00`
-        - Includes microsecond precision
-        - Contains timezone offset
-
-    !!! warning
-          EOS logging buffer should be set to severity level `informational` or higher for this test to work.
+      1. Sends a test log message at the specified severity log level.
+      2. Retrieves the most recent logs (last 30 seconds).
+      3. Verifies that the test message is present with a high-resolution RFC3339 timestamp format.
+        - Example format: `2024-01-25T15:30:45.123456+00:00`.
+        - Includes microsecond precision.
+        - Contains timezone offset.
 
     Expected Results
     ----------------
     * Success: If logs are generated with the correct high-resolution RFC3339 timestamp format.
     * Failure: If any of the following occur:
-        - The test message is not found in recent logs
-        - The timestamp format does not match the expected RFC3339 format
+        - The test message is not found in recent logs.
+        - The timestamp format does not match the expected RFC3339 format.
 
     Examples
     --------
     ```yaml
     anta.tests.logging:
       - VerifyLoggingTimestamp:
+          severity_level: informational
     ```
     """
 
     categories: ClassVar[list[str]] = ["logging"]
     commands: ClassVar[list[AntaCommand | AntaTemplate]] = [
-        AntaCommand(command="send log level informational message ANTA VerifyLoggingTimestamp validation", ofmt="text"),
-        AntaCommand(command="show logging informational last 30 seconds | grep ANTA", ofmt="text", use_cache=False),
+        AntaTemplate(template="send log level {severity_level} message ANTA VerifyLoggingTimestamp validation", ofmt="text"),
+        AntaTemplate(template="show logging {severity_level} last 30 seconds | grep ANTA", ofmt="text", use_cache=False),
     ]
 
+    class Input(AntaTest.Input):
+        """Input model for the VerifyLoggingTimestamp test."""
+
+        severity_level: LogSeverityLevel = "informational"
+        """Log severity level. Defaults to informational."""
+
+    def render(self, template: AntaTemplate) -> list[AntaCommand]:
+        """Render the template for log severity level in the input."""
+        return [template.render(severity_level=self.inputs.severity_level)]
+
     @AntaTest.anta_test
     def test(self) -> None:
         """Main test function for VerifyLoggingTimestamp."""
diff --git a/examples/tests.yaml b/examples/tests.yaml
index 6a3ff456..f47285a1 100644
--- a/examples/tests.yaml
+++ b/examples/tests.yaml
@@ -291,6 +291,7 @@ anta.tests.logging:
       # Verifies there are no syslog messages with a severity of ERRORS or higher.
   - VerifyLoggingHostname:
       # Verifies if logs are generated with the device FQDN.
+      severity_level: informational
   - VerifyLoggingHosts:
       # Verifies logging hosts (syslog servers) for a specified VRF.
       hosts:
@@ -299,6 +300,7 @@ anta.tests.logging:
       vrf: default
   - VerifyLoggingLogsGeneration:
       # Verifies if logs are generated.
+      severity_level: informational
   - VerifyLoggingPersistent:
       # Verifies if logging persistent is enabled and logs are saved in flash.
   - VerifyLoggingSourceIntf:
@@ -307,6 +309,7 @@ anta.tests.logging:
       vrf: default
   - VerifyLoggingTimestamp:
       # Verifies if logs are generated with the appropriate timestamp.
+      severity_level: informational
   - VerifySyslogLogging:
       # Verifies if syslog logging is enabled.
 anta.tests.mlag:
diff --git a/tests/units/anta_tests/test_logging.py b/tests/units/anta_tests/test_logging.py
index 0c140881..3022d4cd 100644
--- a/tests/units/anta_tests/test_logging.py
+++ b/tests/units/anta_tests/test_logging.py
@@ -167,14 +167,14 @@ DATA: list[dict[str, Any]] = [
             "2023-05-10T13:54:21.463497-05:00 NW-CORE.example.org ConfigAgent: %SYS-6-LOGMSG_INFO: "
             "Message from arista on command-api (10.22.1.107): ANTA VerifyLoggingLogsGeneration validation\n",
         ],
-        "inputs": None,
+        "inputs": {"severity_level": "informational"},
         "expected": {"result": "success"},
     },
     {
         "name": "failure",
         "test": VerifyLoggingLogsGeneration,
         "eos_data": ["", "Log Buffer:\n"],
-        "inputs": None,
+        "inputs": {"severity_level": "notifications"},
         "expected": {"result": "failure", "messages": ["Logs are not generated"]},
     },
     {
@@ -186,7 +186,7 @@ DATA: list[dict[str, Any]] = [
             "2023-05-10T15:41:44.701810-05:00 NW-CORE.example.org ConfigAgent: %SYS-6-LOGMSG_INFO: "
             "Message from arista on command-api (10.22.1.107): ANTA VerifyLoggingHostname validation\n",
         ],
-        "inputs": None,
+        "inputs": {"severity_level": "informational"},
         "expected": {"result": "success"},
     },
     {
@@ -195,10 +195,10 @@ DATA: list[dict[str, Any]] = [
         "eos_data": [
             {"hostname": "NW-CORE", "fqdn": "NW-CORE.example.org"},
             "",
-            "2023-05-10T13:54:21.463497-05:00 NW-CORE ConfigAgent: %SYS-6-LOGMSG_INFO: "
+            "2023-05-10T13:54:21.463497-05:00 NW-CORE ConfigAgent: %SYS-6-LOGMSG_NOTICE: "
             "Message from arista on command-api (10.22.1.107): ANTA VerifyLoggingLogsHostname validation\n",
         ],
-        "inputs": None,
+        "inputs": {"severity_level": "notifications"},
         "expected": {"result": "failure", "messages": ["Logs are not generated with the device FQDN"]},
     },
     {
@@ -211,7 +211,7 @@ DATA: list[dict[str, Any]] = [
             "2023-05-10T15:42:44.680813-05:00 NW-CORE.example.org ConfigAgent: %SYS-6-LOGMSG_INFO: "
             "Other log\n",
         ],
-        "inputs": None,
+        "inputs": {"severity_level": "informational"},
         "expected": {"result": "success"},
     },
     {
@@ -224,7 +224,7 @@ DATA: list[dict[str, Any]] = [
             "2023-05-10T15:42:44.680813+05:00 NW-CORE.example.org ConfigAgent: %SYS-6-LOGMSG_INFO: "
             "Other log\n",
         ],
-        "inputs": None,
+        "inputs": {"severity_level": "informational"},
         "expected": {"result": "success"},
     },
     {
@@ -232,10 +232,10 @@ DATA: list[dict[str, Any]] = [
         "test": VerifyLoggingTimestamp,
         "eos_data": [
             "",
-            "May 10 13:54:22 NE-CORE.example.org ConfigAgent: %SYS-6-LOGMSG_INFO: "
+            "May 10 13:54:22 NE-CORE.example.org ConfigAgent: %SYS-6-LOGMSG_ALERT: "
             "Message from arista on command-api (10.22.1.107): ANTA VerifyLoggingTimestamp validation\n",
         ],
-        "inputs": None,
+        "inputs": {"severity_level": "alerts"},
         "expected": {"result": "failure", "messages": ["Logs are not generated with the appropriate timestamp format"]},
     },
     {
@@ -243,9 +243,9 @@ DATA: list[dict[str, Any]] = [
         "test": VerifyLoggingTimestamp,
         "eos_data": [
             "",
-            "May 10 13:54:22 NE-CORE.example.org ConfigAgent: %SYS-6-LOGMSG_INFO: Message from arista on command-api (10.22.1.107): BLAH\n",
+            "May 10 13:54:22 NE-CORE.example.org ConfigAgent: %SYS-6-LOGMSG_NOTICE: Message from arista on command-api (10.22.1.107): BLAH\n",
         ],
-        "inputs": None,
+        "inputs": {"severity_level": "notifications"},
         "expected": {"result": "failure", "messages": ["Logs are not generated with the appropriate timestamp format"]},
     },
     {