mirror of
https://github.com/thegeeklab/ansible-later.git
synced 2024-11-22 21:00:44 +00:00
work on better json logging
This commit is contained in:
parent
be74192c67
commit
4f33afff3e
@ -6,6 +6,7 @@ import logging
|
|||||||
|
|
||||||
from ansiblelater import __version__
|
from ansiblelater import __version__
|
||||||
from ansiblelater import LOG
|
from ansiblelater import LOG
|
||||||
|
from ansiblelater import logger
|
||||||
from ansiblelater.command import base
|
from ansiblelater.command import base
|
||||||
from ansiblelater.command import candidates
|
from ansiblelater.command import candidates
|
||||||
|
|
||||||
@ -29,12 +30,15 @@ def main():
|
|||||||
args = parser.parse_args().__dict__
|
args = parser.parse_args().__dict__
|
||||||
|
|
||||||
settings = base.get_settings(args)
|
settings = base.get_settings(args)
|
||||||
|
config = settings.config
|
||||||
# print(json.dumps(settings.config, indent=4, sort_keys=True))
|
# print(json.dumps(settings.config, indent=4, sort_keys=True))
|
||||||
LOG.setLevel(settings.config["logging"]["level"])
|
|
||||||
files = settings.config["rules"]["files"]
|
|
||||||
standards = base.get_standards(settings.config["rules"]["standards"])
|
|
||||||
|
|
||||||
errors = []
|
logger.update_logger(LOG, config["logging"]["level"], config["logging"]["json"])
|
||||||
|
|
||||||
|
files = config["rules"]["files"]
|
||||||
|
standards = base.get_standards(config["rules"]["standards"])
|
||||||
|
|
||||||
|
errors = 0
|
||||||
for filename in files:
|
for filename in files:
|
||||||
lines = None
|
lines = None
|
||||||
candidate = candidates.classify(filename, settings, standards)
|
candidate = candidates.classify(filename, settings, standards)
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
|
|
||||||
import codecs
|
import codecs
|
||||||
|
import copy
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
@ -10,7 +11,8 @@ from distutils.version import LooseVersion
|
|||||||
import ansible
|
import ansible
|
||||||
|
|
||||||
from ansiblelater import LOG
|
from ansiblelater import LOG
|
||||||
from ansiblelater.utils import (get_property, is_line_in_ranges, lines_ranges, standards_latest)
|
from ansiblelater import utils
|
||||||
|
from ansiblelater.command.review import Error
|
||||||
from ansiblelater.exceptions import ( # noqa
|
from ansiblelater.exceptions import ( # noqa
|
||||||
LaterError, LaterAnsibleError
|
LaterError, LaterAnsibleError
|
||||||
)
|
)
|
||||||
@ -37,10 +39,10 @@ class Candidate(object):
|
|||||||
self.path = filename
|
self.path = filename
|
||||||
self.binary = False
|
self.binary = False
|
||||||
self.vault = False
|
self.vault = False
|
||||||
self.standards = standards
|
|
||||||
self.filetype = type(self).__name__.lower()
|
self.filetype = type(self).__name__.lower()
|
||||||
self.expected_version = True
|
self.expected_version = True
|
||||||
self.version = self._find_version(settings)
|
self.standards = self._get_standards(settings, standards)
|
||||||
|
self.version = self._get_version(settings)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with codecs.open(filename, mode="rb", encoding="utf-8") as f:
|
with codecs.open(filename, mode="rb", encoding="utf-8") as f:
|
||||||
@ -49,13 +51,14 @@ class Candidate(object):
|
|||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
self.binary = True
|
self.binary = True
|
||||||
|
|
||||||
def _find_version(self, settings):
|
def _get_version(self, settings):
|
||||||
if isinstance(self, RoleFile):
|
if isinstance(self, RoleFile):
|
||||||
parentdir = os.path.dirname(os.path.abspath(self.path))
|
parentdir = os.path.dirname(os.path.abspath(self.path))
|
||||||
while parentdir != os.path.dirname(parentdir):
|
while parentdir != os.path.dirname(parentdir):
|
||||||
meta_file = os.path.join(parentdir, "meta", "main.yml")
|
meta_file = os.path.join(parentdir, "meta", "main.yml")
|
||||||
if os.path.exists(meta_file):
|
if os.path.exists(meta_file):
|
||||||
path = meta_file
|
path = meta_file
|
||||||
|
break
|
||||||
parentdir = os.path.dirname(parentdir)
|
parentdir = os.path.dirname(parentdir)
|
||||||
else:
|
else:
|
||||||
path = self.path
|
path = self.path
|
||||||
@ -70,7 +73,7 @@ class Candidate(object):
|
|||||||
version = match.group(1)
|
version = match.group(1)
|
||||||
|
|
||||||
if not version:
|
if not version:
|
||||||
version = standards_latest(self.standards)
|
version = utils.standards_latest(self.standards)
|
||||||
if self.expected_version:
|
if self.expected_version:
|
||||||
if isinstance(self, RoleFile):
|
if isinstance(self, RoleFile):
|
||||||
LOG.warn("%s %s is in a role that contains a meta/main.yml without a declared "
|
LOG.warn("%s %s is in a role that contains a meta/main.yml without a declared "
|
||||||
@ -87,40 +90,58 @@ class Candidate(object):
|
|||||||
|
|
||||||
return version
|
return version
|
||||||
|
|
||||||
|
def _get_standards(self, settings, standards):
|
||||||
|
target_standards = []
|
||||||
|
limits = settings.config["rules"]["filter"]
|
||||||
|
|
||||||
|
if limits:
|
||||||
|
for standard in standards:
|
||||||
|
if standard.id in limits:
|
||||||
|
target_standards.append(standard)
|
||||||
|
else:
|
||||||
|
target_standards = standards
|
||||||
|
|
||||||
|
# print(target_standards)
|
||||||
|
return target_standards
|
||||||
|
|
||||||
def review(self, settings, lines=None):
|
def review(self, settings, lines=None):
|
||||||
errors = 0
|
errors = 0
|
||||||
|
|
||||||
for standard in standards.standards:
|
for standard in self.standards:
|
||||||
print(type(standard))
|
if type(self).__name__.lower() not in standard.types:
|
||||||
if type(candidate).__name__.lower() not in standard.types:
|
|
||||||
continue
|
continue
|
||||||
if settings.standards_filter and standard.name not in settings.standards_filter:
|
result = standard.check(self, settings.config)
|
||||||
continue
|
|
||||||
result = standard.check(candidate, settings)
|
|
||||||
|
|
||||||
if not result:
|
if not result:
|
||||||
abort("Standard '%s' returns an empty result object." %
|
utils.sysexit_with_message("Standard '%s' returns an empty result object." %
|
||||||
(standard.check.__name__))
|
(standard.check.__name__))
|
||||||
|
|
||||||
|
labels = {"tag": "review", "standard": standard.name, "file": self.path, "passed": True}
|
||||||
|
|
||||||
for err in [err for err in result.errors
|
for err in [err for err in result.errors
|
||||||
if not err.lineno or is_line_in_ranges(err.lineno, lines_ranges(lines))]:
|
if not err.lineno or utils.is_line_in_ranges(err.lineno, utils.lines_ranges(lines))]:
|
||||||
|
err_labels = copy.copy(labels)
|
||||||
|
err_labels["passed"] = False
|
||||||
|
if isinstance(err, Error):
|
||||||
|
err_labels.update(err.to_dict())
|
||||||
|
|
||||||
if not standard.version:
|
if not standard.version:
|
||||||
warn("{id}Best practice '{name}' not met:\n{path}:{error}".format(
|
LOG.warn("{id}Best practice '{name}' not met:\n{path}:{error}".format(
|
||||||
id=standard.id, name=standard.name, path=candidate.path, error=err), settings)
|
id=standard.id, name=standard.name, path=self.path, error=err), extra=err_labels)
|
||||||
elif LooseVersion(standard.version) > LooseVersion(candidate.version):
|
elif LooseVersion(standard.version) > LooseVersion(self.version):
|
||||||
warn("{id}Future standard '{name}' not met:\n{path}:{error}".format(
|
LOG.warn("{id}Future standard '{name}' not met:\n{path}:{error}".format(
|
||||||
id=standard.id, name=standard.name, path=candidate.path, error=err), settings)
|
id=standard.id, name=standard.name, path=self.path, error=err), extra=err_labels)
|
||||||
else:
|
else:
|
||||||
error("{id}Standard '{name}' not met:\n{path}:{error}".format(
|
LOG.error("{id}Standard '{name}' not met:\n{path}:{error}".format(
|
||||||
id=standard.id, name=standard.name, path=candidate.path, error=err))
|
id=standard.id, name=standard.name, path=self.path, error=err), extra=err_labels)
|
||||||
errors = errors + 1
|
errors = errors + 1
|
||||||
if not result.errors:
|
if not result.errors:
|
||||||
if not standard.version:
|
if not standard.version:
|
||||||
info("Best practice '%s' met" % standard.name, settings)
|
LOG.info("Best practice '%s' met" % standard.name, extra=labels)
|
||||||
elif LooseVersion(standard.version) > LooseVersion(candidate.version):
|
elif LooseVersion(standard.version) > LooseVersion(self.version):
|
||||||
info("Future standard '%s' met" % standard.name, settings)
|
LOG.info("Future standard '%s' met" % standard.name, extra=labels)
|
||||||
else:
|
else:
|
||||||
info("Standard '%s' met" % standard.name, settings)
|
LOG.info("Standard '%s' met" % standard.name)
|
||||||
|
|
||||||
return errors
|
return errors
|
||||||
|
|
||||||
@ -134,18 +155,14 @@ class Candidate(object):
|
|||||||
class RoleFile(Candidate):
|
class RoleFile(Candidate):
|
||||||
def __init__(self, filename, settings={}, standards=[]):
|
def __init__(self, filename, settings={}, standards=[]):
|
||||||
super(RoleFile, self).__init__(filename, settings, standards)
|
super(RoleFile, self).__init__(filename, settings, standards)
|
||||||
self.version = None
|
|
||||||
# parentdir = os.path.dirname(os.path.abspath(filename))
|
|
||||||
# while parentdir != os.path.dirname(parentdir):
|
|
||||||
# meta_file = os.path.join(parentdir, "meta", "main.yml")
|
|
||||||
# if os.path.exists(meta_file):
|
|
||||||
# self.version = self._find_version(meta_file)
|
|
||||||
# if self.version:
|
|
||||||
# break
|
|
||||||
|
|
||||||
# role_modules = os.path.join(parentdir, "library")
|
parentdir = os.path.dirname(os.path.abspath(filename))
|
||||||
# if os.path.exists(role_modules):
|
while parentdir != os.path.dirname(parentdir):
|
||||||
# module_loader.add_directory(role_modules)
|
role_modules = os.path.join(parentdir, "library")
|
||||||
|
if os.path.exists(role_modules):
|
||||||
|
module_loader.add_directory(role_modules)
|
||||||
|
break
|
||||||
|
parentdir = os.path.dirname(parentdir)
|
||||||
|
|
||||||
|
|
||||||
class Playbook(Candidate):
|
class Playbook(Candidate):
|
||||||
@ -257,4 +274,3 @@ def classify(filename, settings={}, standards=[]):
|
|||||||
if "README" in basename:
|
if "README" in basename:
|
||||||
return Doc(filename, settings, standards)
|
return Doc(filename, settings, standards)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -3,11 +3,13 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from six import iteritems
|
||||||
|
|
||||||
|
|
||||||
class Error(object):
|
class Error(object):
|
||||||
"""Default error object created if a rule failed."""
|
"""Default error object created if a rule failed."""
|
||||||
|
|
||||||
def __init__(self, lineno, message):
|
def __init__(self, lineno, message, error_type=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Initialize a new error object and returns None.
|
Initialize a new error object and returns None.
|
||||||
|
|
||||||
@ -17,6 +19,9 @@ class Error(object):
|
|||||||
"""
|
"""
|
||||||
self.lineno = lineno
|
self.lineno = lineno
|
||||||
self.message = message
|
self.message = message
|
||||||
|
self.kwargs = kwargs
|
||||||
|
for (key, value) in iteritems(kwargs):
|
||||||
|
setattr(self, key, value)
|
||||||
|
|
||||||
def __repr__(self): # noqa
|
def __repr__(self): # noqa
|
||||||
if self.lineno:
|
if self.lineno:
|
||||||
@ -24,6 +29,12 @@ class Error(object):
|
|||||||
else:
|
else:
|
||||||
return " %s" % (self.message)
|
return " %s" % (self.message)
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
result = dict(lineno=self.lineno, message=self.message)
|
||||||
|
for (key, value) in iteritems(self.kwargs):
|
||||||
|
result[key] = value
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
class Result(object):
|
class Result(object):
|
||||||
def __init__(self, candidate, errors=None):
|
def __init__(self, candidate, errors=None):
|
||||||
|
@ -8,6 +8,9 @@ import colorama
|
|||||||
from ansible.module_utils.parsing.convert_bool import boolean as to_bool
|
from ansible.module_utils.parsing.convert_bool import boolean as to_bool
|
||||||
from pythonjsonlogger import jsonlogger
|
from pythonjsonlogger import jsonlogger
|
||||||
|
|
||||||
|
CONSOLE_FORMAT = "%(levelname)s: %(message)s"
|
||||||
|
JSON_FORMAT = "(levelname) (message) (asctime)"
|
||||||
|
|
||||||
|
|
||||||
def _should_do_markup():
|
def _should_do_markup():
|
||||||
|
|
||||||
@ -21,6 +24,19 @@ def _should_do_markup():
|
|||||||
colorama.init(autoreset=True, strip=not _should_do_markup())
|
colorama.init(autoreset=True, strip=not _should_do_markup())
|
||||||
|
|
||||||
|
|
||||||
|
def OverwriteMakeRecord(self, name, level, fn, lno, msg, args, exc_info, func=None, extra=None):
|
||||||
|
"""
|
||||||
|
A factory method which can be overridden in subclasses to create
|
||||||
|
specialized LogRecords.
|
||||||
|
"""
|
||||||
|
rv = logging.LogRecord(name, level, fn, lno, msg, args, exc_info, func)
|
||||||
|
if extra is not None:
|
||||||
|
for key in extra:
|
||||||
|
rv.__dict__[key] = extra[key]
|
||||||
|
print("xxx", rv.__dict__)
|
||||||
|
return rv
|
||||||
|
|
||||||
|
|
||||||
class LogFilter(object):
|
class LogFilter(object):
|
||||||
"""A custom log filter which excludes log messages above the logged level."""
|
"""A custom log filter which excludes log messages above the logged level."""
|
||||||
|
|
||||||
@ -50,6 +66,7 @@ def get_logger(name=None, level=logging.DEBUG, json=False):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
logger = logging.getLogger(name)
|
logger = logging.getLogger(name)
|
||||||
|
logger.makeRecord(OverwriteMakeRecord)
|
||||||
logger.setLevel(level)
|
logger.setLevel(level)
|
||||||
logger.addHandler(_get_error_handler(json=json))
|
logger.addHandler(_get_error_handler(json=json))
|
||||||
logger.addHandler(_get_warn_handler(json=json))
|
logger.addHandler(_get_warn_handler(json=json))
|
||||||
@ -60,14 +77,25 @@ def get_logger(name=None, level=logging.DEBUG, json=False):
|
|||||||
return logger
|
return logger
|
||||||
|
|
||||||
|
|
||||||
|
def update_logger(logger, level=None, json=None):
|
||||||
|
for handler in logger.handlers[:]:
|
||||||
|
logger.removeHandler(handler)
|
||||||
|
|
||||||
|
logger.setLevel(level)
|
||||||
|
logger.addHandler(_get_error_handler(json=json))
|
||||||
|
logger.addHandler(_get_warn_handler(json=json))
|
||||||
|
logger.addHandler(_get_info_handler(json=json))
|
||||||
|
logger.addHandler(_get_critical_handler(json=json))
|
||||||
|
|
||||||
|
|
||||||
def _get_error_handler(json=False):
|
def _get_error_handler(json=False):
|
||||||
handler = logging.StreamHandler(sys.stderr)
|
handler = logging.StreamHandler(sys.stderr)
|
||||||
handler.setLevel(logging.ERROR)
|
handler.setLevel(logging.ERROR)
|
||||||
handler.addFilter(LogFilter(logging.ERROR))
|
handler.addFilter(LogFilter(logging.ERROR))
|
||||||
handler.setFormatter(logging.Formatter(error("%(message)s")))
|
handler.setFormatter(logging.Formatter(error(CONSOLE_FORMAT)))
|
||||||
|
|
||||||
if json:
|
if json:
|
||||||
handler.setFormatter(jsonlogger.JsonFormatter("%(message)s"))
|
handler.setFormatter(jsonlogger.JsonFormatter(JSON_FORMAT))
|
||||||
|
|
||||||
return handler
|
return handler
|
||||||
|
|
||||||
@ -76,10 +104,10 @@ def _get_warn_handler(json=False):
|
|||||||
handler = logging.StreamHandler(sys.stdout)
|
handler = logging.StreamHandler(sys.stdout)
|
||||||
handler.setLevel(logging.WARN)
|
handler.setLevel(logging.WARN)
|
||||||
handler.addFilter(LogFilter(logging.WARN))
|
handler.addFilter(LogFilter(logging.WARN))
|
||||||
handler.setFormatter(logging.Formatter(warn("%(message)s")))
|
handler.setFormatter(logging.Formatter(warn(CONSOLE_FORMAT)))
|
||||||
|
|
||||||
if json:
|
if json:
|
||||||
handler.setFormatter(jsonlogger.JsonFormatter("%(message)s"))
|
handler.setFormatter(jsonlogger.JsonFormatter(JSON_FORMAT))
|
||||||
|
|
||||||
return handler
|
return handler
|
||||||
|
|
||||||
@ -91,7 +119,7 @@ def _get_info_handler(json=False):
|
|||||||
handler.setFormatter(logging.Formatter(info("%(message)s")))
|
handler.setFormatter(logging.Formatter(info("%(message)s")))
|
||||||
|
|
||||||
if json:
|
if json:
|
||||||
handler.setFormatter(jsonlogger.JsonFormatter("%(message)s"))
|
handler.setFormatter(jsonlogger.JsonFormatter(JSON_FORMAT))
|
||||||
|
|
||||||
return handler
|
return handler
|
||||||
|
|
||||||
@ -100,32 +128,32 @@ def _get_critical_handler(json=False):
|
|||||||
handler = logging.StreamHandler(sys.stderr)
|
handler = logging.StreamHandler(sys.stderr)
|
||||||
handler.setLevel(logging.CRITICAL)
|
handler.setLevel(logging.CRITICAL)
|
||||||
handler.addFilter(LogFilter(logging.CRITICAL))
|
handler.addFilter(LogFilter(logging.CRITICAL))
|
||||||
handler.setFormatter(logging.Formatter(critical("%(message)s")))
|
handler.setFormatter(logging.Formatter(critical(CONSOLE_FORMAT)))
|
||||||
|
|
||||||
if json:
|
if json:
|
||||||
handler.setFormatter(jsonlogger.JsonFormatter("%(message)s"))
|
handler.setFormatter(jsonlogger.JsonFormatter(JSON_FORMAT))
|
||||||
|
|
||||||
return handler
|
return handler
|
||||||
|
|
||||||
|
|
||||||
def critical(message):
|
def critical(message):
|
||||||
"""Format critical messages and return string."""
|
"""Format critical messages and return string."""
|
||||||
return color_text(colorama.Fore.RED, "FATAL: {}".format(message))
|
return color_text(colorama.Fore.RED, "{}".format(message))
|
||||||
|
|
||||||
|
|
||||||
def error(message):
|
def error(message):
|
||||||
"""Format error messages and return string."""
|
"""Format error messages and return string."""
|
||||||
return color_text(colorama.Fore.RED, "ERROR: {}".format(message))
|
return color_text(colorama.Fore.RED, "{}".format(message))
|
||||||
|
|
||||||
|
|
||||||
def warn(message):
|
def warn(message):
|
||||||
"""Format warn messages and return string."""
|
"""Format warn messages and return string."""
|
||||||
return color_text(colorama.Fore.YELLOW, "WARN: {}".format(message))
|
return color_text(colorama.Fore.YELLOW, "{}".format(message))
|
||||||
|
|
||||||
|
|
||||||
def info(message):
|
def info(message):
|
||||||
"""Format info messages and return string."""
|
"""Format info messages and return string."""
|
||||||
return color_text(colorama.Fore.BLUE, "INFO: {}".format(message))
|
return color_text(colorama.Fore.BLUE, "{}".format(message))
|
||||||
|
|
||||||
|
|
||||||
def color_text(color, msg):
|
def color_text(color, msg):
|
||||||
|
@ -83,6 +83,7 @@ class Settings(object):
|
|||||||
},
|
},
|
||||||
"logging": {
|
"logging": {
|
||||||
"level": logging.WARN,
|
"level": logging.WARN,
|
||||||
|
"json": False
|
||||||
},
|
},
|
||||||
"ansible": {
|
"ansible": {
|
||||||
"custom_modules": [],
|
"custom_modules": [],
|
||||||
|
@ -13,12 +13,12 @@ class Standard(object):
|
|||||||
:param standard_dict: Dictionary object containing all neseccary attributes
|
:param standard_dict: Dictionary object containing all neseccary attributes
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if "id" not in standard_dict:
|
# if "id" not in standard_dict:
|
||||||
standard_dict.update(id="")
|
# standard_dict.update(id="")
|
||||||
else:
|
# else:
|
||||||
standard_dict.update(id="[{}] ".format(standard_dict.get("id")))
|
# standard_dict.update(id="[{}] ".format(standard_dict.get("id")))
|
||||||
|
|
||||||
self.id = standard_dict.get("id")
|
self.id = standard_dict.get("id", "")
|
||||||
self.name = standard_dict.get("name")
|
self.name = standard_dict.get("name")
|
||||||
self.version = standard_dict.get("version")
|
self.version = standard_dict.get("version")
|
||||||
self.check = standard_dict.get("check")
|
self.check = standard_dict.get("check")
|
||||||
|
@ -51,7 +51,7 @@ def get_normalized_task(task, candidate, settings):
|
|||||||
normalized = None
|
normalized = None
|
||||||
errors = []
|
errors = []
|
||||||
try:
|
try:
|
||||||
normalized = normalize_task(task, candidate.path, settings.custom_modules)
|
normalized = normalize_task(task, candidate.path, settings["ansible"]["custom_modules"])
|
||||||
except LaterError as ex:
|
except LaterError as ex:
|
||||||
e = ex.original
|
e = ex.original
|
||||||
errors.append(Error(e.problem_mark.line + 1, "syntax error: %s" % (e.problem)))
|
errors.append(Error(e.problem_mark.line + 1, "syntax error: %s" % (e.problem)))
|
||||||
@ -77,7 +77,7 @@ def get_normalized_tasks(candidate, settings):
|
|||||||
if 'skip_ansible_lint' in (task.get('tags') or []):
|
if 'skip_ansible_lint' in (task.get('tags') or []):
|
||||||
# No need to normalize_task if we are skipping it.
|
# No need to normalize_task if we are skipping it.
|
||||||
continue
|
continue
|
||||||
normalized.append(normalize_task(task, candidate.path, settings.custom_modules))
|
normalized.append(normalize_task(task, candidate.path, settings["ansible"]["custom_modules"]))
|
||||||
|
|
||||||
except LaterError as ex:
|
except LaterError as ex:
|
||||||
e = ex.original
|
e = ex.original
|
||||||
|
Loading…
Reference in New Issue
Block a user