logging: port to standard Python logging

Use the standard `logging` module to configure logging instead of the
in-house `ipapython.log_manager` module and remove `ipapython.log_manager`.

Disable the logging-not-lazy and logging-format-interpolation pylint
checks.

Reviewed-By: Martin Basti <mbasti@redhat.com>
This commit is contained in:
Jan Cholasta 2016-09-14 15:45:50 +02:00 committed by Martin Basti
parent 464516489f
commit f62a0fdb90
9 changed files with 152 additions and 1743 deletions

View File

@ -13,6 +13,8 @@ from __future__ import (
absolute_import, absolute_import,
) )
import logging
import dns import dns
import getpass import getpass
import gssapi import gssapi
@ -51,7 +53,7 @@ from ipapython.dn import DN
from ipapython.install import typing from ipapython.install import typing
from ipapython.install.core import group, knob, extend_knob from ipapython.install.core import group, knob, extend_knob
from ipapython.install.common import step from ipapython.install.common import step
from ipapython.ipa_log_manager import log_mgr, root_logger from ipapython.ipa_log_manager import root_logger
from ipapython.ipautil import ( from ipapython.ipautil import (
CalledProcessError, CalledProcessError,
dir_exists, dir_exists,
@ -3366,9 +3368,12 @@ def uninstall(options):
def init(installer): def init(installer):
try: for handler in root_logger.handlers:
installer.debug = log_mgr.get_handler('console').level == 'debug' if (isinstance(handler, logging.StreamHandler) and
except KeyError: handler.stream is sys.stderr): # pylint: disable=no-member
installer.debug = handler.level == logging.DEBUG
break
else:
installer.debug = True installer.debug = True
installer.unattended = not installer.interactive installer.unattended = not installer.interactive

View File

@ -24,7 +24,9 @@ The classes in this module make heavy use of Python container emulation. If
you are unfamiliar with this Python feature, see you are unfamiliar with this Python feature, see
http://docs.python.org/ref/sequence-types.html http://docs.python.org/ref/sequence-types.html
""" """
import logging
import operator import operator
import re
import sys import sys
import threading import threading
import os import os
@ -42,7 +44,7 @@ from ipalib.text import _
from ipalib.util import classproperty from ipalib.util import classproperty
from ipalib.base import ReadOnly, lock, islocked from ipalib.base import ReadOnly, lock, islocked
from ipalib.constants import DEFAULT_CONFIG from ipalib.constants import DEFAULT_CONFIG
from ipapython import ipautil from ipapython import ipa_log_manager, ipautil
from ipapython.ipa_log_manager import ( from ipapython.ipa_log_manager import (
log_mgr, log_mgr,
LOGGING_FORMAT_FILE, LOGGING_FORMAT_FILE,
@ -439,28 +441,63 @@ class API(ReadOnly):
parser = self.build_global_parser() parser = self.build_global_parser()
self.parser = parser self.parser = parser
root_logger = ipa_log_manager.root_logger
# If logging has already been configured somewhere else (like in the # If logging has already been configured somewhere else (like in the
# installer), don't add handlers or change levels: # installer), don't add handlers or change levels:
if log_mgr.configure_state != 'default' or self.env.validate_api: if root_logger.handlers or self.env.validate_api:
return return
log_mgr.default_level = 'info'
log_mgr.configure_from_env(self.env, configure_state='api')
# Add stderr handler:
level = 'info'
if self.env.debug: if self.env.debug:
level = 'debug' level = logging.DEBUG
else:
level = logging.INFO
root_logger.setLevel(level)
for attr in self.env:
match = re.match(r'^log_logger_level_'
r'(debug|info|warn|warning|error|critical|\d+)$',
attr)
if not match:
continue
value = match.group(1)
try:
level = int(value)
except ValueError:
try:
level = {
'debug': logging.DEBUG,
'info': logging.INFO,
'warn': logging.WARNING,
'warning': logging.WARNING,
'error': logging.ERROR,
'critical': logging.CRITICAL
}[value]
except KeyError:
raise ValueError('unknown log level (%s)' % value)
value = getattr(self.env, attr)
regexps = re.split('\s*,\s*', value)
# Add the regexp, it maps to the configured level
for regexp in regexps:
root_logger.addFilter(ipa_log_manager.Filter(regexp, level))
# Add stderr handler:
level = logging.INFO
if self.env.debug:
level = logging.DEBUG
else: else:
if self.env.context == 'cli': if self.env.context == 'cli':
if self.env.verbose > 0: if self.env.verbose > 0:
level = 'info' level = logging.INFO
else: else:
level = 'warning' level = logging.WARNING
handler = logging.StreamHandler()
log_mgr.create_log_handlers([dict(name='console', handler.setLevel(level)
stream=sys.stderr, handler.setFormatter(ipa_log_manager.Formatter(LOGGING_FORMAT_STDERR))
level=level, root_logger.addHandler(handler)
format=LOGGING_FORMAT_STDERR)])
# Add file handler: # Add file handler:
if self.env.mode in ('dummy', 'unit_test'): if self.env.mode in ('dummy', 'unit_test'):
@ -475,17 +512,17 @@ class API(ReadOnly):
log.error('Could not create log_dir %r', log_dir) log.error('Could not create log_dir %r', log_dir)
return return
level = 'info' level = logging.INFO
if self.env.debug: if self.env.debug:
level = 'debug' level = logging.DEBUG
try: try:
log_mgr.create_log_handlers([dict(name='file', handler = logging.FileHandler(self.env.log)
filename=self.env.log,
level=level,
format=LOGGING_FORMAT_FILE)])
except IOError as e: except IOError as e:
log.error('Cannot open log file %r: %s', self.env.log, e) log.error('Cannot open log file %r: %s', self.env.log, e)
return return
handler.setLevel(level)
handler.setFormatter(ipa_log_manager.Formatter(LOGGING_FORMAT_FILE))
root_logger.addHandler(handler)
def build_global_parser(self, parser=None, context=None): def build_global_parser(self, parser=None, context=None):
""" """

View File

@ -22,6 +22,7 @@
Handles common operations like option parsing and logging Handles common operations like option parsing and logging
""" """
import logging
import sys import sys
import os import os
import traceback import traceback
@ -230,8 +231,12 @@ class AdminTool(object):
Logging to file is only set up after option validation and prompting; Logging to file is only set up after option validation and prompting;
before that, all output will go to the console only. before that, all output will go to the console only.
""" """
if 'console' in ipa_log_manager.log_mgr.handlers: root_logger = ipa_log_manager.root_logger
ipa_log_manager.log_mgr.remove_handler('console') for handler in root_logger.handlers:
if (isinstance(handler, logging.StreamHandler) and
handler.stream is sys.stderr): # pylint: disable=no-member
root_logger.removeHandler(handler)
break
self._setup_logging(log_file_mode=log_file_mode) self._setup_logging(log_file_mode=log_file_mode)

View File

@ -16,23 +16,19 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
#------------------------------------------------------------------------------- import logging
import os
import re
import time
import sys
import six
# Module exports # Module exports
__all__ = ['log_mgr', 'root_logger', 'standard_logging_setup', __all__ = ['log_mgr', 'root_logger', 'standard_logging_setup',
'IPA_ROOT_LOGGER_NAME', 'ISO8601_UTC_DATETIME_FMT', 'IPA_ROOT_LOGGER_NAME', 'ISO8601_UTC_DATETIME_FMT',
'LOGGING_FORMAT_STDERR', 'LOGGING_FORMAT_STDOUT', 'LOGGING_FORMAT_FILE'] 'LOGGING_FORMAT_STDERR', 'LOGGING_FORMAT_STDOUT', 'LOGGING_FORMAT_FILE']
#-------------------------------------------------------------------------------
import sys
import re
import copy
from ipapython.log_manager import LogManager, parse_log_level
#-------------------------------------------------------------------------------
# Our root logger, all loggers will be descendents of this. # Our root logger, all loggers will be descendents of this.
IPA_ROOT_LOGGER_NAME = 'ipa' IPA_ROOT_LOGGER_NAME = 'ipa'
@ -62,159 +58,88 @@ LOGGING_FORMAT_STANDARD_CONSOLE = '%(name)-12s: %(levelname)-8s %(message)s'
# Used by standard_logging_setup() for file message # Used by standard_logging_setup() for file message
LOGGING_FORMAT_STANDARD_FILE = '%(asctime)s %(levelname)s %(message)s' LOGGING_FORMAT_STANDARD_FILE = '%(asctime)s %(levelname)s %(message)s'
#-------------------------------------------------------------------------------
class IPALogManager(LogManager): def get_logger(who, bind_logger_names=False):
''' if isinstance(who, six.string_types):
Subclass the LogManager to enforce some IPA specfic logging obj_name = who
conventions. else:
obj_name = '%s.%s' % (who.__module__, who.__class__.__name__)
* Default to timestamps in UTC. if obj_name == IPA_ROOT_LOGGER_NAME:
* Default to ISO 8601 timestamp format. logger_name = obj_name
* Default the message format. else:
''' logger_name = IPA_ROOT_LOGGER_NAME + '.' + obj_name
log_logger_level_config_re = re.compile(r'^log_logger_level_(debug|info|warn|warning|error|critical|\d+)$') logger = logging.getLogger(logger_name)
def __init__(self, configure_state=None): if bind_logger_names:
''' method = 'log'
:parameters: if hasattr(who, method):
configure_state raise ValueError('%s is already bound to %s' % (method, repr(who)))
Used by clients of the log manager to track the setattr(who, method, logger)
configuration state, may be any object.
'''
super(IPALogManager, self).__init__(IPA_ROOT_LOGGER_NAME, configure_state) for method in ('debug',
'info',
'warning',
'error',
'exception',
'critical'):
if hasattr(who, method):
raise ValueError(
'%s is already bound to %s' % (method, repr(who)))
setattr(who, method, getattr(logger, method))
def configure_from_env(self, env, configure_state=None): return logger
'''
Read the loggger configuration from the Env config. The
following items may be configured:
Logger Levels
*log_logger_XXX = comma separated list of regexps*
Logger levels can be explicitly specified for specific loggers as class Filter(object):
opposed to a global logging level. Specific loggers are indiciated def __init__(self, regexp, level):
by a list of regular expressions bound to a level. If a logger's self.regexp = re.compile(regexp)
name matches the regexp then it is assigned that level. The keys self.level = level
in the Env config must begin with "log_logger_level\_" and then be
followed by a symbolic or numeric log level, for example::
log_logger_level_debug = ipapython\.dn\..* def filter(self, record):
log_logger_level_35 = ipalib\.plugins\.dogtag return (not self.regexp.match(record.name) or
record.levelno >= self.level)
The first line says any logger belonging to the ipapython.dn module
will have it's level configured to debug.
The second line say the ipa.plugins.dogtag logger will be class Formatter(logging.Formatter):
configured to level 35. def __init__(
self, fmt=LOGGING_FORMAT_STDOUT, datefmt=ISO8601_UTC_DATETIME_FMT):
super(Formatter, self).__init__(fmt, datefmt)
self.converter = time.gmtime
Note: logger names are a dot ('.') separated list forming a path
in the logger tree. The dot character is also a regular
expression metacharacter (matches any character) therefore you
will usually need to escape the dot in the logger names by
preceeding it with a backslash.
The return value of this function is a dict with the following
format:
logger_regexps
List of (regexp, level) tuples
:parameters:
env
Env object configuration values are read from.
configure_state
If other than None update the log manger's configure_state
variable to this object. Clients of the log manager can
use configure_state to track the state of the log manager.
'''
logger_regexps = []
config = {'logger_regexps' : logger_regexps,
}
for attr in ('debug', 'verbose'):
value = getattr(env, attr, None)
if value is not None:
config[attr] = value
for attr in list(env):
# Get logger level configuration
match = IPALogManager.log_logger_level_config_re.search(attr)
if match:
value = match.group(1)
level = parse_log_level(value)
value = getattr(env, attr)
regexps = re.split('\s*,\s*', value)
# Add the regexp, it maps to the configured level
for regexp in regexps:
logger_regexps.append((regexp, level))
continue
self.configure(config, configure_state)
return config
def create_log_handlers(self, configs, logger=None, configure_state=None):
'Enforce some IPA specific configurations'
configs = copy.copy(configs)
for cfg in configs:
if not 'time_zone_converter' in cfg:
cfg['time_zone_converter'] = 'utc'
if not 'datefmt' in cfg:
cfg['datefmt'] = ISO8601_UTC_DATETIME_FMT
if not 'format' in cfg:
cfg['format'] = LOGGING_FORMAT_STDOUT
return super(IPALogManager, self).create_log_handlers(configs, logger, configure_state)
#-------------------------------------------------------------------------------
def standard_logging_setup(filename=None, verbose=False, debug=False, def standard_logging_setup(filename=None, verbose=False, debug=False,
filemode='w', console_format=None): filemode='w', console_format=None):
if console_format is None: if console_format is None:
console_format = LOGGING_FORMAT_STANDARD_CONSOLE console_format = LOGGING_FORMAT_STANDARD_CONSOLE
handlers = [] root_logger.setLevel(logging.DEBUG)
# File output is always logged at debug level # File output is always logged at debug level
if filename is not None: if filename is not None:
file_handler = dict(name='file', umask = os.umask(0o177)
filename=filename, try:
filemode=filemode, file_handler = logging.FileHandler(filename, mode=filemode)
permission=0o600, finally:
level='debug', os.umask(umask)
format=LOGGING_FORMAT_STANDARD_FILE) file_handler.setLevel(logging.DEBUG)
handlers.append(file_handler) file_handler.setFormatter(Formatter(LOGGING_FORMAT_STANDARD_FILE))
root_logger.addHandler(file_handler)
level = 'error' level = logging.ERROR
if verbose: if verbose:
level = 'info' level = logging.INFO
if debug: if debug:
level = 'debug' level = logging.DEBUG
console_handler = dict(name='console', console_handler = logging.StreamHandler()
stream=sys.stderr, console_handler.setLevel(level)
level=level, console_handler.setFormatter(Formatter(console_format))
format=console_format) root_logger.addHandler(console_handler)
handlers.append(console_handler)
# default_level must be debug becuase we want the file handler to
# always log at the debug level.
log_mgr.configure(dict(default_level='debug',
handlers=handlers),
configure_state='standard')
return log_mgr.root_logger
#-------------------------------------------------------------------------------
# Single shared instance of log manager # Single shared instance of log manager
log_mgr = sys.modules[__name__]
log_mgr = IPALogManager() root_logger = logging.getLogger(IPA_ROOT_LOGGER_NAME)
log_mgr.configure(dict(default_level='error',
handlers=[]),
configure_state='default')
root_logger = log_mgr.root_logger

File diff suppressed because it is too large Load Diff

View File

@ -20,6 +20,7 @@
from __future__ import print_function from __future__ import print_function
from contextlib import contextmanager from contextlib import contextmanager
import logging
import os import os
from textwrap import wrap from textwrap import wrap
@ -28,7 +29,7 @@ from ipalib.plugable import Plugin, API
from ipalib.errors import ValidationError from ipalib.errors import ValidationError
from ipaplatform.paths import paths from ipaplatform.paths import paths
from ipapython import admintool from ipapython import admintool
from ipapython.ipa_log_manager import log_mgr from ipapython.ipa_log_manager import Filter, root_logger
""" """
@ -507,7 +508,7 @@ class IpaAdvise(admintool.AdminTool):
if not self.options.verbose: if not self.options.verbose:
# Do not print connection information by default # Do not print connection information by default
logger_name = r'ipa\.ipalib\.plugins\.rpcclient' logger_name = r'ipa\.ipalib\.plugins\.rpcclient'
log_mgr.configure(dict(logger_regexps=[(logger_name, 'warning')])) root_logger.addFilter(Filter(logger_name, logging.WARNING))
# With no argument, print the list out and exit # With no argument, print the list out and exit
if not self.args: if not self.args:

View File

@ -25,20 +25,18 @@ If the plugin is active, sets up IPA logging to also log to Beaker.
import logging import logging
from ipapython.ipa_log_manager import log_mgr from ipapython.ipa_log_manager import Formatter, root_logger
def pytest_configure(config): def pytest_configure(config):
plugin = config.pluginmanager.getplugin('BeakerLibPlugin') plugin = config.pluginmanager.getplugin('BeakerLibPlugin')
if plugin: if plugin:
root_logger.setLevel(logging.DEBUG)
handler = BeakerLibLogHandler(plugin.run_beakerlib_command) handler = BeakerLibLogHandler(plugin.run_beakerlib_command)
log_mgr.configure( handler.setLevel(logging.INFO)
{ handler.setFormatter(Formatter('[%(name)s] %(message)s'))
'default_level': 'DEBUG', root_logger.addHandler(handler)
'handlers': [{'log_handler': handler,
'format': '[%(name)s] %(message)s',
'level': 'info'}]},
configure_state='beakerlib_plugin')
class BeakerLibLogHandler(logging.Handler): class BeakerLibLogHandler(logging.Handler):

View File

@ -23,7 +23,7 @@ import os
import sys import sys
import logging import logging
from ipapython.ipa_log_manager import log_mgr from ipapython.ipa_log_manager import Formatter, root_logger
def pytest_addoption(parser): def pytest_addoption(parser):
@ -61,13 +61,7 @@ def pytest_configure(config):
capture._capturing.resume_capturing() capture._capturing.resume_capturing()
sys.stdout, sys.stderr = orig_stdout, orig_stderr sys.stdout, sys.stderr = orig_stdout, orig_stderr
log_mgr.configure( handler = LogHandler()
{ handler.setFormatter(Formatter('[%(name)s] %(message)s'))
'default_level': config.getoption('logging_level'), handler.setLevel(config.getoption('logging_level'))
'handlers': [{'log_handler': LogHandler(), root_logger.addHandler(handler)
'format': '[%(name)s] %(message)s',
'level': 'debug'},
{'level': 'debug',
'name': 'real_stderr',
'stream': sys.stderr}]},
configure_state='tests')

View File

@ -94,6 +94,8 @@ disable=
consider-merging-isinstance, # new in pylint 1.7 consider-merging-isinstance, # new in pylint 1.7
unsupported-assignment-operation # new in pylint 1.7 unsupported-assignment-operation # new in pylint 1.7
consider-iterating-dictionary, # wontfix for better python2/3 code consider-iterating-dictionary, # wontfix for better python2/3 code
logging-not-lazy,
logging-format-interpolation,
[REPORTS] [REPORTS]