"""Log module."""
import logging
import os
import socket
root_logger = logging.getLogger() # pylint: disable=invalid-name
irc_logger = logging.getLogger('spicerack_irc_announce') # pylint: disable=invalid-name
[docs]class IRCSocketHandler(logging.Handler):
"""Log handler for logmsgbot on #wikimedia-operation.
Sends log events to a tcpircbot server for relay to an IRC channel.
"""
[docs] def __init__(self, host, port, username):
"""Initialize the IRC socket handler.
Arguments:
host (str): tcpircbot hostname.
port (int): tcpircbot listening port.
username (str): the user to refer in the IRC messages.
"""
super().__init__()
self.addr = (host, port)
self.username = username
self.level = logging.INFO
[docs] def emit(self, record):
"""According to Python logging.Handler interface.
See https://docs.python.org/3/library/logging.html#handler-objects
"""
message = '!log {msg} ({user}@{host})'.format(
msg=record.getMessage(), user=self.username, host=socket.gethostname())
sock = None
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(1.0)
sock.connect(self.addr)
sock.sendall(message.encode('utf-8'))
except OSError:
self.handleError(record)
finally:
if sock is not None:
sock.close()
[docs]class FilterOutCumin(logging.Filter):
"""A logging output filter to filter out Cumin's logs."""
[docs] def filter(self, record):
"""Filter out Cumin's log messages.
Arguments:
record (logging.LogRecord):
Returns:
int: 0 if the record should be filtered out, non-zero if it should be included. According to Python's
logging interface, see: https://docs.python.org/3/library/logging.html#filter-objects
"""
if record.name == 'cumin' or record.name.startswith('cumin.'):
return 0 # Filter it out
return 1
[docs]def setup_logging(base_path, name, user, dry_run=True, host=None, port=0):
"""Setup the root logger instance.
Arguments:
base_path (str, path-like object): the base path where to save the logs.
name (str): the name of log file to use without extension.
dry_run (bool, optional): whether this is a dry-run.
host (str, optional): the tcpircbot hostname for the IRC logging.
port (int, optional): the tcpircbot port for the IRC logging.
"""
base_path = str(base_path) # Since Python 3.6 it could be a path-like object
os.makedirs(base_path, mode=0o755, exist_ok=True)
# Default INFO logging
formatter = logging.Formatter(fmt='%(asctime)s [%(levelname)s] %(message)s')
handler = logging.FileHandler(os.path.join(base_path, '{name}.log'.format(name=name)))
handler.setFormatter(formatter)
handler.setLevel(logging.INFO)
# Extended logging for detailed debugging
formatter_extended = logging.Formatter(
fmt='%(asctime)s [%(levelname)s %(filename)s:%(lineno)s in %(funcName)s] %(message)s')
handler_extended = logging.FileHandler(os.path.join(base_path, '{name}-extended.log'.format(name=name)))
handler_extended.setFormatter(formatter_extended)
handler_extended.setLevel(logging.DEBUG)
# Stderr logging
output_handler = logging.StreamHandler()
if dry_run:
output_handler.setFormatter(logging.Formatter(fmt='DRY-RUN: %(message)s'))
output_handler.setLevel(logging.DEBUG)
else:
output_handler.setLevel(logging.INFO)
output_handler.addFilter(FilterOutCumin())
root_logger.addHandler(handler)
root_logger.addHandler(handler_extended)
root_logger.addHandler(output_handler)
root_logger.raiseExceptions = False
root_logger.setLevel(logging.DEBUG)
if not dry_run and host is not None and port > 0:
irc_logger.addHandler(IRCSocketHandler(host, port, user))
irc_logger.setLevel(logging.INFO)
# Silence external noisy loggers
logging.getLogger('urllib3').setLevel(logging.WARNING)
logging.getLogger('requests').setLevel(logging.WARNING)
[docs]def log_task_start(message):
"""Log the start of a task both on the logs and IRC.
Arguments:
message (str): the message to be logged.
"""
irc_logger.info('START - %s', message)
[docs]def log_task_end(status, message):
"""Log the start of a task both on the logs and IRC.
Arguments:
status (str): the final status of the task.
message (str): the message to be logged.
"""
irc_logger.info('END (%s) - %s', status, message)