# coding=utf-8
# --------------------------------------------------------------------
# Copyright (C) 1991 - 2026 - EDF - www.code-aster.org
# This file is part of code_aster.
#
# code_aster is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# code_aster is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with code_aster. If not, see <http://www.gnu.org/licenses/>.
# --------------------------------------------------------------------
"""
:py:mod:`logger` --- Logging and messages output
************************************************
This module defines a logger object and error functions.
All message outputs should pass by this object.
Probably additional levels should be added to distinguish low-debug messages,
debug messages that may be interesting for the user (equivalent to
``INFO=2``)...
It might be necessary to refactor it in C++ for better performance and a
global access (no C interface currently)...
"""
import logging
import os
import sys
from contextlib import contextmanager
from functools import partial, wraps
from libaster import AsterError
# using these values allows to use them as `lvl` in `logger.log(lvl, ...)`
ERROR = logging.ERROR
WARNING = logging.WARNING
INFO = logging.INFO
OK = INFO
DEBUG = logging.DEBUG
RETURNCODE = {OK: 0, DEBUG: 0, WARNING: 2, ERROR: 4}
assert OK < WARNING < ERROR, (OK, WARNING, ERROR)
[docs]class HgStreamHandler(logging.StreamHandler):
"""StreamHandler switching between sys.stdout and sys.stderr
like the mercurial ui does"""
[docs] def _adjust_stream(self, level):
"""Adjust the stream according to the given level"""
self.flush()
if level >= WARNING:
self.stream = sys.stderr
else:
self.stream = sys.stdout
[docs] def emit(self, record):
"""Enhance error and warning messages"""
self._adjust_stream(record.levelno)
return logging.StreamHandler.emit(self, record)
[docs]def build_logger(level=INFO, raise_exception=True):
"""Initialize the logger with its handlers.
Arguments:
level (int): Logging level.
raise_exception (bool): If *True* (default), an exception is raised in
case of error.
Returns:
Logger: logger object.
"""
logger = logging.getLogger("code_aster")
# keep only debug, info and error
logger.critical = logger.fatal = None
if int(os.getenv("DEBUG", 0)):
level = DEBUG
logger.setLevel(level)
term = HgStreamHandler(sys.stdout)
term.setFormatter(PerLevelFormatter())
logger.addHandler(term)
if raise_exception:
logger._error_orig = logger.error
def _error(self, *args, **kwargs):
logger._error_orig(self, *args, **kwargs)
raise AsterError("SUPERVIS_99")
logger.error = _error
return logger
logger = build_logger()
[docs]@contextmanager
def loglevel(level):
"""Change the logging level.
Arguments:
level (int): Logger level.
"""
previous = logger.getEffectiveLevel()
logger.setLevel(level)
try:
yield
finally:
logger.setLevel(previous)
[docs]def with_loglevel(level=DEBUG, with_result=False):
"""Decorator to temporarly change the logging level for a function
(only for debuging a priori).
Example:
.. code-block:: python
@with_loglevel()
def my_function(its_args):
[...]
Arguments:
level (int): Logger level.
with_result (bool): If *True*, the result is logged (debug mode).
"""
def change_level(func):
"""Raw decorator"""
@wraps(func)
def wrapper(*args, **kwargs):
"""Wrapper"""
with loglevel(level):
result = func(*args, **kwargs)
if with_result:
logger.debug("returns: %s", result)
return result
return wrapper
return change_level
COLOR = {
"red": r"\033[1;31m",
"green": r"\033[1;32m",
"blue": r"\033[1;34m",
"grey": r"\033[1;30m",
"magenta": r"\033[1;35m",
"cyan": r"\033[1;36m",
"yellow": r"\033[1;33m",
"endc": r"\033[1;m",
}
try:
_colored = sys.stdout.isatty()
except AttributeError:
_colored = False
[docs]def _colorize(color, string):
"""Return the colored `string`"""
if not _colored or not string.strip():
return string
return COLOR[color] + string + COLOR["endc"]
red = partial(_colorize, "red")
green = partial(_colorize, "green")
blue = partial(_colorize, "blue")
magenta = partial(_colorize, "magenta")
cyan = partial(_colorize, "cyan")
yellow = partial(_colorize, "yellow")
grey = partial(_colorize, "grey")