diff --git a/example-conf.yml b/example-conf.yml index 0b9d3d0..7e7dbdd 100644 --- a/example-conf.yml +++ b/example-conf.yml @@ -12,11 +12,6 @@ bot: # Server description (shown in /links, /whois, etc.) serverdesc: PyLink Server - # Console log verbosity: see - # https://docs.python.org/3/library/logging.html#logging-levels - # for available settings. - loglevel: DEBUG - # Sets the fantasy command prefix for calling commands inside channels # (requires fantasy plugin). prefix: "." @@ -31,13 +26,45 @@ login: password: changeme logging: - # This configuration block defines channels that PyLink can log to. + # This configuration block defines targets that PyLink should log commands, + # errors, etc., to. + + # This sets the level for STDOUT logging, which is always enabled. Valid + # settings include DEBUG, INFO, WARNING, ERROR, and CRITICAL: see + # https://docs.python.org/3/library/logging.html#logging-levels for details. + stdout: INFO - # Note: DEBUG logging is not supported here: any log level settings below - # INFO be automatically raised to INFO. channels: + # Log to channels on the specified networks. + # Note: DEBUG logging is not supported here: any log level settings + # below INFO be automatically raised to INFO. + # Log messages are forwarded over relay, so you will get duplicate + # messages if you add log blocks for more than one channel in one + # relay. + loglevel: INFO + inspnet: - "#services" + - "#pylink-log" + + ts6net: + - "#services" + + files: + # Logs to file targets. These will be placed in the log/ folder in the + # PyLink directory, with a filename based on the current instance name + # and the target name defined: instancename-targetname.log + # Changing settings in this block will require a restart to take effect. + + # When running with ./pylink, this will create log/pylink-errors.log + # When running with ./pylink someconf.yml, this will create log/someconf-errors.log + - "errors" + loglevel: ERROR + + # Ditto above. When running with ./pylink, it will use log/pylink-commands.log + # When running with ./pylink someconf.yml, this will create log/someconf-commands.log + - "commands" + loglevel: INFO servers: inspnet: diff --git a/log.py b/log.py index b825637..4517d4e 100644 --- a/log.py +++ b/log.py @@ -12,36 +12,52 @@ import os from conf import conf, confname -level = conf['bot'].get('loglevel') or 'INFO' - -try: - level = getattr(logging, level.upper()) -except AttributeError: - print('ERROR: Invalid log level %r specified in config.' % level) - sys.exit(3) +stdout_level = conf['logging'].get('stdout') or 'INFO' +# Set the logging directory to $CURDIR/log, creating it if it doesn't +# already exist curdir = os.path.dirname(os.path.realpath(__file__)) logdir = os.path.join(curdir, 'log') -# Make sure our log/ directory exists os.makedirs(logdir, exist_ok=True) +# Basic logging setup, set up here on first import, logs to STDOUT based +# on the log level configured. _format = '%(asctime)s [%(levelname)s] %(message)s' -logging.basicConfig(level=level, format=_format) +logformatter = logging.Formatter(_format) +logging.basicConfig(level=stdout_level, format=_format) -# Set log file to $CURDIR/log/pylink -logformat = logging.Formatter(_format) -logfile = logging.FileHandler(os.path.join(logdir, '%s.log' % confname), mode='w') -logfile.setFormatter(logformat) - -global log +# Get the main logger object; plugins can import this variable for convenience. log = logging.getLogger() -log.addHandler(logfile) + +def makeFileLogger(filename, level=None): + """ + Initializes a file logging target with the given filename and level. + """ + # Use log names specific to the current instance, to prevent multiple + # PyLink instances from overwriting each others' log files. + target = os.path.join(logdir, '%s-%s.log' % (confname, filename)) + + filelogger = logging.FileHandler(target, mode='w') + filelogger.setFormatter(logformatter) + + if level: # Custom log level was defined, use that instead. + filelogger.setLevel(level) + + log.addHandler(filelogger) + + return filelogger + +# Set up file logging now, creating a file logger for each block. +files = conf['logging'].get('files') +if files: + for filename, config in files.items(): + makeFileLogger(filename, config.get('loglevel')) class PyLinkChannelLogger(logging.Handler): """ Log handler to log to channels in PyLink. """ - def __init__(self, irc, channels): + def __init__(self, irc, channels, level=None): super(PyLinkChannelLogger, self).__init__() self.irc = irc self.channels = channels @@ -53,7 +69,8 @@ class PyLinkChannelLogger(logging.Handler): # Log level has to be at least 20 (INFO) to prevent loops due # to outgoing messages being logged - loglevel = max(log.getEffectiveLevel(), 20) + level = level or log.getEffectiveLevel() + loglevel = max(level, 20) self.setLevel(loglevel) def emit(self, record):