hat.syslog.server.main
Syslog Server main module
1"""Syslog Server main module""" 2 3from pathlib import Path 4import argparse 5import asyncio 6import contextlib 7import logging.config 8import sys 9import time 10 11import appdirs 12 13from hat import aio 14from hat import json 15 16from hat.syslog.server.backend import create_backend 17from hat.syslog.server.syslog import create_syslog_server 18from hat.syslog.server.ui import create_web_server 19 20 21mlog: logging.Logger = logging.getLogger('hat.syslog.server.main') 22"""Module logger""" 23 24user_data_dir: Path = Path(appdirs.user_data_dir('hat')) 25"""User data directory path""" 26 27default_log_level: str = 'INFO' 28"""Default console log level""" 29 30default_ui_addr: str = 'http://0.0.0.0:23020' 31"""Default UI listening address""" 32 33default_db_path: Path = user_data_dir / 'syslog.db' 34"""Default DB file path""" 35 36default_db_low_size: int = int(1e6) 37"""Default DB low size count""" 38 39default_db_high_size: int = int(1e7) 40"""Default DB high size count""" 41 42default_syslog_addrs: list[str] = ['tcp://0.0.0.0:6514', 43 'udp://0.0.0.0:6514'] 44"""Default syslog listening addresses""" 45 46 47def create_argument_parser() -> argparse.ArgumentParser: 48 """Create argument parser""" 49 parser = argparse.ArgumentParser( 50 description="Syslog Server listening for TCP and/or UDP messages. " 51 "If listening addresses are not provided, Syslog Server " 52 "listens on 'tcp://0.0.0.0:6514' and " 53 "'udp://0.0.0.0:6514'.") 54 parser.add_argument( 55 '--log-level', metavar='LEVEL', default=default_log_level, 56 choices=['DEBUG', 'INFO', 'WARNING', 'ERROR'], 57 help=f"console log level (default {default_log_level})") 58 parser.add_argument( 59 '--log-access', action='store_true', 60 help="include access logs in console log") 61 parser.add_argument( 62 '--log-conf', metavar='PATH', type=Path, default=None, 63 help="path to json/yaml/toml custom log configuration as specified by " 64 "https://docs.python.org/3/library/logging.config.html#configuration-dictionary-schema") # NOQA 65 parser.add_argument( 66 '--ui-addr', metavar='ADDR', default=default_ui_addr, 67 help=f"UI listening address (default {default_ui_addr})") 68 parser.add_argument( 69 '--db-path', metavar='PATH', type=Path, default=default_db_path, 70 help="sqlite database file path " 71 "(default $XDG_DATA_HOME/hat/syslog.db)") 72 parser.add_argument( 73 '--db-low-size', metavar='N', type=int, default=default_db_low_size, 74 help=f"number of messages kept in database after database " 75 f"cleanup (default {default_db_low_size})") 76 parser.add_argument( 77 '--db-high-size', metavar='N', type=int, default=default_db_high_size, 78 help=f"number of messages that will trigger database cleanup " 79 f"(default {default_db_high_size})") 80 parser.add_argument( 81 '--db-enable-archive', action='store_true', 82 help="should messages, deleted during database cleanup, be kept " 83 "in archive files") 84 parser.add_argument( 85 '--db-disable-journal', action='store_true', 86 help="disable sqlite journaling") 87 parser.add_argument( 88 '--syslog-pem-path', metavar='PATH', type=Path, default=None, 89 help="certificate PEM path used in case of tls syslog") 90 parser.add_argument( 91 'syslog_addrs', metavar='ADDR', nargs='*', 92 default=default_syslog_addrs, 93 help="syslog listening address formated as <prot>://<host>:<port> " 94 "(<prot> is 'tcp', 'udp' or 'tls'; <host> is host name or IP " 95 "address; <port> is UDP/TCP port)") 96 return parser 97 98 99def get_logging_conf(log_level: str, 100 log_access: bool, 101 log_conf_path: Path | None 102 ) -> json.Data: 103 """Get logging configuration""" 104 if log_conf_path: 105 conf = json.decode_file(log_conf_path) 106 107 validator = json.DefaultSchemaValidator(json.json_schema_repo) 108 validator.validate('hat-json://logging.yaml', conf) 109 110 return conf 111 112 return { 113 'version': 1, 114 'formatters': { 115 'console_formater': { 116 'format': '[%(asctime)s %(levelname)s %(name)s] %(message)s'}}, 117 'handlers': { 118 'console_handler': { 119 'class': 'logging.StreamHandler', 120 'formatter': 'console_formater', 121 'level': log_level}}, 122 'root': { 123 'level': log_level, 124 'handlers': ['console_handler']}, 125 'loggers': { 126 'aiohttp.access': { 127 'propagate': log_access}}, 128 'disable_existing_loggers': False} 129 130 131def main(): 132 """Syslog Server""" 133 parser = create_argument_parser() 134 args = parser.parse_args() 135 136 logging_conf = get_logging_conf(log_level=args.log_level, 137 log_access=args.log_access, 138 log_conf_path=args.log_conf) 139 logging.config.dictConfig(logging_conf) 140 141 aio.init_asyncio() 142 with contextlib.suppress(asyncio.CancelledError): 143 aio.run_asyncio(async_main(ui_addr=args.ui_addr, 144 db_path=args.db_path, 145 db_low_size=args.db_low_size, 146 db_high_size=args.db_high_size, 147 db_enable_archive=args.db_enable_archive, 148 db_disable_journal=args.db_disable_journal, 149 syslog_pem_path=args.syslog_pem_path, 150 syslog_addrs=args.syslog_addrs)) 151 152 153async def async_main(ui_addr: str, 154 db_path: Path, 155 db_low_size: int, 156 db_high_size: int, 157 db_enable_archive: bool, 158 db_disable_journal: bool, 159 syslog_pem_path: Path | None, 160 syslog_addrs: list[str]): 161 """Syslog Server async main""" 162 async_group = aio.Group() 163 164 async def on_msg(msg): 165 await backend.register(time.time(), msg) 166 167 async def async_close(): 168 await async_group.async_close() 169 await asyncio.sleep(0.1) 170 171 try: 172 mlog.debug("creating backend...") 173 backend = await _create_resource(async_group, create_backend, 174 db_path, db_low_size, db_high_size, 175 db_enable_archive, db_disable_journal) 176 177 mlog.debug("creating web server...") 178 await _create_resource(async_group, create_web_server, ui_addr, 179 backend) 180 181 mlog.debug("creating syslog servers...") 182 for syslog_addr in syslog_addrs: 183 await _create_resource(async_group, create_syslog_server, 184 syslog_addr, on_msg, syslog_pem_path) 185 186 mlog.debug("initialization done") 187 await async_group.wait_closing() 188 189 finally: 190 mlog.debug("closing...") 191 await aio.uncancellable(async_close()) 192 193 194async def _create_resource(async_group, fn, *args): 195 resource = await async_group.spawn(fn, *args) 196 async_group.spawn(aio.call_on_cancel, resource.async_close) 197 async_group.spawn(aio.call_on_done, resource.wait_closing(), 198 async_group.close) 199 return resource 200 201 202if __name__ == '__main__': 203 sys.argv[0] = 'hat-syslog-server' 204 sys.exit(main())
Module logger
user_data_dir: pathlib.Path =
PosixPath('/home/runner/.local/share/hat')
User data directory path
default_log_level: str =
'INFO'
Default console log level
default_ui_addr: str =
'http://0.0.0.0:23020'
Default UI listening address
default_db_path: pathlib.Path =
PosixPath('/home/runner/.local/share/hat/syslog.db')
Default DB file path
default_db_low_size: int =
1000000
Default DB low size count
default_db_high_size: int =
10000000
Default DB high size count
default_syslog_addrs: list[str] =
['tcp://0.0.0.0:6514', 'udp://0.0.0.0:6514']
Default syslog listening addresses
def
create_argument_parser() -> argparse.ArgumentParser:
48def create_argument_parser() -> argparse.ArgumentParser: 49 """Create argument parser""" 50 parser = argparse.ArgumentParser( 51 description="Syslog Server listening for TCP and/or UDP messages. " 52 "If listening addresses are not provided, Syslog Server " 53 "listens on 'tcp://0.0.0.0:6514' and " 54 "'udp://0.0.0.0:6514'.") 55 parser.add_argument( 56 '--log-level', metavar='LEVEL', default=default_log_level, 57 choices=['DEBUG', 'INFO', 'WARNING', 'ERROR'], 58 help=f"console log level (default {default_log_level})") 59 parser.add_argument( 60 '--log-access', action='store_true', 61 help="include access logs in console log") 62 parser.add_argument( 63 '--log-conf', metavar='PATH', type=Path, default=None, 64 help="path to json/yaml/toml custom log configuration as specified by " 65 "https://docs.python.org/3/library/logging.config.html#configuration-dictionary-schema") # NOQA 66 parser.add_argument( 67 '--ui-addr', metavar='ADDR', default=default_ui_addr, 68 help=f"UI listening address (default {default_ui_addr})") 69 parser.add_argument( 70 '--db-path', metavar='PATH', type=Path, default=default_db_path, 71 help="sqlite database file path " 72 "(default $XDG_DATA_HOME/hat/syslog.db)") 73 parser.add_argument( 74 '--db-low-size', metavar='N', type=int, default=default_db_low_size, 75 help=f"number of messages kept in database after database " 76 f"cleanup (default {default_db_low_size})") 77 parser.add_argument( 78 '--db-high-size', metavar='N', type=int, default=default_db_high_size, 79 help=f"number of messages that will trigger database cleanup " 80 f"(default {default_db_high_size})") 81 parser.add_argument( 82 '--db-enable-archive', action='store_true', 83 help="should messages, deleted during database cleanup, be kept " 84 "in archive files") 85 parser.add_argument( 86 '--db-disable-journal', action='store_true', 87 help="disable sqlite journaling") 88 parser.add_argument( 89 '--syslog-pem-path', metavar='PATH', type=Path, default=None, 90 help="certificate PEM path used in case of tls syslog") 91 parser.add_argument( 92 'syslog_addrs', metavar='ADDR', nargs='*', 93 default=default_syslog_addrs, 94 help="syslog listening address formated as <prot>://<host>:<port> " 95 "(<prot> is 'tcp', 'udp' or 'tls'; <host> is host name or IP " 96 "address; <port> is UDP/TCP port)") 97 return parser
Create argument parser
def
get_logging_conf( log_level: str, log_access: bool, log_conf_path: pathlib.Path | None) -> None | bool | int | float | str | List[ForwardRef('Data')] | Dict[str, ForwardRef('Data')]:
100def get_logging_conf(log_level: str, 101 log_access: bool, 102 log_conf_path: Path | None 103 ) -> json.Data: 104 """Get logging configuration""" 105 if log_conf_path: 106 conf = json.decode_file(log_conf_path) 107 108 validator = json.DefaultSchemaValidator(json.json_schema_repo) 109 validator.validate('hat-json://logging.yaml', conf) 110 111 return conf 112 113 return { 114 'version': 1, 115 'formatters': { 116 'console_formater': { 117 'format': '[%(asctime)s %(levelname)s %(name)s] %(message)s'}}, 118 'handlers': { 119 'console_handler': { 120 'class': 'logging.StreamHandler', 121 'formatter': 'console_formater', 122 'level': log_level}}, 123 'root': { 124 'level': log_level, 125 'handlers': ['console_handler']}, 126 'loggers': { 127 'aiohttp.access': { 128 'propagate': log_access}}, 129 'disable_existing_loggers': False}
Get logging configuration
def
main():
132def main(): 133 """Syslog Server""" 134 parser = create_argument_parser() 135 args = parser.parse_args() 136 137 logging_conf = get_logging_conf(log_level=args.log_level, 138 log_access=args.log_access, 139 log_conf_path=args.log_conf) 140 logging.config.dictConfig(logging_conf) 141 142 aio.init_asyncio() 143 with contextlib.suppress(asyncio.CancelledError): 144 aio.run_asyncio(async_main(ui_addr=args.ui_addr, 145 db_path=args.db_path, 146 db_low_size=args.db_low_size, 147 db_high_size=args.db_high_size, 148 db_enable_archive=args.db_enable_archive, 149 db_disable_journal=args.db_disable_journal, 150 syslog_pem_path=args.syslog_pem_path, 151 syslog_addrs=args.syslog_addrs))
Syslog Server
async def
async_main( ui_addr: str, db_path: pathlib.Path, db_low_size: int, db_high_size: int, db_enable_archive: bool, db_disable_journal: bool, syslog_pem_path: pathlib.Path | None, syslog_addrs: list[str]):
154async def async_main(ui_addr: str, 155 db_path: Path, 156 db_low_size: int, 157 db_high_size: int, 158 db_enable_archive: bool, 159 db_disable_journal: bool, 160 syslog_pem_path: Path | None, 161 syslog_addrs: list[str]): 162 """Syslog Server async main""" 163 async_group = aio.Group() 164 165 async def on_msg(msg): 166 await backend.register(time.time(), msg) 167 168 async def async_close(): 169 await async_group.async_close() 170 await asyncio.sleep(0.1) 171 172 try: 173 mlog.debug("creating backend...") 174 backend = await _create_resource(async_group, create_backend, 175 db_path, db_low_size, db_high_size, 176 db_enable_archive, db_disable_journal) 177 178 mlog.debug("creating web server...") 179 await _create_resource(async_group, create_web_server, ui_addr, 180 backend) 181 182 mlog.debug("creating syslog servers...") 183 for syslog_addr in syslog_addrs: 184 await _create_resource(async_group, create_syslog_server, 185 syslog_addr, on_msg, syslog_pem_path) 186 187 mlog.debug("initialization done") 188 await async_group.wait_closing() 189 190 finally: 191 mlog.debug("closing...") 192 await aio.uncancellable(async_close())
Syslog Server async main