#!/usr/bin/env python3 # Common GUI Logging Module # Provides a consistent and easy to use logging facility. Log messages printed # to the terminal will be colorized for easy identification of log level. # # Copyright (c) 2017 Kurt Jacobson # # This program 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 2 of the License, or # (at your option) any later version. # # This program 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. import os import logging from linuxcnc import ini # For convenience import log levels so we don't need to import # logging to set the log level within other modules. from logging import DEBUG, INFO, WARNING, ERROR, CRITICAL # add a new verbose level VERBOSE = 5 logging.VERBOSE = 5 logging.addLevelName(logging.VERBOSE, "VERBOSE") # add a custom log level function def verbose(self, message, *args, **kws): if self.isEnabledFor(VERBOSE): # Yes, logger takes its '*args' as 'args'. self._log(logging.VERBOSE, message, args, **kws) # add the custom log level to the library (class patch) logging.Logger.verbose = verbose logging.Logger.VERBOSE = VERBOSE # Our custom colorizing formatters for the terminal/file handler from .colored_formatter import ColoredFormatter, QtColoredFormatter # Global name of the base logger BASE_LOGGER_NAME = None BASE_LOGGER_FILE = None # Define the log message formats TERM_FORMAT = '[%(name)s][%(levelname)s] %(message)s (%(filename)s:%(lineno)d)' FILE_FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' QTFILE_FORMAT = '%(levelname)s - %(message)s (%(filename)s:%(lineno)d)
' # Get logger for module based on module.__name__ def getLogger(name): if BASE_LOGGER_NAME is None: initBaseLogger('DEFAULT') name = '{0}.{1}'.format(BASE_LOGGER_NAME, name.upper()) return logging.getLogger(name) # Set global logging level def setGlobalLevel(level): base_log = logging.getLogger(BASE_LOGGER_NAME) base_log.setLevel(level) # Initialize the base logger def initBaseLogger(name, log_file=None, log_level=DEBUG, logToFile=False): global BASE_LOGGER_NAME BASE_LOGGER_NAME = name if not log_file: log_file = getLogFile(name) global BASE_LOGGER_FILE BASE_LOGGER_FILE = log_file # Clear the previous sessions log file with open(log_file, 'w') as fh: pass # Create base logger base_log = logging.getLogger(BASE_LOGGER_NAME) base_log.setLevel(log_level) # Add console handler ch = logging.StreamHandler() ch.setLevel(logging.VERBOSE) # is this a Qtvcp logger? Qt = bool( 'qt' in name.lower()) cf = ColoredFormatter(TERM_FORMAT, pass_clean = not Qt) ch.setFormatter(cf) base_log.addHandler(ch) # Add file handler if logToFile: fh = logging.FileHandler(log_file) fh.setLevel(logging.VERBOSE) # if Qtvcp, use Qt Rich text colors # else use plain text if Qt: ff = QtColoredFormatter(QTFILE_FORMAT) else: ff = logging.Formatter(FILE_FORMAT) fh.setFormatter(ff) base_log.addHandler(fh) # Get logger for logger log = getLogger(__name__) if logToFile: base_log.info('Logging to: yellow<{}>'.format(log_file)) return base_log # Attempt to find the log file specified INI [DISPLAY] LOG_FILE, # failing that log to $HOME/.log def getLogFile(name): # Default log file to use if not specified in INI log_file = os.path.expanduser('~/{}.log').format(name.lower()) # LinuxCNC may not be running, so use get() to avoid a KeyError ini_file = os.environ.get('INI_FILE_NAME') config_dir = os.environ.get('CONFIG_DIR') if ini_file: lcnc_ini = ini(ini_file) path = lcnc_ini.find('DISPLAY', 'LOG_FILE') if path: if path.startswith('~'): # Path is relative to $HOME log_file = os.path.expanduser(path) elif not os.path.isabs(path): # Assume intended path is relative to the INI file log_file = os.path.join(config_dir, path) else: # It must be an absolute path then log_file = os.path.realpath(path) return log_file