Skip to content

Commit

Permalink
feat: add json and simple log formatter (#8)
Browse files Browse the repository at this point in the history
  • Loading branch information
xoxys authored Jun 12, 2021
1 parent 73b39e2 commit 91ce466
Show file tree
Hide file tree
Showing 6 changed files with 270 additions and 250 deletions.
15 changes: 0 additions & 15 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 18 additions & 7 deletions prometheuspvesd/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
from prometheuspvesd.config import SingleConfig
from prometheuspvesd.discovery import Discovery
from prometheuspvesd.exception import APIError
from prometheuspvesd.logger import SingleLog
from prometheuspvesd.model import HostList
from prometheuspvesd.utils import SingleLog


class PrometheusSD:
Expand Down Expand Up @@ -54,7 +54,11 @@ def _cli_args(self):
"""
parser = argparse.ArgumentParser(description="Prometheus Service Discovery for Proxmox VE")
parser.add_argument(
"-c", "--config", dest="config_file", help="location of configuration file"
"-c",
"--config",
dest="config_file",
action="store",
help="location of configuration file"
)
parser.add_argument(
"-o", "--output", dest="output_file", action="store", help="output file"
Expand All @@ -68,10 +72,15 @@ def _cli_args(self):
help="delay between discovery runs"
)
parser.add_argument(
"--no-service",
dest="service",
action="store_false",
help="run discovery as a service"
"--no-service", dest="service", action="store_false", help="run discovery only once"
)
parser.add_argument(
"-f",
"--log-format",
dest="logging.format",
metavar="LOG_FORMAT",
action="store",
help="used log format"
)
parser.add_argument(
"-v", dest="logging.level", action="append_const", const=-1, help="increase log level"
Expand All @@ -92,7 +101,9 @@ def _get_config(self):
self.log.sysexit_with_message(e)

try:
self.log.set_level(config.config["logging"]["level"])
self.log.update_logger(
config.config["logging"]["level"], config.config["logging"]["format"]
)
except ValueError as e:
self.log.sysexit_with_message("Can not set log level.\n{}".format(str(e)))

Expand Down
9 changes: 5 additions & 4 deletions prometheuspvesd/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@ class Config():
"file": True,
"type": environs.Env().str
},
"logging.json": {
"default": False,
"env": "LOG_JSON",
"logging.format": {
"default": "console",
"env": "LOG_FORMAT",
"file": True,
"type": environs.Env().bool
"type": environs.Env().str
},
"output_file": {
"default": default_output_file,
Expand Down Expand Up @@ -213,6 +213,7 @@ def _set_config(self):
defaults.pop("config_file")

defaults["logging"]["level"] = defaults["logging"]["level"].upper()
defaults["logging"]["format"] = defaults["logging"]["format"].strip().lower()

Path(PurePath(self.config_file).parent).mkdir(parents=True, exist_ok=True)
Path(PurePath(defaults["output_file"]).parent).mkdir(parents=True, exist_ok=True)
Expand Down
2 changes: 1 addition & 1 deletion prometheuspvesd/discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@

from prometheuspvesd.config import SingleConfig
from prometheuspvesd.exception import APIError
from prometheuspvesd.logger import SingleLog
from prometheuspvesd.model import Host
from prometheuspvesd.model import HostList
from prometheuspvesd.utils import SingleLog
from prometheuspvesd.utils import to_bool

try:
Expand Down
246 changes: 246 additions & 0 deletions prometheuspvesd/logger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
#!/usr/bin/env python3
"""Global utility methods and classes."""

import logging
import os
import sys

import colorama
from pythonjsonlogger import jsonlogger

from prometheuspvesd.utils import Singleton
from prometheuspvesd.utils import to_bool

CONSOLE_FORMAT = "{}{}[%(levelname)s]{} %(message)s"
JSON_FORMAT = "%(asctime)s %(levelname)s %(message)s"


def _should_do_markup():
py_colors = os.environ.get("PY_COLORS", None)
if py_colors is not None:
return to_bool(py_colors)

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):
"""
Initialize a new custom log filter.
:param level: Log level limit
:returns: None
"""
self.__level = level

def filter(self, logRecord): # noqa
# https://docs.python.org/3/library/logging.html#logrecord-attributes
return logRecord.levelno <= self.__level


class SimpleFormatter(logging.Formatter):
"""Logging Formatter for simple logs."""

def format(self, record): # noqa
return logging.Formatter.format(self, record)


class MultilineFormatter(logging.Formatter):
"""Logging Formatter to reset color after newline characters."""

def format(self, record): # noqa
record.msg = record.msg.replace("\n", "\n{}... ".format(colorama.Style.RESET_ALL))
return logging.Formatter.format(self, record)


class MultilineJsonFormatter(jsonlogger.JsonFormatter):
"""Logging Formatter to remove newline characters."""

def format(self, record): # noqa
record.msg = record.msg.replace("\n", " ")
return jsonlogger.JsonFormatter.format(self, record)


class Log:
"""Handle logging."""

def __init__(self, level=logging.WARN, name="prometheuspvesd", log_format="console"):
self.logger = logging.getLogger(name)
self.logger.setLevel(level)
self.logger.addHandler(self._get_error_handler(log_format))
self.logger.addHandler(self._get_warn_handler(log_format))
self.logger.addHandler(self._get_info_handler(log_format))
self.logger.addHandler(self._get_critical_handler(log_format))
self.logger.addHandler(self._get_debug_handler(log_format))
self.logger.propagate = False

def _get_error_handler(self, log_format):
handler = logging.StreamHandler(sys.stderr)
handler.setLevel(logging.ERROR)
handler.addFilter(LogFilter(logging.ERROR))

if log_format == "json":
handler.setFormatter(MultilineJsonFormatter(JSON_FORMAT))
elif log_format == "simple":
handler.setFormatter(SimpleFormatter())
else:
handler.setFormatter(
MultilineFormatter(
self.error(
CONSOLE_FORMAT.format(
colorama.Fore.RED, colorama.Style.BRIGHT, colorama.Style.RESET_ALL
)
)
)
)

return handler

def _get_warn_handler(self, log_format):
handler = logging.StreamHandler(sys.stdout)
handler.setLevel(logging.WARN)
handler.addFilter(LogFilter(logging.WARN))

if log_format == "json":
handler.setFormatter(MultilineJsonFormatter(JSON_FORMAT))
elif log_format == "simple":
handler.setFormatter(SimpleFormatter())
else:
handler.setFormatter(
MultilineFormatter(
self.warn(
CONSOLE_FORMAT.format(
colorama.Fore.YELLOW, colorama.Style.BRIGHT, colorama.Style.RESET_ALL
)
)
)
)

return handler

def _get_info_handler(self, log_format):
handler = logging.StreamHandler(sys.stdout)
handler.setLevel(logging.INFO)
handler.addFilter(LogFilter(logging.INFO))

if log_format == "json":
handler.setFormatter(MultilineJsonFormatter(JSON_FORMAT))
elif log_format == "simple":
handler.setFormatter(SimpleFormatter())
else:
handler.setFormatter(
MultilineFormatter(
self.info(
CONSOLE_FORMAT.format(
colorama.Fore.CYAN, colorama.Style.BRIGHT, colorama.Style.RESET_ALL
)
)
)
)

return handler

def _get_critical_handler(self, log_format):
handler = logging.StreamHandler(sys.stderr)
handler.setLevel(logging.CRITICAL)
handler.addFilter(LogFilter(logging.CRITICAL))

if log_format == "json":
handler.setFormatter(MultilineJsonFormatter(JSON_FORMAT))
elif log_format == "simple":
handler.setFormatter(SimpleFormatter())
else:
handler.setFormatter(
MultilineFormatter(
self.critical(
CONSOLE_FORMAT.format(
colorama.Fore.RED, colorama.Style.BRIGHT, colorama.Style.RESET_ALL
)
)
)
)

return handler

def _get_debug_handler(self, log_format):
handler = logging.StreamHandler(sys.stderr)
handler.setLevel(logging.DEBUG)
handler.addFilter(LogFilter(logging.DEBUG))

if log_format == "json":
handler.setFormatter(MultilineJsonFormatter(JSON_FORMAT))
elif log_format == "simple":
handler.setFormatter(SimpleFormatter())
else:
handler.setFormatter(
MultilineFormatter(
self.critical(
CONSOLE_FORMAT.format(
colorama.Fore.BLUE, colorama.Style.BRIGHT, colorama.Style.RESET_ALL
)
)
)
)

return handler

def update_logger(self, level=None, log_level=None):
for handler in self.logger.handlers[:]:
self.logger.removeHandler(handler)

self.logger.setLevel(level)
self.logger.addHandler(self._get_error_handler(log_level))
self.logger.addHandler(self._get_warn_handler(log_level))
self.logger.addHandler(self._get_info_handler(log_level))
self.logger.addHandler(self._get_critical_handler(log_level))
self.logger.addHandler(self._get_debug_handler(log_level))

def debug(self, msg):
"""Format info messages and return string."""
return msg

def critical(self, msg):
"""Format critical messages and return string."""
return msg

def error(self, msg):
"""Format error messages and return string."""
return msg

def warn(self, msg):
"""Format warn messages and return string."""
return msg

def info(self, msg):
"""Format info messages and return string."""
return msg

def _color_text(self, color, msg):
"""
Colorize strings.
:param color: colorama color settings
:param msg: string to colorize
:returns: string
"""
return "{}{}{}".format(color, msg, colorama.Style.RESET_ALL)

def sysexit(self, code=1):
sys.exit(code)

def sysexit_with_message(self, msg, code=1):
self.logger.critical(str(msg))
self.sysexit(code)


class SingleLog(Log, metaclass=Singleton):
"""Singleton logging class."""

pass
Loading

0 comments on commit 91ce466

Please # to comment.