#!/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