diff --git a/src/renatoxsr-py/logger.py b/src/renatoxsr-py/logger.py new file mode 100644 index 0000000..a0d0aec --- /dev/null +++ b/src/renatoxsr-py/logger.py @@ -0,0 +1,242 @@ +#!/usr/bin/env python3 +# vim: set ft=python ts=4 sw=4 et: fileencoding=utf-8 +# SPDX-FileCopyrightText: 2025-present RenatoXSR +# SPDX-License-Identifier: BSD-3-Clause-Clear +"""Create a logger object (stdout) and optionally a logfile.""" + +from datetime import datetime +from enum import StrEnum +import logging +import os +from pathlib import Path +from platform import node, platform, system +import sys +import colorlog + + +from renatoxsr.const import COLORS, INVALID +# Defaults +FORMAT = f"[%(name)s:%(levelname)s:%(filename)s:%(funcName)s:%(lineno)d][%(process)d] %(message)s" +now = datetime.now().strftime('%Y-%m-%d_%H-%M-%S') +default_logfile = Path.cwd() / "logs" / f"{now}-{__name__}.log" + +def get_formatter(format: str = FORMAT) -> logging.Formatter: + """Return a new logger formatter. + + Args: + format (str, optional): [description]. Defaults to None. + + Returns: + logging.Formatter: [description]. + + """ + formatter = logging.Formatter(format) + return formatter + + +def set_logfile( + *, + name: str | None = None, + formatter: logging.Formatter | None = None, + format: str | None = None, + logfile: Path | None = None, +) -> logging.Handler: + """Log a message to a file . + + Args: + name (str, optional): [description]. Defaults to None. + formatter (logging.Formatter, optional): [description]. Defaults to None. + format (str, optional): [description]. Defaults to None. + logfile (Path, optional): [description]. Defaults to None. + + Returns: + logging.Handler: [description]. + + """ + # file handler + now_str = now + + if name is None: + name = __name__ + + if formatter is None: + if format is not None: + formatter = get_formatter(format) + else: + formatter = get_formatter() + + if logfile is None: + logfile = default_logfile + + if logfile.is_file(): + logfile.parent.mkdir(parents=True, exist_ok=True) + file_handler = logging.FileHandler(logfile, mode="a", encoding="utf-8") + + if logfile.is_dir(): + logfile.mkdir(parents=True, exist_ok=True) + file_handler = logging.FileHandler( + logfile / f"{name}.log", mode="a", encoding="utf-8" + ) + + file_handler.setFormatter(formatter) + + return file_handler + + +def set_loglevel(logger_instance: logging.Logger | None = None, logLevel="") -> int: + """Set the loglevel of a logger instance . + + Args: + logger_instance (logging.Logger, optional): [description]. Defaults to None. + logLevel (str, optional): logging.[DEBUG|INFO|etc]. Defaults to "". + + Returns: + logging.setLevel or None + int: EffectiveLoglevel() + + """ + if logger_instance: + logger_instance.setLevel(getattr(logging, logLevel, "INFO")) + elif logLevel: + logger.setLevel(getattr(logging, logLevel, "INFO")) + return logger.getEffectiveLevel() + + +def set_logger( + *, + name: str | None = None, + formatter: logging.Formatter | None = None, + format: str | None = None, + loglevel: str | None = None, + logfile: Path | None = None, + log_to_file: bool = False, +) -> logging.Logger: + """Create and return a logger. + + Args: + name (str): logger name. + formatter (logging.Formatter): formatter to use. + format (str): format string to create a formatter. + loglevel (str): log level to select. + logfile (Path): logfile path. + log_to_file (bool): whether to log to file. + + Returns: + logging.Logger: configured logger. + + """ + if name is None: + name = __name__ + + if formatter is None: + if isinstance(format, str): + formatter = get_formatter(format) + else: + formatter = get_formatter() + + stdout_handler = colorlog.StreamHandler() + stdout_handler.setFormatter(colorlog.ColoredFormatter( + "%(log_color)s" + FORMAT, + reset=True, + log_colors={ + 'DEBUG': 'cyan', + 'INFO': 'green', + 'WARNING': 'yellow', + 'ERROR': 'red', + 'CRITICAL': 'red,bg_white', + }, + secondary_log_colors={}, + style ='%', + )) + + # stdout handler + #stdout_handler = logging.StreamHandler() + #stdout_handler.setFormatter(formatter) + + logger = logging.getLogger(name) + logger.addHandler(stdout_handler) + + # file handler + if logfile: + log_to_file = True + + + + if log_to_file: + file_handler = set_logfile( + name=name, + formatter=formatter, + format=format, + logfile=logfile, + ) + + logger.addHandler(file_handler) + + if loglevel is None: + loglevel = os.getenv("LOGLEVEL", "INFO") + # if logging.getLevelNamesMapping().get(loglevel.upper()) is None: + if loglevel not in logging.getLevelNamesMapping(): + loglevel = "INFO" + + logger.setLevel(loglevel) + + logger.debug("Loglevel: %s", os.getenv("LOGLEVEL")) + return logger + + +# upon import: +loglevel = "INFO" +for arg in [a for a in sys.argv if a.startswith("--log")]: + if arg.startswith("--loglevel="): + loglevel = arg.split("=", 1)[1] + if arg.startswith("--logfile="): + log_to_file = arg.split("=", 1)[1] + if arg == "--debug": + loglevel = "DEBUG" +logger = set_logger() +logger.setLevel(loglevel) + + +# CRITICAL = 50 +# FATAL = CRITICAL +# ERROR = 40 +# WARNING = 30 +# WARN = WARNING +# INFO = 20 +# DEBUG = 10 +# NOTSET = 0 + +# _levelToName = { +# CRITICAL: 'CRITICAL', +# ERROR: 'ERROR', +# WARNING: 'WARNING', +# INFO: 'INFO', +# DEBUG: 'DEBUG', +# NOTSET: 'NOTSET', +# } +# _nameToLevel = { +# 'CRITICAL': CRITICAL, +# 'FATAL': FATAL, +# 'ERROR': ERROR, +# 'WARN': WARNING, +# 'WARNING': WARNING, +# 'INFO': INFO, +# 'DEBUG': DEBUG, +# 'NOTSET': NOTSET, +# } + + +# def log(level, msg, *args, **kwargs): +# logger.log(level, f"{COLORS.level}" + msg + f"{COLORS.RESET}", *args, **kwargs) + +# def debug(msg, *args, **kwargs): +# logger.log("DEBUG", msg, *args, **kwargs) + +# def info(msg, *args, **kwargs): +# logger.log("INFO", msg, *args, **kwargs) + +# def error(msg, *args, **kwargs): +# logger.log("ERROR", msg, *args, **kwargs) + +# def warning(msg, *args, **kwargs): +# logger.log("WARNING", msg, *args, **kwargs) \ No newline at end of file