InvokeAI/invokeai/backend/util/logging.py

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

280 lines
9.3 KiB
Python
Raw Normal View History

# Copyright (c) 2023 Lincoln D. Stein and The InvokeAI Development Team
"""invokeai.util.logging
2023-05-12 14:13:49 +00:00
Logging class for InvokeAI that produces console messages
2023-05-12 14:13:49 +00:00
Usage:
from invokeai.backend.util.logging import InvokeAILogger
2023-05-12 14:13:49 +00:00
logger = InvokeAILogger.getLogger(name='InvokeAI') // Initialization
(or)
logger = InvokeAILogger.getLogger(__name__) // To use the filename
logger.critical('this is critical') // Critical Message
logger.error('this is an error') // Error Message
logger.warning('this is a warning') // Warning Message
logger.info('this is info') // Info Message
logger.debug('this is debugging') // Debug Message
Console messages:
2023-05-12 14:13:49 +00:00
[12-05-2023 20]::[InvokeAI]::CRITICAL --> This is an info message [In Bold Red]
[12-05-2023 20]::[InvokeAI]::ERROR --> This is an info message [In Red]
[12-05-2023 20]::[InvokeAI]::WARNING --> This is an info message [In Yellow]
[12-05-2023 20]::[InvokeAI]::INFO --> This is an info message [In Grey]
[12-05-2023 20]::[InvokeAI]::DEBUG --> This is an info message [In Grey]
Alternate Method (in this case the logger name will be set to InvokeAI):
import invokeai.backend.util.logging as IAILogger
IAILogger.debug('this is a debugging message')
"""
2023-05-12 14:13:49 +00:00
2023-05-25 03:57:15 +00:00
import logging.handlers
import socket
import urllib.parse
2023-05-25 03:57:15 +00:00
from abc import abstractmethod
from pathlib import Path
from invokeai.app.services.config import InvokeAIAppConfig, get_invokeai_config
2023-05-12 14:13:49 +00:00
try:
import syslog
SYSLOG_AVAILABLE = True
except:
SYSLOG_AVAILABLE = False
add logging support This commit adds invokeai.backend.util.logging, which provides support for formatted console and logfile messages that follow the status reporting conventions of earlier InvokeAI versions. Examples: ### A critical error (logging.CRITICAL) *** A non-fatal error (logging.ERROR) ** A warning (logging.WARNING) >> Informational message (logging.INFO) | Debugging message (logging.DEBUG) This style logs everything through a single logging object and is identical to using Python's `logging` module. The commonly-used module-level logging functions are implemented as simple pass-thrus to logging: import invokeai.backend.util.logging as ialog ialog.debug('this is a debugging message') ialog.info('this is a informational message') ialog.log(level=logging.CRITICAL, 'get out of dodge') ialog.disable(level=logging.INFO) ialog.basicConfig(filename='/var/log/invokeai.log') Internally, the invokeai logging module creates a new default logger named "invokeai" so that its logging does not interfere with other module's use of the vanilla logging module. So `logging.error("foo")` will go through the regular logging path and not add the additional message decorations. For more control, the logging module's object-oriented logging style is also supported. The API is identical to the vanilla logging usage. In fact, the only thing that has changed is that the getLogger() method adds a custom formatter to the log messages. import logging from invokeai.backend.util.logging import InvokeAILogger logger = InvokeAILogger.getLogger(__name__) fh = logging.FileHandler('/var/invokeai.log') logger.addHandler(fh) logger.critical('this will be logged to both the console and the log file')
2023-04-11 14:46:38 +00:00
# module level functions
def debug(msg, *args, **kwargs):
InvokeAILogger.getLogger().debug(msg, *args, **kwargs)
add logging support This commit adds invokeai.backend.util.logging, which provides support for formatted console and logfile messages that follow the status reporting conventions of earlier InvokeAI versions. Examples: ### A critical error (logging.CRITICAL) *** A non-fatal error (logging.ERROR) ** A warning (logging.WARNING) >> Informational message (logging.INFO) | Debugging message (logging.DEBUG) This style logs everything through a single logging object and is identical to using Python's `logging` module. The commonly-used module-level logging functions are implemented as simple pass-thrus to logging: import invokeai.backend.util.logging as ialog ialog.debug('this is a debugging message') ialog.info('this is a informational message') ialog.log(level=logging.CRITICAL, 'get out of dodge') ialog.disable(level=logging.INFO) ialog.basicConfig(filename='/var/log/invokeai.log') Internally, the invokeai logging module creates a new default logger named "invokeai" so that its logging does not interfere with other module's use of the vanilla logging module. So `logging.error("foo")` will go through the regular logging path and not add the additional message decorations. For more control, the logging module's object-oriented logging style is also supported. The API is identical to the vanilla logging usage. In fact, the only thing that has changed is that the getLogger() method adds a custom formatter to the log messages. import logging from invokeai.backend.util.logging import InvokeAILogger logger = InvokeAILogger.getLogger(__name__) fh = logging.FileHandler('/var/invokeai.log') logger.addHandler(fh) logger.critical('this will be logged to both the console and the log file')
2023-04-11 14:46:38 +00:00
def info(msg, *args, **kwargs):
InvokeAILogger.getLogger().info(msg, *args, **kwargs)
add logging support This commit adds invokeai.backend.util.logging, which provides support for formatted console and logfile messages that follow the status reporting conventions of earlier InvokeAI versions. Examples: ### A critical error (logging.CRITICAL) *** A non-fatal error (logging.ERROR) ** A warning (logging.WARNING) >> Informational message (logging.INFO) | Debugging message (logging.DEBUG) This style logs everything through a single logging object and is identical to using Python's `logging` module. The commonly-used module-level logging functions are implemented as simple pass-thrus to logging: import invokeai.backend.util.logging as ialog ialog.debug('this is a debugging message') ialog.info('this is a informational message') ialog.log(level=logging.CRITICAL, 'get out of dodge') ialog.disable(level=logging.INFO) ialog.basicConfig(filename='/var/log/invokeai.log') Internally, the invokeai logging module creates a new default logger named "invokeai" so that its logging does not interfere with other module's use of the vanilla logging module. So `logging.error("foo")` will go through the regular logging path and not add the additional message decorations. For more control, the logging module's object-oriented logging style is also supported. The API is identical to the vanilla logging usage. In fact, the only thing that has changed is that the getLogger() method adds a custom formatter to the log messages. import logging from invokeai.backend.util.logging import InvokeAILogger logger = InvokeAILogger.getLogger(__name__) fh = logging.FileHandler('/var/invokeai.log') logger.addHandler(fh) logger.critical('this will be logged to both the console and the log file')
2023-04-11 14:46:38 +00:00
def warning(msg, *args, **kwargs):
InvokeAILogger.getLogger().warning(msg, *args, **kwargs)
add logging support This commit adds invokeai.backend.util.logging, which provides support for formatted console and logfile messages that follow the status reporting conventions of earlier InvokeAI versions. Examples: ### A critical error (logging.CRITICAL) *** A non-fatal error (logging.ERROR) ** A warning (logging.WARNING) >> Informational message (logging.INFO) | Debugging message (logging.DEBUG) This style logs everything through a single logging object and is identical to using Python's `logging` module. The commonly-used module-level logging functions are implemented as simple pass-thrus to logging: import invokeai.backend.util.logging as ialog ialog.debug('this is a debugging message') ialog.info('this is a informational message') ialog.log(level=logging.CRITICAL, 'get out of dodge') ialog.disable(level=logging.INFO) ialog.basicConfig(filename='/var/log/invokeai.log') Internally, the invokeai logging module creates a new default logger named "invokeai" so that its logging does not interfere with other module's use of the vanilla logging module. So `logging.error("foo")` will go through the regular logging path and not add the additional message decorations. For more control, the logging module's object-oriented logging style is also supported. The API is identical to the vanilla logging usage. In fact, the only thing that has changed is that the getLogger() method adds a custom formatter to the log messages. import logging from invokeai.backend.util.logging import InvokeAILogger logger = InvokeAILogger.getLogger(__name__) fh = logging.FileHandler('/var/invokeai.log') logger.addHandler(fh) logger.critical('this will be logged to both the console and the log file')
2023-04-11 14:46:38 +00:00
def error(msg, *args, **kwargs):
InvokeAILogger.getLogger().error(msg, *args, **kwargs)
2023-05-12 14:13:49 +00:00
add logging support This commit adds invokeai.backend.util.logging, which provides support for formatted console and logfile messages that follow the status reporting conventions of earlier InvokeAI versions. Examples: ### A critical error (logging.CRITICAL) *** A non-fatal error (logging.ERROR) ** A warning (logging.WARNING) >> Informational message (logging.INFO) | Debugging message (logging.DEBUG) This style logs everything through a single logging object and is identical to using Python's `logging` module. The commonly-used module-level logging functions are implemented as simple pass-thrus to logging: import invokeai.backend.util.logging as ialog ialog.debug('this is a debugging message') ialog.info('this is a informational message') ialog.log(level=logging.CRITICAL, 'get out of dodge') ialog.disable(level=logging.INFO) ialog.basicConfig(filename='/var/log/invokeai.log') Internally, the invokeai logging module creates a new default logger named "invokeai" so that its logging does not interfere with other module's use of the vanilla logging module. So `logging.error("foo")` will go through the regular logging path and not add the additional message decorations. For more control, the logging module's object-oriented logging style is also supported. The API is identical to the vanilla logging usage. In fact, the only thing that has changed is that the getLogger() method adds a custom formatter to the log messages. import logging from invokeai.backend.util.logging import InvokeAILogger logger = InvokeAILogger.getLogger(__name__) fh = logging.FileHandler('/var/invokeai.log') logger.addHandler(fh) logger.critical('this will be logged to both the console and the log file')
2023-04-11 14:46:38 +00:00
def critical(msg, *args, **kwargs):
InvokeAILogger.getLogger().critical(msg, *args, **kwargs)
def log(level, msg, *args, **kwargs):
InvokeAILogger.getLogger().log(level, msg, *args, **kwargs)
def disable(level=logging.CRITICAL):
InvokeAILogger.getLogger().disable(level)
2023-04-11 15:10:43 +00:00
def basicConfig(**kwargs):
InvokeAILogger.getLogger().basicConfig(**kwargs)
2023-05-12 14:13:49 +00:00
def getLogger(name: str = None) -> logging.Logger:
2023-04-11 16:23:13 +00:00
return InvokeAILogger.getLogger(name)
2023-05-12 14:13:49 +00:00
2023-05-25 03:57:15 +00:00
_FACILITY_MAP = dict(
LOG_KERN = syslog.LOG_KERN,
LOG_USER = syslog.LOG_USER,
LOG_MAIL = syslog.LOG_MAIL,
LOG_DAEMON = syslog.LOG_DAEMON,
LOG_AUTH = syslog.LOG_AUTH,
LOG_LPR = syslog.LOG_LPR,
LOG_NEWS = syslog.LOG_NEWS,
LOG_UUCP = syslog.LOG_UUCP,
LOG_CRON = syslog.LOG_CRON,
LOG_SYSLOG = syslog.LOG_SYSLOG,
LOG_LOCAL0 = syslog.LOG_LOCAL0,
LOG_LOCAL1 = syslog.LOG_LOCAL1,
LOG_LOCAL2 = syslog.LOG_LOCAL2,
LOG_LOCAL3 = syslog.LOG_LOCAL3,
LOG_LOCAL4 = syslog.LOG_LOCAL4,
LOG_LOCAL5 = syslog.LOG_LOCAL5,
LOG_LOCAL6 = syslog.LOG_LOCAL6,
LOG_LOCAL7 = syslog.LOG_LOCAL7,
) if SYSLOG_AVAILABLE else dict()
2023-05-25 03:57:15 +00:00
_SOCK_MAP = dict(
SOCK_STREAM = socket.SOCK_STREAM,
SOCK_DGRAM = socket.SOCK_DGRAM,
)
class InvokeAIFormatter(logging.Formatter):
'''
2023-05-25 03:57:15 +00:00
Base class for logging formatter
'''
def format(self, record):
formatter = logging.Formatter(self.log_fmt(record.levelno))
return formatter.format(record)
@abstractmethod
def log_fmt(self, levelno: int)->str:
pass
class InvokeAISyslogFormatter(InvokeAIFormatter):
'''
Formatting for syslog
'''
def log_fmt(self, levelno: int)->str:
return '%(name)s [%(process)d] <%(levelname)s> %(message)s'
class InvokeAILegacyLogFormatter(InvokeAIFormatter):
'''
Formatting for the InvokeAI Logger (legacy version)
'''
FORMATS = {
logging.DEBUG: " | %(message)s",
logging.INFO: ">> %(message)s",
logging.WARNING: "** %(message)s",
logging.ERROR: "*** %(message)s",
logging.CRITICAL: "### %(message)s",
}
def log_fmt(self,levelno:int)->str:
return self.FORMATS.get(levelno)
class InvokeAIPlainLogFormatter(InvokeAIFormatter):
'''
Custom Formatting for the InvokeAI Logger (plain version)
'''
2023-05-25 03:57:15 +00:00
def log_fmt(self, levelno: int)->str:
return "[%(asctime)s]::[%(name)s]::%(levelname)s --> %(message)s"
2023-05-25 03:57:15 +00:00
class InvokeAIColorLogFormatter(InvokeAIFormatter):
'''
Custom Formatting for the InvokeAI Logger
'''
2023-05-12 14:13:49 +00:00
# Color Codes
grey = "\x1b[38;20m"
yellow = "\x1b[33;20m"
red = "\x1b[31;20m"
2023-05-13 21:06:57 +00:00
cyan = "\x1b[36;20m"
2023-05-12 14:13:49 +00:00
bold_red = "\x1b[31;1m"
reset = "\x1b[0m"
# Log Format
2023-05-21 10:24:37 +00:00
log_format = "[%(asctime)s]::[%(name)s]::%(levelname)s --> %(message)s"
2023-05-12 14:13:49 +00:00
## More Formatting Options: %(pathname)s, %(filename)s, %(module)s, %(lineno)d
# Format Map
FORMATS = {
2023-05-21 10:24:37 +00:00
logging.DEBUG: cyan + log_format + reset,
logging.INFO: grey + log_format + reset,
logging.WARNING: yellow + log_format + reset,
logging.ERROR: red + log_format + reset,
logging.CRITICAL: bold_red + log_format + reset
2023-05-12 14:13:49 +00:00
}
2023-05-25 03:57:15 +00:00
def log_fmt(self, levelno: int)->str:
return self.FORMATS.get(levelno)
2023-05-12 14:13:49 +00:00
2023-05-25 03:57:15 +00:00
LOG_FORMATTERS = {
'plain': InvokeAIPlainLogFormatter,
'color': InvokeAIColorLogFormatter,
'syslog': InvokeAISyslogFormatter,
'legacy': InvokeAILegacyLogFormatter,
}
class InvokeAILogger(object):
loggers = dict()
2023-05-12 14:13:49 +00:00
@classmethod
def getLogger(cls, name: str = 'InvokeAI', config: InvokeAIAppConfig=None) -> logging.Logger:
if not config:
config = InvokeAIAppConfig()
config.parse_args()
2023-05-21 10:24:37 +00:00
if name not in cls.loggers:
logger = logging.getLogger(name)
2023-05-25 03:57:15 +00:00
logger.setLevel(config.log_level.upper()) # yes, strings work here
for ch in cls.getLoggers(config):
logger.addHandler(ch)
2023-05-21 10:24:37 +00:00
cls.loggers[name] = logger
return cls.loggers[name]
2023-05-25 03:57:15 +00:00
@classmethod
def getLoggers(cls, config: InvokeAIAppConfig) -> list[logging.Handler]:
handler_strs = config.log_handlers
handlers = list()
for handler in handler_strs:
handler_name,*args = handler.split('=',2)
args = args[0] if len(args) > 0 else None
# console and file get the fancy formatter.
# syslog gets a simple one
# http gets no custom formatter
formatter = LOG_FORMATTERS[config.log_format]
2023-05-25 03:57:15 +00:00
if handler_name=='console':
ch = logging.StreamHandler()
ch.setFormatter(formatter())
handlers.append(ch)
elif handler_name=='syslog':
ch = cls._parse_syslog_args(args)
ch.setFormatter(InvokeAISyslogFormatter())
handlers.append(ch)
elif handler_name=='file':
ch = cls._parse_file_args(args)
ch.setFormatter(formatter())
handlers.append(ch)
2023-05-25 03:57:15 +00:00
elif handler_name=='http':
handlers.append(cls._parse_http_args(args))
return handlers
@staticmethod
def _parse_syslog_args(
args: str=None
)-> logging.Handler:
if not SYSLOG_AVAILABLE:
raise ValueError("syslog is not available on this system")
2023-05-25 03:57:15 +00:00
if not args:
args='/dev/log' if Path('/dev/log').exists() else 'address:localhost:514'
syslog_args = dict()
try:
for a in args.split(','):
arg_name,*arg_value = a.split(':',2)
if arg_name=='address':
host,*port = arg_value
port = 514 if len(port)==0 else int(port[0])
syslog_args['address'] = (host,port)
elif arg_name=='facility':
syslog_args['facility'] = _FACILITY_MAP[arg_value[0]]
elif arg_name=='socktype':
syslog_args['socktype'] = _SOCK_MAP[arg_value[0]]
else:
syslog_args['address'] = arg_name
except:
raise ValueError(f"{args} is not a value argument list for syslog logging")
return logging.handlers.SysLogHandler(**syslog_args)
@staticmethod
def _parse_file_args(args: str=None)-> logging.Handler:
if not args:
raise ValueError("please provide filename for file logging using format 'file=/path/to/logfile.txt'")
return logging.FileHandler(args)
@staticmethod
def _parse_http_args(args: str=None)-> logging.Handler:
if not args:
raise ValueError("please provide destination for http logging using format 'http=url'")
arg_list = args.split(',')
url = urllib.parse.urlparse(arg_list.pop(0))
if url.scheme != 'http':
raise ValueError(f"the http logging module can only log to HTTP URLs, but {url.scheme} was specified")
host = url.hostname
path = url.path
port = url.port or 80
syslog_args = dict()
for a in arg_list:
arg_name, *arg_value = a.split(':',2)
if arg_name=='method':
arg_value = arg_value[0] if len(arg_value)>0 else 'GET'
syslog_args[arg_name] = arg_value
else: # TODO: Provide support for SSL context and credentials
pass
return logging.handlers.HTTPHandler(f'{host}:{port}',path,**syslog_args)