Refactor logging setup to be more modular

This commit is contained in:
Anders Ingemann 2014-07-09 22:20:20 +02:00
parent 9d8821235f
commit 7fe9c1ba36
2 changed files with 77 additions and 54 deletions

View file

@ -4,6 +4,48 @@ both to a file and to the console.
import logging import logging
def get_console_handler(debug):
"""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)
:return: The console logging handler
"""
# Create a console log handler
import sys
console_handler = logging.StreamHandler(sys.stderr)
# We want to colorize the output to the console, so we add a formatter
console_handler.setFormatter(ConsoleFormatter())
# 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
def get_log_filename(manifest_path): def get_log_filename(manifest_path):
"""Returns the path to a logfile given a manifest """Returns the path to a logfile given a manifest
The logfile name is constructed from the current timestamp and the basename of the manifest The logfile name is constructed from the current timestamp and the basename of the manifest
@ -22,40 +64,6 @@ def get_log_filename(manifest_path):
return filename return filename
def setup_logger(logfile=None, debug=False):
"""Sets up the python logger to log to both a file and the console
:param str logfile: Path to a logfile
:param bool debug: Whether to log debug output to the console
"""
root = logging.getLogger()
# Make sure all logging statements are processed by our handlers, they decide the log level
root.setLevel(logging.NOTSET)
# Only enable logging to file if a destination was supplied
if logfile is not None:
# Create a file log handler
file_handler = logging.FileHandler(logfile)
# 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)
root.addHandler(file_handler)
# Create a console log handler
import sys
console_handler = logging.StreamHandler(sys.stderr)
# We want to colorize the output to the console, so we add a formatter
console_handler.setFormatter(ConsoleFormatter())
# Set the log level depending on the debug argument
if debug:
console_handler.setLevel(logging.DEBUG)
else:
console_handler.setLevel(logging.INFO)
root.addHandler(console_handler)
class ConsoleFormatter(logging.Formatter): class ConsoleFormatter(logging.Formatter):
"""Formats log statements for the console """Formats log statements for the console
""" """

View file

@ -17,20 +17,14 @@ def main():
if os.geteuid() != 0 and not opts['--dry-run']: if os.geteuid() != 0 and not opts['--dry-run']:
raise Exception('This program requires root privileges.') raise Exception('This program requires root privileges.')
import log # Set up logging
# Log to file unless --log is a single dash setup_loggers(opts)
if opts['--log'] != '-':
# Setup logging
if not os.path.exists(opts['--log']):
os.makedirs(opts['--log'])
log_filename = log.get_log_filename(opts['MANIFEST'])
logfile = os.path.join(opts['--log'], log_filename)
else:
logfile = None
log.setup_logger(logfile=logfile, debug=opts['--debug'])
# Everything has been set up, begin the bootstrapping process # Everything has been set up, begin the bootstrapping process
run(opts) run(opts['MANIFEST'],
debug=opts['--debug'],
pause_on_error=opts['--pause-on-error'],
dry_run=opts['--dry-run'])
def get_opts(): def get_opts():
@ -49,18 +43,39 @@ Options:
--debug Print debugging information --debug Print debugging information
-h, --help show this help -h, --help show this help
""" """
opts = docopt(usage) return docopt(usage)
return opts
def run(opts): def setup_loggers(opts):
"""Sets up the file and console loggers
:params dict opts: Dictionary of options from the commandline
"""
import logging
root = logging.getLogger()
root.setLevel(logging.NOTSET)
import log
# Log to file unless --log is a single dash
if opts['--log'] != '-':
import os.path
log_filename = log.get_log_filename(opts['MANIFEST'])
logpath = os.path.join(opts['--log'], log_filename)
file_handler = log.get_file_handler(path=logpath, debug=True)
root.addHandler(file_handler)
console_handler = log.get_console_handler(debug=opts['--debug'])
root.addHandler(console_handler)
def run(manifest_path, debug=False, pause_on_error=False, dry_run=False):
"""Runs the bootstrapping process """Runs the bootstrapping process
:params dict opts: Dictionary of options from the commandline :params dict opts: Dictionary of options from the commandline
""" """
# Load the manifest # Load the manifest
from manifest import Manifest from manifest import Manifest
manifest = Manifest(opts['MANIFEST']) manifest = Manifest(manifest_path)
# Get the tasklist # Get the tasklist
from tasklist import load_tasks from tasklist import load_tasks
@ -71,18 +86,18 @@ def run(opts):
# Create the bootstrap information object that'll be used throughout the bootstrapping process # Create the bootstrap information object that'll be used throughout the bootstrapping process
from bootstrapinfo import BootstrapInformation from bootstrapinfo import BootstrapInformation
bootstrap_info = BootstrapInformation(manifest=manifest, debug=opts['--debug']) bootstrap_info = BootstrapInformation(manifest=manifest, debug=debug)
try: try:
# Run all the tasks the tasklist has gathered # Run all the tasks the tasklist has gathered
tasklist.run(info=bootstrap_info, dry_run=opts['--dry-run']) tasklist.run(info=bootstrap_info, dry_run=dry_run)
# We're done! :-) # We're done! :-)
log.info('Successfully completed bootstrapping') log.info('Successfully completed bootstrapping')
return bootstrap_info return bootstrap_info
except (Exception, KeyboardInterrupt) as e: except (Exception, KeyboardInterrupt) as e:
# When an error occurs, log it and begin rollback # When an error occurs, log it and begin rollback
log.exception(e) log.exception(e)
if opts['--pause-on-error']: if pause_on_error:
# The --pause-on-error is useful when the user wants to inspect the volume before rollback # The --pause-on-error is useful when the user wants to inspect the volume before rollback
raw_input('Press Enter to commence rollback') raw_input('Press Enter to commence rollback')
log.error('Rolling back') log.error('Rolling back')
@ -106,6 +121,6 @@ def run(opts):
rollback_tasklist = TaskList(rollback_tasks) rollback_tasklist = TaskList(rollback_tasks)
# Run the rollback tasklist # Run the rollback tasklist
rollback_tasklist.run(info=bootstrap_info, dry_run=opts['--dry-run']) rollback_tasklist.run(info=bootstrap_info, dry_run=dry_run)
log.info('Successfully completed rollback') log.info('Successfully completed rollback')
raise e raise e