v0.1.3: avoid regex re-compile on every message
4 files changed, 36 insertions(+), 24 deletions(-)

M aiosyslogd/config.py
M aiosyslogd/rfc5424.py
M aiosyslogd/server.py
M pyproject.toml
M aiosyslogd/config.py +6 -3
@@ 30,7 30,8 @@ def _create_default_config(path: str) ->
     with open(path, "w") as f:
         toml.dump(DEFAULT_CONFIG, f)
     print(
-        f"Default configuration file created. Please review '{path}' and restart the server if needed."
+        f"Default configuration file created. Please review '{path}' "
+        "and restart the server if needed."
     )
     return DEFAULT_CONFIG
 

          
@@ 42,8 43,10 @@ def load_config() -> Dict[str, Any]:
     It first checks for the 'AIOSYSLOGD_CONFIG' environment variable for a custom path.
     If the variable is not set, it falls back to 'aiosyslogd.toml' in the current directory.
 
-    - If a custom path is specified and the file doesn't exist, the server will exit with an error.
-    - If the default file ('aiosyslogd.toml') doesn't exist, it will be created automatically.
+    - If a custom path is specified and the file doesn't exist,
+      the server will exit with an error.
+    - If the default file ('aiosyslogd.toml') doesn't exist,
+      it will be created automatically.
     """
     config_path_from_env: str | None = os.environ.get("AIOSYSLOGD_CONFIG")
 

          
M aiosyslogd/rfc5424.py +16 -11
@@ 3,6 3,10 @@ from datetime import datetime, UTC
 from typing import Dict
 import re
 
+# --- RFC 5424 and RFC 3164 Syslog Message Patterns ---
+# These patterns are compiled here to avoid repeated compilation.
+
+# Pattern for RFC 5424: <PRI>VER TS HOST APP PID MSGID SD MSG
 RFC5424_PATTERN: re.Pattern[str] = re.compile(
     r"<(?P<pri>\d+)>"
     r"(?P<ver>\d+)\s"

          
@@ 16,23 20,24 @@ RFC5424_PATTERN: re.Pattern[str] = re.co
     re.DOTALL,
 )
 
+# Pattern for RFC 3164: <PRI>MMM DD HH:MM:SS HOSTNAME TAG[PID]: MSG
+# Made the colon after the tag optional and adjusted tag capture.
+RFC3164_PATTERN: re.Pattern[str] = re.compile(
+    r"<(?P<pri>\d{1,3})>"
+    r"(?P<mon>\w{3})\s+(?P<day>\d{1,2})\s+(?P<hr>\d{2}):(?P<min>\d{2}):(?P<sec>\d{2})"
+    r"\s+(?P<host>[\w\-\.]+)"
+    r"\s+(?P<tag>\S+?)(:|\s-)?\s"  # Flexible tag/separator matching
+    r"(?P<msg>.*)",
+    re.DOTALL,
+)
+
 
 def convert_rfc3164_to_rfc5424(message: str, debug_mode: bool = False) -> str:
     """
     Converts a best-effort RFC 3164 syslog message to an RFC 5424 message.
     This version is more flexible to handle formats like FortiGate's.
     """
-    # Pattern for RFC 3164: <PRI>MMM DD HH:MM:SS HOSTNAME TAG[PID]: MSG
-    # Made the colon after the tag optional and adjusted tag capture.
-    pattern: re.Pattern[str] = re.compile(
-        r"<(?P<pri>\d{1,3})>"
-        r"(?P<mon>\w{3})\s+(?P<day>\d{1,2})\s+(?P<hr>\d{2}):(?P<min>\d{2}):(?P<sec>\d{2})"
-        r"\s+(?P<host>[\w\-\.]+)"
-        r"\s+(?P<tag>\S+?)(:|\s-)?\s"  # Flexible tag/separator matching
-        r"(?P<msg>.*)",
-        re.DOTALL,
-    )
-    match: re.Match[str] | None = pattern.match(message)
+    match: re.Match[str] | None = RFC3164_PATTERN.match(message)
 
     if not match:
         if debug_mode:

          
M aiosyslogd/server.py +13 -9
@@ 63,9 63,8 @@ class SyslogUDPServer(asyncio.DatagramPr
         server = cls(host, port)
         print(f"aiosyslogd starting on UDP {host}:{port}...")
         if SQL_WRITE:
-            print(
-                f"SQLite writing ENABLED to '{SQLITE_DB_PATH}'. Batch size: {BATCH_SIZE}, Timeout: {BATCH_TIMEOUT}s"
-            )
+            print(f"SQLite writing ENABLED to '{SQLITE_DB_PATH}.")
+            print(f"Batch size: {BATCH_SIZE}, Timeout: {BATCH_TIMEOUT}s")
             await server.connect_to_sqlite()
         if DEBUG:
             print("Debug mode is ON.")

          
@@ 237,25 236,30 @@ class SyslogUDPServer(asyncio.DatagramPr
 
         async with self.db.cursor() as cursor:
             await cursor.execute(
-                f"SELECT name FROM sqlite_master WHERE type='table' AND name='{table_name}'"
+                "SELECT name FROM sqlite_master "
+                f"WHERE type='table' AND name='{table_name}'"
             )
             if await cursor.fetchone() is None:
                 if DEBUG:
                     print(
-                        f"Creating new tables for {year_month}: {table_name}, {fts_table_name}"
+                        "Creating new tables for "
+                        f"{year_month}: {table_name}, {fts_table_name}"
                     )
                 await self.db.execute(
                     f"""CREATE TABLE {table_name} (
-                    ID INTEGER PRIMARY KEY AUTOINCREMENT, Facility INTEGER, Priority INTEGER,
-                    FromHost TEXT, InfoUnitID INTEGER, ReceivedAt TIMESTAMP, DeviceReportedTime TIMESTAMP,
+                    ID INTEGER PRIMARY KEY AUTOINCREMENT, Facility INTEGER,
+                    Priority INTEGER, FromHost TEXT, InfoUnitID INTEGER,
+                    ReceivedAt TIMESTAMP, DeviceReportedTime TIMESTAMP,
                     SysLogTag TEXT, ProcessID TEXT, Message TEXT
                 )"""
                 )
                 await self.db.execute(
-                    f"CREATE INDEX idx_ReceivedAt_{year_month} ON {table_name} (ReceivedAt)"
+                    f"CREATE INDEX idx_ReceivedAt_{year_month} "
+                    f"ON {table_name} (ReceivedAt)"
                 )
                 await self.db.execute(
-                    f"CREATE VIRTUAL TABLE {fts_table_name} USING fts5(Message, content='{table_name}', content_rowid='ID')"
+                    f"CREATE VIRTUAL TABLE {fts_table_name} "
+                    f"USING fts5(Message, content='{table_name}', content_rowid='ID')"
                 )
                 await self.db.commit()
         return table_name

          
M pyproject.toml +1 -1
@@ 1,6 1,6 @@ 
 [project]
 name = "aiosyslogd"
-version = "0.1.2"
+version = "0.1.3"
 description = "Asynchronous Syslog server using asyncio, with an optional uvloop integration and SQLite backend."
 authors = [
     {name = "Chaiwat Suttipongsakul",email = "cwt@bashell.com"}