1
0
mirror of https://github.com/checktheroads/hyperglass synced 2024-05-11 05:55:08 +00:00
Files
checktheroads-hyperglass/hyperglass/log.py

212 lines
5.9 KiB
Python
Raw Normal View History

2020-04-14 10:24:20 -07:00
"""Logging instance setup & configuration."""
# Standard Library
2021-01-12 00:16:34 -07:00
import sys
2021-09-15 18:25:37 -07:00
import typing as t
2020-09-28 14:43:17 -07:00
import logging
2020-04-14 10:24:20 -07:00
from datetime import datetime
# Third Party
from loguru import logger as _loguru_logger
from rich.theme import Theme
from rich.console import Console
2021-09-26 16:50:25 -07:00
from rich.logging import RichHandler
# Local
2024-04-01 23:28:46 -04:00
from .util import dict_to_kwargs
2021-09-26 16:50:25 -07:00
from .constants import __version__
if t.TYPE_CHECKING:
# Standard Library
from pathlib import Path
# Third Party
2024-04-01 23:28:46 -04:00
from loguru import Logger as Record
2021-09-26 16:50:25 -07:00
from pydantic import ByteSize
# Project
from hyperglass.models.fields import LogFormat
2020-04-14 10:24:20 -07:00
2024-04-01 23:28:46 -04:00
_FMT_DEBUG = (
"<lvl><b>[{level}]</b> {time:YYYYMMDD} {time:HH:mm:ss} <lw>|</lw>"
"<b>{line}</b> <lw>|</lw> {function}</lvl> <lvl><b>→</b></lvl> {message} {extra}"
2020-04-14 10:24:20 -07:00
)
2021-09-26 16:50:25 -07:00
2024-04-01 23:28:46 -04:00
_FMT = "<lvl><b>[{level}]</b> {time:YYYYMMDD} {time:HH:mm:ss} <lw>|</lw></lvl> {message} {extra}"
_FMT_FILE = "[{time:YYYYMMDD} {time:HH:mm:ss}] {message} {extra}"
_FMT_BASIC = "{message} {extra}"
2020-04-14 10:24:20 -07:00
_LOG_LEVELS = [
2021-04-24 10:44:40 -07:00
{"name": "TRACE", "color": "<m>"},
{"name": "DEBUG", "color": "<c>"},
{"name": "INFO", "color": "<le>"},
{"name": "SUCCESS", "color": "<g>"},
{"name": "WARNING", "color": "<y>"},
{"name": "ERROR", "color": "<y>"},
{"name": "CRITICAL", "color": "<r>"},
2020-04-14 10:24:20 -07:00
]
HyperglassConsole = Console(
theme=Theme(
{
"info": "bold cyan",
"warning": "bold yellow",
"error": "bold red",
"success": "bold green",
2024-02-27 17:44:19 -05:00
"critical": "bold bright_red",
"logging.level.info": "bold cyan",
"logging.level.warning": "bold yellow",
"logging.level.error": "bold red",
"logging.level.critical": "bold bright_red",
"logging.level.success": "bold green",
"subtle": "rgb(128,128,128)",
}
)
)
log = _loguru_logger
2020-04-14 10:24:20 -07:00
2024-04-01 23:28:46 -04:00
def formatter(record: "Record") -> str:
"""Format log messages with extra data as kwargs string."""
msg = record.get("message", "")
extra = record.get("extra", {})
extra_str = dict_to_kwargs(extra)
return " ".join((msg, extra_str))
def filter_uvicorn_values(record: "Record") -> bool:
"""Drop noisy uvicorn messages."""
drop = (
"Application startup",
"Application shutdown",
"Finished server process",
"Shutting down",
"Waiting for application",
"Started server process",
"Started parent process",
"Stopping parent process",
)
for match in drop:
if match in record["message"]:
return False
return True
class LibInterceptHandler(logging.Handler):
2021-09-15 18:25:37 -07:00
"""Custom log handler for integrating third party library logging with hyperglass's logger."""
def emit(self, record):
"""Emit log record.
See: https://github.com/Delgan/loguru (Readme)
"""
# Get corresponding Loguru level if it exists
try:
level = _loguru_logger.level(record.levelname).name
except ValueError:
level = record.levelno
# Find caller from where originated the logged message
frame, depth = logging.currentframe(), 2
while frame.f_code.co_filename == logging.__file__:
frame = frame.f_back
depth += 1
_loguru_logger.opt(depth=depth, exception=record.exc_info).log(level, record.getMessage())
2024-04-01 23:28:46 -04:00
def init_logger(level: t.Union[int, str] = logging.INFO):
2020-04-14 10:24:20 -07:00
"""Initialize hyperglass logging instance."""
2021-09-26 16:50:25 -07:00
# Reset built-in Loguru configurations.
2020-04-14 10:24:20 -07:00
_loguru_logger.remove()
2021-09-26 16:50:25 -07:00
2024-04-02 10:10:16 -04:00
if sys.stdout.isatty():
2021-09-26 16:50:25 -07:00
# Use Rich for logging if hyperglass started from a TTY.
2024-02-27 17:44:19 -05:00
2021-09-26 16:50:25 -07:00
_loguru_logger.add(
sink=RichHandler(
console=HyperglassConsole,
2021-09-26 16:50:25 -07:00
rich_tracebacks=True,
2024-04-01 23:28:46 -04:00
tracebacks_show_locals=level == logging.DEBUG,
2021-09-26 16:50:25 -07:00
log_time_format="[%Y%m%d %H:%M:%S]",
),
2024-04-01 23:28:46 -04:00
format=formatter,
2021-09-26 16:50:25 -07:00
level=level,
2024-04-01 23:28:46 -04:00
filter=filter_uvicorn_values,
2021-09-26 16:50:25 -07:00
enqueue=True,
)
else:
# Otherwise, use regular format.
2024-04-01 23:28:46 -04:00
_loguru_logger.add(
sink=sys.stdout,
enqueue=True,
format=_FMT if level == logging.INFO else _FMT_DEBUG,
level=level,
filter=filter_uvicorn_values,
)
2021-09-26 16:50:25 -07:00
2024-04-01 23:28:46 -04:00
_loguru_logger.configure(levels=_LOG_LEVELS)
2021-09-26 16:50:25 -07:00
2020-04-14 10:24:20 -07:00
return _loguru_logger
2021-09-26 16:50:25 -07:00
def enable_file_logging(
2024-04-01 23:28:46 -04:00
*,
directory: "Path",
log_format: "LogFormat",
max_size: "ByteSize",
level: t.Union[str, int],
2021-09-26 16:50:25 -07:00
) -> None:
2020-04-14 10:24:20 -07:00
"""Set up file-based logging from configuration parameters."""
if log_format == "json":
2020-04-15 11:17:03 -07:00
log_file_name = "hyperglass.log.json"
2020-04-14 10:24:20 -07:00
structured = True
else:
2020-04-15 11:17:03 -07:00
log_file_name = "hyperglass.log"
2020-04-14 10:24:20 -07:00
structured = False
2024-04-01 23:28:46 -04:00
log_file = directory / log_file_name
2020-04-14 10:24:20 -07:00
if log_format == "text":
2021-09-26 16:50:25 -07:00
now_str = datetime.utcnow().strftime("%B %d, %Y beginning at %H:%M:%S UTC")
header_lines = (
f"# {line}"
for line in (
f"hyperglass {__version__}",
f"Logs for {now_str}",
2024-04-01 23:28:46 -04:00
f"Log Level: {'INFO' if level == logging.INFO else 'DEBUG'}",
2021-09-26 16:50:25 -07:00
)
2020-04-14 10:24:20 -07:00
)
2021-09-26 16:50:25 -07:00
header = "\n" + "\n".join(header_lines) + "\n"
2020-04-14 10:24:20 -07:00
with log_file.open("a+") as lf:
2021-09-26 16:50:25 -07:00
lf.write(header)
_loguru_logger.add(
enqueue=True,
sink=log_file,
format=_FMT_FILE,
serialize=structured,
2024-04-01 23:28:46 -04:00
level=level,
2021-09-26 16:50:25 -07:00
encoding="utf8",
2024-04-01 23:28:46 -04:00
rotation=max_size.human_readable(),
2020-09-28 14:43:17 -07:00
)
2024-04-01 23:28:46 -04:00
_loguru_logger.bind(path=log_file).debug("Logging to file")
2020-04-14 10:24:20 -07:00
2024-04-01 23:28:46 -04:00
def enable_syslog_logging(*, host: str, port: int) -> None:
2020-04-14 10:24:20 -07:00
"""Set up syslog logging from configuration parameters."""
2020-09-28 14:43:17 -07:00
# Standard Library
2020-04-14 10:24:20 -07:00
from logging.handlers import SysLogHandler
2021-09-26 16:50:25 -07:00
_loguru_logger.add(
2024-04-01 23:28:46 -04:00
SysLogHandler(address=(str(host), port)),
2021-12-06 17:12:30 -07:00
format=_FMT_BASIC,
enqueue=True,
2020-04-14 10:24:20 -07:00
)
2024-04-01 23:28:46 -04:00
_loguru_logger.bind(host=host, port=port).debug("Logging to syslog target")