Source code for sertit.logs

# -*- coding: utf-8 -*-
# Copyright 2024, SERTIT-ICube - France, https://sertit.unistra.fr/
# This file is part of sertit-utils project
#     https://github.com/sertit/sertit-utils
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
""" Logging tools """
import logging
import logging.config
import os
from datetime import datetime
from typing import Union

LOGGING_FORMAT = "%(asctime)s - [%(levelname)s] - %(message)s"
SU_NAME = "sertit"


[docs] def init_logger( curr_logger: logging.Logger, log_lvl: int = logging.DEBUG, log_format: str = LOGGING_FORMAT, ) -> None: """ Initialize a very basic logger to trace the first lines in the stream. To be done before everything (like parsing log_file etc...) Args: curr_logger (logging.Logger): Logger to be initialize log_lvl (int): Logging level to be set log_format (str): Logger format to be set Example: >>> logger = logging.getLogger("logger_test") >>> init_logger(logger, logging.INFO, '%(asctime)s - [%(levelname)s] - %(message)s') >>> logger.info("MESSAGE") 2021-03-02 16:57:35 - [INFO] - MESSAGE """ logging.config.dictConfig( { "version": 1, "disable_existing_loggers": False, "formatters": { "fmt": { "format": log_format, } }, "handlers": { "stream": { "level": logging.getLevelName(log_lvl), "class": "logging.StreamHandler", "formatter": "fmt", }, }, "loggers": { curr_logger.name: { "handlers": ["stream"], "propagate": False, "level": logging.getLevelName(log_lvl), } }, } )
# pylint: disable=R0913 # Too many arguments (8/5) (too-many-arguments)
[docs] def create_logger( logger: logging.Logger, file_log_level: int = logging.DEBUG, stream_log_level: int = logging.INFO, output_folder: str = None, name: str = None, other_loggers_names: Union[str, list] = None, other_loggers_file_log_level: int = None, other_loggers_stream_log_level: int = None, ) -> None: """ Create file and stream logger at the wanted level for the given logger. - If you have :code:`colorlog` installed, it will produce colored logs. - If you do not give any output and name, it won't create any file logger It will also manage the log level of other specified logger that you give. Args: logger (logging.Logger): Logger to create file_log_level (int): File log level stream_log_level (int): Stream log level output_folder (str): Output folder. Won't create File logger if not specified name (str): Name of the log file, prefixed with the date and suffixed with _log. Can be None. other_loggers_names (Union[str, list]): Other existing logger to manage (setting the right format and log level) other_loggers_file_log_level (int): File log level for other loggers other_loggers_stream_log_level (int): Stream log level for other loggers Example: >>> logger = logging.getLogger("logger_test") >>> create_logger(logger, logging.DEBUG, logging.INFO, "path/to/log", "log.txt") >>> logger.info("MESSAGE") 2021-03-02 16:57:35 - [INFO] - MESSAGE >>> >>> # "logger_test" will also log DEBUG messages >>> # to the "path/to/log/log.txt" file with the same format """ if not isinstance(other_loggers_names, list): other_loggers_names = [other_loggers_names] # Manage other log levels if other_loggers_file_log_level is None: other_loggers_file_log_level = file_log_level if other_loggers_stream_log_level is None: other_loggers_stream_log_level = stream_log_level # Formatters basic_fmter = { "format": LOGGING_FORMAT, } try: # 'colorlog.ColoredFormatter' imported but unused from colorlog import ColoredFormatter # noqa: F401 color_fmter = { "()": "colorlog.ColoredFormatter", "format": "%(asctime)s - [%(log_color)s%(levelname)s%(reset)s] - %(message_log_color)s%(message)s", "datefmt": "%Y-%m-%d %H:%M:%S", "reset": True, "log_colors": { "DEBUG": "white", "INFO": "green", "WARNING": "cyan", "ERROR": "red", "CRITICAL": "fg_bold_red,bg_white", }, "secondary_log_colors": { "message": { "DEBUG": "white", "INFO": "green", "WARNING": "cyan", "ERROR": "red", "CRITICAL": "bold_red", } }, "style": "%", } except ModuleNotFoundError: logger.debug("Impossible to import colorlog, will log without colors.") color_fmter = basic_fmter # Initiate the logging configuration dictionary logging_dict = { "version": 1, "disable_existing_loggers": False, "formatters": {"color_fmter": color_fmter, "basic_fmter": basic_fmter}, "handlers": { "stream_main": { "level": logging.getLevelName(stream_log_level), "class": "logging.StreamHandler", "formatter": "color_fmter", }, "stream_other": { "level": logging.getLevelName(other_loggers_stream_log_level), "class": "logging.StreamHandler", "formatter": "color_fmter", }, }, } # Get logger file path if output_folder: date = datetime.today().replace(microsecond=0).strftime("%y%m%d_%H%M%S") log_file_name = f"{date}{f'_{name}' if name else ''}_log.txt" log_path = os.path.join(output_folder, log_file_name) logging_dict["handlers"].update( { "file_main": { "level": logging.getLevelName(file_log_level), "class": "logging.FileHandler", "filename": log_path, "formatter": "basic_fmter", }, "file_other": { "level": logging.getLevelName(other_loggers_file_log_level), "class": "logging.FileHandler", "filename": log_path, "formatter": "basic_fmter", }, } ) handlers_main = ["stream_main", "file_main"] handlers_other = ["stream_other", "file_other"] else: handlers_main = ["stream_main"] handlers_other = ["stream_other"] # Add logger to the config dict logging_dict.update( { "loggers": { logger.name: { "handlers": handlers_main, "propagate": False, "level": "DEBUG", } } } ) # Manage other loggers if other_loggers_names: for log_name in other_loggers_names: logging_dict["loggers"].update( { log_name: { "handlers": handlers_other, "propagate": False, "level": "DEBUG", } } ) logging.config.dictConfig(logging_dict)
[docs] def shutdown_logger(logger: logging.Logger) -> None: """ Shutdown logger (if you need to delete the log file for example) Args: logger (logging.Logger): Logger to shutdown Example: >>> logger = logging.getLogger("logger_test") >>> shutdown_logger(logger) >>> # "logger_test" won't log anything after another init """ for handler in list(logger.handlers): logger.removeHandler(handler) handler.flush() handler.close()
[docs] def reset_logging() -> None: """ Reset root logger Warning: MAYBE OVERKILL Example: >>> reset_logging() Reset root logger """ manager = logging.root.manager manager.disabled = logging.NOTSET for logger in manager.loggerDict.values(): if isinstance(logger, logging.Logger): logger.setLevel(logging.NOTSET) logger.propagate = True logger.disabled = False logger.filters.clear() handlers = logger.handlers.copy() for handler in handlers: # Copied from `logging.shutdown`. try: handler.acquire() handler.flush() handler.close() except (OSError, ValueError): pass finally: handler.release() logger.removeHandler(handler)
[docs] def deprecation_warning(msg: str) -> None: """ Create a depreciation warning. Args: msg (str): Deprecation warning Example: >>> def deprecated_fct(): >>> deprecation_warning("This function is deprecated. Use this other function instead.") >>> >>> deprecated_fct() DeprecationWarning: This function is deprecated. Use this other function instead. """ from warnings import warn warn(msg, category=DeprecationWarning, stacklevel=3) # Don't show this function