refactor logging and switch to python standard library

This commit is contained in:
Robert Kaussow 2019-03-28 16:54:45 +01:00
parent f92c77f5b8
commit 0ffdf36a0d
6 changed files with 209 additions and 113 deletions

View File

@ -16,11 +16,11 @@ from distutils.version import LooseVersion
import ansible
from appdirs import AppDirs
from ansiblelater.utils import (abort, error, get_property, info,
from . import logger
from .settings import Settings
from ansiblelater.utils import (get_property,
is_line_in_ranges, lines_ranges,
read_standards, standards_latest, warn)
read_standards, standards_latest)
try:
# Ansible 2.4 import of module loader
@ -31,13 +31,8 @@ except ImportError:
except ImportError:
from ansible.utils import module_finder as module_loader
try:
import ConfigParser as configparser
except ImportError:
import configparser
settings = Settings()
logger = logger.get_logger(__name__, settings.config["logging"]["level"])
class Standard(object):
@ -281,13 +276,13 @@ def candidate_review(candidate, settings, lines=None):
candidate.version = standards_latest(standards.standards)
if candidate.expected_version:
if isinstance(candidate, RoleFile):
warn("%s %s is in a role that contains a meta/main.yml without a declared "
logger.warn("%s %s is in a role that contains a meta/main.yml without a declared "
"standards version. "
"Using latest standards version %s" %
(type(candidate).__name__, candidate.path, candidate.version),
settings)
else:
warn("%s %s does not present standards version. "
logger.warn("%s %s does not present standards version. "
"Using latest standards version %s" %
(type(candidate).__name__, candidate.path, candidate.version),
settings)

View File

@ -1,56 +1,45 @@
#!/usr/bin/env python
import argparse
import json
import logging
import optparse
import os
import sys
from appdirs import AppDirs
from pkg_resources import resource_filename
from ansiblelater import __version__, settings, logger
from ansiblelater.utils import get_property
from ansiblelater import classify, settings
from ansiblelater.utils import get_property, info, warn
from .settings import Settings
# from .settings import Settings
def main():
config_dir = AppDirs("ansible-later").user_config_dir
default_config_file = os.path.join(config_dir, "config.yml")
parser = optparse.OptionParser("%prog playbook_file|role_file|inventory_file",
version="%prog " + get_property("__version__"))
parser.add_option('-c', dest='config_file', default=default_config_file,
help="Location of configuration file: [%s]" % default_config_file)
parser.add_option('-d', dest='rules_dir',
parser = argparse.ArgumentParser(
description="Validate ansible files against best pratice guideline")
parser.add_argument('-c', dest='config_file',
help="Location of configuration file: [%s]" % settings.config_file)
parser.add_argument('-d', dest='rules.standards',
help="Location of standards rules")
parser.add_option('-q', dest='log_level', action="store_const",
parser.add_argument('-q', dest='logging.level', action="store_const",
const=logging.ERROR, help="Only output errors")
parser.add_option('-s', dest='standards_filter', action='append',
parser.add_argument('-s', dest='rules.filter', action='append',
help="limit standards to specific names")
parser.add_option('-v', '--verbose', dest='log_level', action="count",
parser.add_argument('-v', '--verbose', dest='logging.level', action="count",
help="Show more verbose output")
parser.add_argument('--version', action='version', version='%(prog)s {}'.format(__version__))
options, args = parser.parse_args(sys.argv[1:])
args = parser.parse_args().__dict__
settings = Settings(options)
# Override correct log level from argparse
levels = [logging.WARNING, logging.INFO, logging.DEBUG]
if args.get("logging.level"):
args["logging.level"] = levels[min(len(levels) - 1, args["logging.level"] - 1)]
# print(settings.rulesdir)
# settings = read_config(options.configfile)
settings.set_args(args)
# print(json.dumps(settings.config, indent=4, sort_keys=True))
# print(settings.config["logging"]["level"])
# # Merge CLI options with config options. CLI options override config options.
# for key, value in options.__dict__.items():
# if value:
# setattr(settings, key, value)
# if os.path.exists(settings.configfile):
# info("Using configuration file: %s" % settings.configfile, settings)
# else:
# warn("No configuration file found at %s" % settings.configfile, settings, file=sys.stderr)
# if not settings.rulesdir:
# rules_dir = os.path.join(resource_filename('ansiblelater', 'examples'))
# warn("Using example standards found at %s" % rules_dir, settings, file=sys.stderr)
# settings.rulesdir = rules_dir
# if len(args) == 0:
# candidates = []

111
ansiblelater/logger.py Normal file
View File

@ -0,0 +1,111 @@
import logging
import os
import sys
import colorama
from pythonjsonlogger import jsonlogger
from ansible.module_utils.parsing.convert_bool import boolean as to_bool
def should_do_markup():
py_colors = os.environ.get('PY_COLORS', None)
if py_colors is not None:
return to_bool(py_colors, strict=False)
return sys.stdout.isatty() and os.environ.get('TERM') != 'dumb'
colorama.init(autoreset=True, strip=not should_do_markup())
class LogFilter(object):
"""
A custom log filter which excludes log messages above the logged
level.
"""
def __init__(self, level):
self.__level = level
def filter(self, logRecord): # pragma: no cover
# https://docs.python.org/3/library/logging.html#logrecord-attributes
return logRecord.levelno <= self.__level
def get_logger(name=None, level=logging.DEBUG, json=False):
"""
Build a logger with the given name and returns the logger.
:param name: The name for the logger. This is usually the module
name, ``__name__``.
:return: logger object
"""
logger = logging.getLogger(name)
logger.setLevel(level)
#handler = logging.StreamHandler()
#formatter = jsonlogger.JsonFormatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
#formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
#handler.setFormatter(formatter)
logger.addHandler(_get_error_handler(json=json))
logger.addHandler(_get_warn_handler(json=json))
logger.addHandler(_get_info_handler(json=json))
logger.propagate = False
return logger
def _get_error_handler(json=False):
handler = logging.StreamHandler(sys.stderr)
handler.setLevel(logging.ERROR)
handler.addFilter(LogFilter(logging.ERROR))
handler.setFormatter(logging.Formatter(error('%(message)s')))
if json:
handler.setFormatter(jsonlogger.JsonFormatter('%(message)s'))
return handler
def _get_warn_handler(json=False):
handler = logging.StreamHandler(sys.stdout)
handler.setLevel(logging.WARN)
handler.addFilter(LogFilter(logging.WARN))
handler.setFormatter(logging.Formatter(warn('%(message)s')))
if json:
handler.setFormatter(jsonlogger.JsonFormatter('%(message)s'))
return handler
def _get_info_handler(json=False):
handler = logging.StreamHandler(sys.stderr)
handler.setLevel(logging.INFO)
handler.addFilter(LogFilter(logging.INFO))
handler.setFormatter(logging.Formatter(info('%(message)s')))
if json:
handler.setFormatter(jsonlogger.JsonFormatter('%(message)s'))
return handler
def abort(message, file=sys.stderr):
return color_text(colorama.Fore.RED, "FATAL: {}".format(message))
sys.exit(1)
def error(message):
return color_text(colorama.Fore.RED, "ERROR: {}".format(message))
def warn(message):
return color_text(colorama.Fore.YELLOW, "WARN: {}".format(message))
def info(message):
return color_text(colorama.Fore.BLUE, "INFO: {}".format(message))
def color_text(color, msg):
return '{}{}{}'.format(color, msg, colorama.Style.RESET_ALL)

View File

@ -1,42 +1,60 @@
import json
import logging
import os
import six
import anyconfig
from appdirs import AppDirs
from pkg_resources import resource_filename
from ansiblelater import utils
from ansiblelater import utils, logger
try:
import ConfigParser as configparser
except ImportError:
import configparser
config_dir = AppDirs("ansible-later").user_config_dir
default_config_file = os.path.join(config_dir, "config.yml")
logger = logger.get_logger(__name__)
class NewInitCaller(type):
def __call__(cls, *args, **kwargs):
obj = type.__call__(cls, *args, **kwargs)
obj.after_init()
return obj
@six.add_metaclass(NewInitCaller)
class Settings(object):
def __init__(self, args={}):
self.args = self._get_args(args)
def __init__(self, args={}, config_file=default_config_file):
self.args = args
self.config_file = config_file
self.config = self._get_config()
def _get_args(self, args):
# Override correct log level from argparse
levels = [logging.WARNING, logging.INFO, logging.DEBUG]
if args.log_level:
args.log_level = levels[min(len(levels) - 1, args.log_level - 1)]
def set_args(self, args={}):
self.config_file = args.get("config_file") or default_config_file
args_dict = dict(filter(lambda item: item[1] is not None, args.__dict__.items()))
return args_dict
args.pop("config_file", None)
args = dict(filter(lambda item: item[1] is not None, args.items()))
args_dict = {}
for key, value in args.items():
args_dict = utils.add_dict_branch(args_dict, key.split("."), value)
self.args = args_dict
self.config = self._get_config()
self._validate()
def _get_config(self):
defaults = self._get_defaults()
config_file = self.args.get('config_file')
config_file = self.config_file
cli_options = self.args
if config_file and os.path.exists(config_file):
with utils.open_file(config_file) as stream:
s = stream.read()
anyconfig.merge(defaults, utils.safe_load(s), ac_merge=anyconfig.MS_DICTS)
print(json.dumps(defaults, indent=4, sort_keys=True))
if cli_options:
anyconfig.merge(defaults, cli_options, ac_merge=anyconfig.MS_DICTS)
return defaults
def _get_defaults(self):
@ -44,13 +62,21 @@ class Settings(object):
return {
'rules': {
'standards': self.args.get('rules_dir', rules_dir),
'standards_filter': [],
'standards': rules_dir,
'filter': [],
},
'logging': {
'level': self.args.get('log_level', logging.WARN),
'level': logging.WARN,
},
'ansible': {
'custom_modules': [],
}
}
def after_init(self):
self.config = self._get_config()
self._validate()
def _validate(self):
logger.setLevel(self.config["logging"]["level"])

View File

@ -17,41 +17,6 @@ try:
except ImportError:
import configparser
def should_do_markup():
py_colors = os.environ.get('PY_COLORS', None)
if py_colors is not None:
return to_bool(py_colors, strict=False)
return sys.stdout.isatty() and os.environ.get('TERM') != 'dumb'
colorama.init(autoreset=True, strip=not should_do_markup())
def abort(message, file=sys.stderr):
return color_text(colorama.Fore.RED, "FATAL: {}".format(message))
sys.exit(1)
def error(message, file=sys.stderr):
return color_text(colorama.Fore.RED, "ERROR: {}".format(message))
def warn(message, settings, file=sys.stdout):
if settings.log_level <= logging.WARNING:
return color_text(colorama.Fore.YELLOW, "WARN: {}".format(message))
def info(message, settings, file=sys.stdout):
if settings.log_level <= logging.INFO:
return color_text(colorama.Fore.BLUE, "INFO: {}".format(message))
def color_text(color, msg):
print('{}{}{}'.format(color, msg, colorama.Style.RESET_ALL))
def count_spaces(c_string):
leading_spaces = 0
trailing_spaces = 0
@ -137,3 +102,13 @@ def open_file(filename, mode='r'):
"""
with open(filename, mode) as stream:
yield stream
def add_dict_branch(tree, vector, value):
key = vector[0]
tree[key] = value \
if len(vector) == 1 \
else add_dict_branch(tree[key] if key in tree else {},
vector[1:],
value)
return tree

View File

@ -11,7 +11,7 @@ max-line-length = 100
inline-quotes = double
exclude = .git,.tox,__pycache__,build,dist,tests,*.pyc,*.egg-info,.cache,.eggs
application-import-names = ansiblelater
format = ${cyan}%(path)s:%(row)d:%(col)d${reset}: ${red_bold}%(code)s${reset} %(text)s
#format = ${cyan}%(path)s:%(row)d:%(col)d${reset}: ${red_bold}%(code)s${reset} %(text)s
[isort]
default_section = THIRDPARTY