patch f7068578a6b2806e38c5c5bfc1c03b8e59a14455 Author: E. Bosch Date: Wed Dec 20 01:50:56 CET 2023 * Fix logging system. Remove logging options from tornado.log that were not working correctly in this setup and use the new options from irgramd ("log_file" and "log_level"). Defer first logs to be included in log file opened later. Improve option error handling. diff -rN -u old-irgramd/irgramd new-irgramd/irgramd --- old-irgramd/irgramd 2024-11-25 23:05:53.161238554 +0100 +++ new-irgramd/irgramd 2024-11-25 23:05:53.165238548 +0100 @@ -20,6 +20,7 @@ from irc import IRCHandler from telegram import TelegramHandler +from utils import parse_loglevel # IRC Telegram Daemon @@ -65,7 +66,16 @@ # Main Execution if __name__ == '__main__': - logger = logging.getLogger() + # Remove tornado.log options (ugly hacks but these must not be defined) + tornado.options.options.logging = None + tornado_log_options = tuple(x for x in tornado.options.options._options.keys() if x != 'help' and x != 'logging') + for opt in tornado_log_options: + del tornado.options.options._options[opt] + # and reuse "--logging" to document empty "--" ;) + tornado.options.options._options['logging'].help = 'Stop parsing options' + for att in ('name', 'metavar', 'group_name', 'default'): + setattr(tornado.options.options._options['logging'], att, '') + # Define irgramd options tornado.options.define('api_hash', default=None, metavar='HASH', help='Telegram API Hash for your account (obtained from https://my.telegram.org/apps)') tornado.options.define('api_id', type=int, default=None, metavar='ID', help='Telegram API ID for your account (obtained from https://my.telegram.org/apps)') tornado.options.define('ask_code', default=False, help='Ask authentication code (sent by Telegram) in console instead of "code" service command in IRC') @@ -82,6 +92,8 @@ tornado.options.define('irc_nicks', type=str, multiple=True, metavar='nick,..', help='List of nicks allowed for IRC, if `pam` and optionally `pam_group` are set, PAM authentication will be used instead') tornado.options.define('irc_password', default='', metavar='PASSWORD', help='Password for IRC authentication, if `pam` is set, PAM authentication will be used instead') tornado.options.define('irc_port', type=int, default=None, metavar='PORT', help='Port to listen on for IRC. (default 6667, default with TLS 6697)') + tornado.options.define('log_file', default=None, metavar='PATH', help='File where logs are appended, if not set will be stderr') + tornado.options.define('log_level', default='INFO', metavar='DEBUG|INFO|WARNING|ERROR|CRITICAL|NONE', help='The log level (and any higher to it) that will be logged') tornado.options.define('media_dir', default=None, metavar='PATH', help='Directory where Telegram media files are downloaded, default "media" in `config_dir`') tornado.options.define('media_url', default=None, metavar='BASE_URL', help='Base URL for media files, should be configured in the external (to irgramd) webserver') tornado.options.define('pam', default=False, help='Use PAM for IRC authentication, if not set you should set `irc_password`') @@ -97,27 +109,51 @@ tornado.options.define('tls', default=False, help='Use TLS/SSL encrypted connection for IRC server') tornado.options.define('tls_cert', default=None, metavar='CERTFILE', help='IRC server certificate chain for TLS/SSL, also can contain private key if not defined with `tls_key`') tornado.options.define('tls_key', default=None, metavar='KEYFILE', help='IRC server private key for TLS/SSL') - # parse cmd line first time to get --config and --config_dir - tornado.options.parse_command_line() + try: + # parse cmd line first time to get --config and --config_dir + tornado.options.parse_command_line() + except Exception as exc: + print(exc) + exit(1) config_file = os.path.expanduser(tornado.options.options.config) config_dir = os.path.expanduser(tornado.options.options.config_dir) if not os.path.exists(config_dir): os.makedirs(config_dir) - logger.info('Configuration Directory: %s', config_dir) + defered_logs = [(logging.INFO, 'Configuration Directory: %s', config_dir)] if not os.path.isabs(config_file): config_file = os.path.join(config_dir, config_file) if os.path.isfile(config_file): - logger.info('Using configuration file: %s', config_file) - tornado.options.parse_config_file(config_file) + defered_logs.append((logging.INFO, 'Using configuration file: %s', config_file)) + try: + tornado.options.parse_config_file(config_file) + except Exception as exc: + print(exc) + exit(1) else: - logger.warning('Configuration file not present, using only command line options and defaults') + defered_logs.append((logging.WARNING, 'Configuration file not present, using only command line options and defaults')) # parse cmd line second time to override file options tornado.options.parse_command_line() options = tornado.options.options.as_dict() options['config_dir'] = config_dir + # configure logging + loglevel = parse_loglevel(options['log_level']) + if loglevel == False: + print("Option 'log_level' requires one of these values: {}".format(tornado.options.options._options['log-level'].metavar)) + exit(1) + logger_formats = { 'datefmt':'%Y-%m-%d %H:%M:%S', 'format':'[%(levelname).1s %(asctime)s %(module)s:%(lineno)d] %(message)s' } + logger = logging.getLogger() + if options['log_file']: + logging.basicConfig(filename=options['log_file'], level=loglevel, **logger_formats) + else: + logging.basicConfig(level=loglevel, **logger_formats) + + for log in defered_logs: + logger.log(*log) + + # main loop irc_server = IRCTelegramd(logger, options) loop = asyncio.new_event_loop() loop.run_until_complete(irc_server.run(options)) diff -rN -u old-irgramd/utils.py new-irgramd/utils.py --- old-irgramd/utils.py 2024-11-25 23:05:53.165238548 +0100 +++ new-irgramd/utils.py 2024-11-25 23:05:53.165238548 +0100 @@ -13,6 +13,7 @@ import datetime import zoneinfo import difflib +import logging # Constants @@ -214,3 +215,13 @@ def format_timestamp(format, tz, date): date_local = date.astimezone(zoneinfo.ZoneInfo(tz)) return date_local.strftime(format) + +def parse_loglevel(level): + levelu = level.upper() + if levelu == 'NONE': + l = None + elif levelu in ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'): + l = getattr(logging, levelu) + else: + l = False + return l