"""
.. module:: smodelsLogging
:synopsis: Simple code that creates and configures a central logger
.. moduleauthor:: Wolfgang Waltenberger <wolfgang.waltenberger@gmail.com>
"""
import os
import logging
[docs]class Colors:
"""ANSI color codes for terminal output. Disabled by default; enable via the ``on`` flag."""
def __init__ ( self ):
self.on : bool = False
@property
def debug ( self ) -> str:
return ""
@property
def error ( self ) -> str:
return self.red
@property
def warn ( self ) -> str:
return self.yellow
@property
def info ( self ) -> str:
return self.green
@property
def magenta( self ) -> str:
if self.on == False: return ""
return '\033[0;35m'
@property
def green( self ) -> str:
if self.on == False: return ""
return '\033[0;32m'
@property
def red ( self ) -> str:
if self.on == False: return ""
return '\033[0;31m'
@property
def yellow ( self ) -> str:
if self.on == False: return ""
return '\033[0;33m'
@property
def cyan ( self ) -> str:
if self.on == False: return ""
return '\033[0;36m'
@property
def blue ( self ) -> str:
if self.on == False: return ""
return '\033[0;34m'
@property
def reset ( self ) -> str:
if self.on == False: return ""
return '\033[;0m'
colors = Colors()
[docs]class ColorizedStreamHandler(logging.StreamHandler):
"""A stream handler that wraps log messages with ANSI color codes based on log level."""
def _color_wrap(self, *c: str) -> callable:
def wrapped(inp: str) -> str:
return "".join(list(c) + [inp, colors.reset])
return wrapped
def __init__(self, stream: object = None):
logging.StreamHandler.__init__(self, stream)
[docs] def should_color(self) -> bool:
# If the stream is a tty we should color it
if hasattr( self.stream, "isatty") and self.stream.isatty():
return True
# If we have an ASNI term we should color it
if os.environ.get("TERM") == "ANSI":
return True
# If anything else we should not color it
return False
[docs]def getLogger () -> logging.Logger:
"""Create and configure the central smodels logger with a colorized stream handler."""
FORMAT = '%(levelname)s in %(module)s.%(funcName)s() in' \
' %(lineno)s: %(message)s'
logging.basicConfig(format=FORMAT)
formatter = logging.Formatter( FORMAT )
ch = ColorizedStreamHandler()
ch.setFormatter ( formatter )
logger = logging.getLogger("smodels")
logger.addHandler(ch)
logger.propagate = False
return logger
logger = getLogger()
[docs]def getLogLevel( asString: bool = False ) -> int | str:
""" obtain the current log level.
:params asString: return string, not number.
"""
# return logger.level
ret = logger.getEffectiveLevel()
if not asString:
return ret
lvlNames= { logging.DEBUG: "debug", logging.INFO: "info",
logging.WARNING: "warning", logging.ERROR: "error",
logging.CRITICAL: "critical", logging.FATAL: "fatal" }
lvl = list ( lvlNames.keys() )
lvl.sort( reverse = True)
for l in lvl:
if ret >= l:
return lvlNames[l]
return "unknown log level"
[docs]def setLogLevel ( level: int | str | None ):
""" set the log level of the central logger.
can either be directly an integer ( e.g. logging.DEBUG ),
or "debug", "info", "warning", or "error".
"""
if level == None: return
if type ( level ) == int:
logger.setLevel ( level=level )
return
level = level.lower()
levels = { "debug": logging.DEBUG, "info": logging.INFO,
"warn": logging.WARNING, "warning": logging.WARNING,
"error": logging.ERROR, "critical": logging.CRITICAL,
"fatal": logging.FATAL }
if level not in levels:
logger.error ( f"Unknown log level ``{level}'' supplied!" )
return
logger.setLevel ( level = levels[level] )