#!/usr/bin/env python3
"""Python dual-logging setup (console and log file).
It supports different log levels and colorized output.
Created by Fonic <https://github.com/fonic>
Date: 04/05/20 - 02/07/23
Based on:
https://stackoverflow.com/a/13733863/1976617
https://uran198.github.io/en/python/2016/07/12/colorful-python-logging.html
https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
"""
import logging
import subprocess
import sys
from importlib.metadata import PackageNotFoundError, version
from pathlib import Path
from typing import Literal
[docs]
def _get_package_version(package_name: str) -> str:
"""Get the package version."""
try:
return version(package_name)
except PackageNotFoundError:
return "Unknown version"
[docs]
def _get_last_commit_hash() -> str:
"""Get the last Git commit hash.
.. todo::
Will look for the script commit number, not for the code commit number!
"""
try:
return (
subprocess.check_output(["git", "rev-parse", "HEAD"])
.decode("utf-8")
.strip()
)
except subprocess.CalledProcessError:
return "Unknown commit"
[docs]
def _console_handler(
output: str, level: str, color: bool, line_template: str
) -> logging.Handler:
"""Set up the console handler."""
output_stream = sys.stdout if output.lower() == "stdout" else sys.stderr
console_handler = logging.StreamHandler(output_stream)
console_handler.setLevel(level.upper())
console_formatter = LogFormatter(fmt=line_template, color=color)
console_handler.setFormatter(console_formatter)
return console_handler
[docs]
def _file_handler(
file: Path,
level: str,
color: bool,
line_template: str,
mode: Literal["a", "w"] = "w",
) -> logging.Handler | Literal[False]:
"""Set up the file handler."""
try:
logfile_handler = logging.FileHandler(file, mode=mode)
except Exception as e:
print(f"Failed to set up log file: {e}")
return False
logfile_handler.setLevel(level.upper())
logfile_formatter = LogFormatter(fmt=line_template, color=color)
logfile_handler.setFormatter(logfile_formatter)
return logfile_handler
def set_up_logging(
package_name: str,
console_log_output: str = "stdout",
console_log_level: str = "INFO",
console_log_color: bool = True,
console_log_line_template: str = "%(color_on)s[%(levelname)-8s] [%(filename)-20s]%(color_off)s %(message)s",
logfile_file: Path = Path("simultipac.log"),
logfile_log_level: str = "INFO",
logfile_log_color: bool = False,
logfile_line_template: str = "%(color_on)s[%(asctime)s] [%(levelname)-8s] [%(filename)-20s]%(color_off)s %(message)s",
logfile_mode: Literal["a", "w"] = "w",
) -> bool:
"""Set up logging with both console and file handlers."""
# Remove previous logger
for handler in logging.root.handlers[:]:
handler.close()
logging.root.removeHandler(handler)
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
logger.addHandler(
_console_handler(
console_log_output,
console_log_level,
console_log_color,
console_log_line_template,
)
)
logfile_handler = _file_handler(
logfile_file,
logfile_log_level,
logfile_log_color,
logfile_line_template,
mode=logfile_mode,
)
if not logfile_handler:
return False
logger.addHandler(logfile_handler)
logger.info(_log_header(package_name))
return True
[docs]
def main():
"""Main function."""
if not set_up_logging(
package_name="Simultipac",
console_log_output="stdout",
console_log_level="warning",
console_log_color=True,
logfile_file=Path("simultipac.log"),
logfile_log_level="INFO",
logfile_log_color=False,
):
print("Failed to set up logging, aborting.")
return 1
# Sample log messages
logging.debug("Debug message")
logging.info("Info message")
logging.warning("Warning message")
logging.error("Error message")
logging.critical("Critical message")
return 0
if __name__ == "__main__":
sys.exit(main())