2014-03-23 16:04:03 +01:00
|
|
|
"""This module holds functions and classes responsible for formatting the log output
|
|
|
|
both to a file and to the console.
|
|
|
|
"""
|
2013-06-23 22:30:41 +02:00
|
|
|
import logging
|
|
|
|
|
|
|
|
|
2014-08-21 14:40:11 -07:00
|
|
|
def get_console_handler(debug, colorize):
|
2014-07-09 22:20:20 +02:00
|
|
|
"""Returns a log handler for the console
|
|
|
|
The handler color codes the different log levels
|
|
|
|
|
|
|
|
:params bool debug: Whether to set the log level to DEBUG (otherwise INFO)
|
2014-08-21 14:40:11 -07:00
|
|
|
:params bool colorize: Whether to colorize console output
|
2014-07-09 22:20:20 +02:00
|
|
|
:return: The console logging handler
|
|
|
|
"""
|
|
|
|
# Create a console log handler
|
|
|
|
import sys
|
|
|
|
console_handler = logging.StreamHandler(sys.stderr)
|
2014-08-21 14:40:11 -07:00
|
|
|
if colorize:
|
|
|
|
# We want to colorize the output to the console, so we add a formatter
|
|
|
|
console_handler.setFormatter(ConsoleFormatter())
|
2014-07-09 22:20:20 +02:00
|
|
|
# Set the log level depending on the debug argument
|
|
|
|
if debug:
|
|
|
|
console_handler.setLevel(logging.DEBUG)
|
|
|
|
else:
|
|
|
|
console_handler.setLevel(logging.INFO)
|
|
|
|
return console_handler
|
|
|
|
|
|
|
|
|
|
|
|
def get_file_handler(path, debug):
|
|
|
|
"""Returns a log handler for the given path
|
|
|
|
If the parent directory of the logpath does not exist it will be created
|
|
|
|
The handler outputs relative timestamps (to when it was created)
|
|
|
|
|
|
|
|
:params str path: The full path to the logfile
|
|
|
|
:params bool debug: Whether to set the log level to DEBUG (otherwise INFO)
|
|
|
|
:return: The file logging handler
|
|
|
|
"""
|
|
|
|
import os.path
|
|
|
|
if not os.path.exists(os.path.dirname(path)):
|
|
|
|
os.makedirs(os.path.dirname(path))
|
|
|
|
# Create the log handler
|
|
|
|
file_handler = logging.FileHandler(path)
|
|
|
|
# Absolute timestamps are rather useless when bootstrapping, it's much more interesting
|
|
|
|
# to see how long things take, so we log in a relative format instead
|
|
|
|
file_handler.setFormatter(FileFormatter('[%(relativeCreated)s] %(levelname)s: %(message)s'))
|
|
|
|
# The file log handler always logs everything
|
|
|
|
file_handler.setLevel(logging.DEBUG)
|
|
|
|
return file_handler
|
|
|
|
|
|
|
|
|
2014-03-23 23:53:20 +01:00
|
|
|
def get_log_filename(manifest_path):
|
2014-03-23 16:04:03 +01:00
|
|
|
"""Returns the path to a logfile given a manifest
|
|
|
|
The logfile name is constructed from the current timestamp and the basename of the manifest
|
|
|
|
|
2014-05-04 19:31:53 +02:00
|
|
|
:param str manifest_path: The path to the manifest
|
|
|
|
:return: The path to the logfile
|
|
|
|
:rtype: str
|
2014-03-23 16:04:03 +01:00
|
|
|
"""
|
2013-06-23 22:30:41 +02:00
|
|
|
import os.path
|
|
|
|
from datetime import datetime
|
|
|
|
|
|
|
|
manifest_basename = os.path.basename(manifest_path)
|
|
|
|
manifest_name, _ = os.path.splitext(manifest_basename)
|
|
|
|
timestamp = datetime.now().strftime('%Y%m%d%H%M%S')
|
2014-05-03 22:24:13 +02:00
|
|
|
filename = '{timestamp}_{name}.log'.format(timestamp=timestamp, name=manifest_name)
|
2014-03-23 23:53:20 +01:00
|
|
|
return filename
|
2013-06-23 22:30:41 +02:00
|
|
|
|
2013-06-26 20:14:37 +02:00
|
|
|
|
2013-06-23 22:30:41 +02:00
|
|
|
class ConsoleFormatter(logging.Formatter):
|
2014-03-23 16:04:03 +01:00
|
|
|
"""Formats log statements for the console
|
|
|
|
"""
|
2013-06-26 20:14:37 +02:00
|
|
|
level_colors = {logging.ERROR: 'red',
|
|
|
|
logging.WARNING: 'magenta',
|
|
|
|
logging.INFO: 'blue',
|
|
|
|
}
|
|
|
|
|
2013-06-23 22:30:41 +02:00
|
|
|
def format(self, record):
|
2013-06-24 23:12:39 +02:00
|
|
|
if(record.levelno in self.level_colors):
|
2014-03-23 16:04:03 +01:00
|
|
|
# Colorize the message if we have a color for it (DEBUG has no color)
|
2013-06-26 20:14:37 +02:00
|
|
|
from termcolor import colored
|
2013-06-24 23:12:39 +02:00
|
|
|
record.msg = colored(record.msg, self.level_colors[record.levelno])
|
2013-06-23 22:30:41 +02:00
|
|
|
return super(ConsoleFormatter, self).format(record)
|
|
|
|
|
|
|
|
|
|
|
|
class FileFormatter(logging.Formatter):
|
2014-03-23 16:04:03 +01:00
|
|
|
"""Formats log statements for output to file
|
|
|
|
Currently this is just a stub
|
|
|
|
"""
|
2013-06-23 22:30:41 +02:00
|
|
|
def format(self, record):
|
2013-06-24 23:12:39 +02:00
|
|
|
return super(FileFormatter, self).format(record)
|
2014-11-24 18:54:31 +01:00
|
|
|
|
|
|
|
|
|
|
|
class LogForwarder(logging.Handler):
|
|
|
|
|
|
|
|
def __init__(self, level=logging.NOTSET):
|
|
|
|
self.server = None
|
|
|
|
super(LogForwarder, self).__init__(level)
|
|
|
|
|
|
|
|
def set_server(self, server):
|
|
|
|
self.server = server
|
|
|
|
|
|
|
|
def emit(self, record):
|
|
|
|
if self.server is not None:
|
|
|
|
if record.exc_info is not None:
|
|
|
|
import traceback
|
|
|
|
exc_type, exc_value, exc_traceback = record.exc_info
|
|
|
|
record.exc_info = traceback.print_exception(exc_type, exc_value, exc_traceback)
|
|
|
|
# TODO: Use serpent instead
|
|
|
|
import pickle
|
|
|
|
self.server.handle(pickle.dumps(record))
|
|
|
|
|
|
|
|
|
|
|
|
class LogServer(object):
|
|
|
|
|
|
|
|
def handle(self, pickled_record):
|
|
|
|
import pickle
|
|
|
|
record = pickle.loads(pickled_record)
|
|
|
|
log = logging.getLogger()
|
|
|
|
log.handle(record)
|