diff --git a/hyperglass/log.py b/hyperglass/log.py
index 611428a..4e3c01c 100644
--- a/hyperglass/log.py
+++ b/hyperglass/log.py
@@ -2,16 +2,22 @@
# Standard Library
import os
-import sys
+import logging
from datetime import datetime
# Third Party
from loguru import logger as _loguru_logger
+from rich.logging import RichHandler
+from rich.console import Console
+from rich.theme import Theme
-_LOG_FMT = (
+_FMT_FILE = (
"[{level}] {time:YYYYMMDD} {time:HH:mm:ss} | {name}:"
"{line} | {function} → {message}"
)
+_DATE_FMT = "%Y%m%d %H:%M:%S"
+_FMT_STDOUT = "{message}"
+_FMT_BASIC = "{message}"
_LOG_LEVELS = [
{"name": "TRACE", "no": 5, "color": ""},
{"name": "DEBUG", "no": 10, "color": ""},
@@ -22,24 +28,85 @@ _LOG_LEVELS = [
{"name": "CRITICAL", "no": 50, "color": ""},
]
+_RICH_THEME = Theme(
+ {
+ "logging.level.debug": "bold grey50",
+ "logging.level.info": "bold blue",
+ "logging.level.success": "bold green",
+ "logging.level.warning": "bold yellow",
+ "logging.level.error": "bold dark_goldenrod",
+ "logging.level.critical": "bold red",
+ }
+)
+
+_RICH_CONSOLE = Console(theme=_RICH_THEME, log_time_format=_DATE_FMT)
+
+
+def _get_rich(debug: bool = False) -> RichHandler:
+ rich_kwargs = {
+ "level": "INFO",
+ "markup": True,
+ "rich_tracebacks": True,
+ "console": _RICH_CONSOLE,
+ }
+ if debug:
+ rich_kwargs["level"] = "DEBUG"
+ return RichHandler(**rich_kwargs)
+
def base_logger():
"""Initialize hyperglass logging instance."""
_loguru_logger.remove()
- _loguru_logger.add(sys.stdout, format=_LOG_FMT, level="INFO", enqueue=True)
+ _loguru_logger.add(_get_rich(), format=_FMT_BASIC, level="INFO", enqueue=True)
_loguru_logger.configure(levels=_LOG_LEVELS)
return _loguru_logger
log = base_logger()
+logging.addLevelName(25, "SUCCESS")
+
+
+def _log_success(self, message, *a, **kw):
+ """Add custom builtin logging handler for the success level."""
+ if self.isEnabledFor(25):
+ self._log(25, message, a, **kw)
+
+
+logging.Logger.success = _log_success
+
+builtin_logging_config = {
+ "version": 1,
+ "formatters": {"basic": {"format": "%(message)s"}},
+ "root": {"level": "INFO", "handlers": ["rich"]},
+ "handlers": {
+ "rich": {
+ "level": "INFO",
+ "formatter": "basic",
+ "class": "rich.logging.RichHandler",
+ },
+ "console": {
+ "level": "INFO",
+ "formatter": "basic",
+ "class": "rich.logging.RichHandler",
+ },
+ },
+ "loggers": {
+ "": {"handlers": ["console"], "level": "INFO", "propagate": True},
+ "uvicorn": {"handlers": ["rich"], "level": "INFO", "propagate": True},
+ "uvicorn.access": {"handlers": ["rich"], "level": "INFO", "propagate": True},
+ "uvicorn.error": {"handlers": ["rich"], "level": "ERROR", "propagate": True},
+ "uvicorn.asgi": {"handlers": ["rich"], "level": "INFO", "propagate": True},
+ },
+}
+
def set_log_level(logger, debug):
"""Set log level based on debug state."""
if debug:
os.environ["HYPERGLASS_LOG_LEVEL"] = "DEBUG"
logger.remove()
- logger.add(sys.stdout, format=_LOG_FMT, level="DEBUG", enqueue=True)
+ logger.add(_get_rich(True), format=_FMT_BASIC, level="DEBUG", enqueue=True)
logger.configure(levels=_LOG_LEVELS)
if debug:
@@ -78,25 +145,31 @@ def enable_file_logging(logger, log_directory, log_format, log_max_size):
with log_file.open("a+") as lf:
lf.write(f'\n\n{"".join(log_break)}\n\n')
- logger.add(log_file, rotation=log_max_size, serialize=structured, enqueue=True)
+ logger.add(
+ log_file,
+ format=_FMT_FILE,
+ rotation=log_max_size,
+ serialize=structured,
+ enqueue=True,
+ )
- logger.debug("Logging to file enabled")
+ logger.debug("Logging to {} enabled", str(log_file))
return True
def enable_syslog_logging(logger, syslog_host, syslog_port):
"""Set up syslog logging from configuration parameters."""
+
+ # Standard Library
from logging.handlers import SysLogHandler
logger.add(
SysLogHandler(address=(str(syslog_host), syslog_port)),
- format="{message}",
+ format=_FMT_BASIC,
enqueue=True,
)
logger.debug(
- "Logging to syslog target {h}:{p} enabled",
- h=str(syslog_host),
- p=str(syslog_port),
+ "Logging to syslog target {}:{} enabled", str(syslog_host), str(syslog_port),
)
return True
diff --git a/hyperglass/parsing/models/juniper.py b/hyperglass/parsing/models/juniper.py
index 032159c..849642e 100644
--- a/hyperglass/parsing/models/juniper.py
+++ b/hyperglass/parsing/models/juniper.py
@@ -192,5 +192,5 @@ class JuniperRoute(_JuniperBase):
vrf=vrf, count=count, routes=routes, winning_weight="low",
)
- log.info("Serialized Juniper response: {}", serialized)
+ log.debug("Serialized Juniper response: {}", serialized)
return serialized
diff --git a/hyperglass/util/__init__.py b/hyperglass/util/__init__.py
index 7162eb2..9a1741a 100644
--- a/hyperglass/util/__init__.py
+++ b/hyperglass/util/__init__.py
@@ -510,7 +510,7 @@ def copyfiles(src_files: Iterable[Path], dst_files: Iterable[Path]):
for _file in src_files:
copied = queue.get()
- log.success("Copied {}", str(copied))
+ log.debug("Copied {}", str(copied))
for thread in threads:
thread.join()