#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# Univention App Center
# appcenter logging module
#
# Copyright 2015-2022 Univention GmbH
#
# https://www.univention.de/
#
# All rights reserved.
#
# The source code of this program is made available
# under the terms of the GNU Affero General Public License version 3
# (GNU AGPL V3) as published by the Free Software Foundation.
#
# Binary versions of this program provided by Univention to you as
# well as other copyrighted, protected or trademarked materials like
# Logos, graphics, fonts, specific documentations and configurations,
# cryptographic keys etc. are subject to a license agreement between
# you and Univention and not subject to the GNU AGPL V3.
#
# In the case you use this program under the terms of the GNU AGPL V3,
# the program is provided 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public
# License with the Debian GNU/Linux or Univention distribution in file
# /usr/share/common-licenses/AGPL-3; if not, see
# <https://www.gnu.org/licenses/>.
#
'''
Univention App Center library:
Logging module
The library logs various messages to logger objects (python stdlib logging)
univention.appcenter.log defines the appcenter base logger, as well as
functions to link the logger objects to the application using the library.
>>> from univention.appcenter.log import *
>>> log_to_logfile()
>>> # logs all messages to '/var/log/univention/appcenter.log'
>>> log_to_stream()
>>> # logs messages other than debug to stdout or (warning/error) stderr
>>> base_logger = get_base_logger()
>>> base_logger.info('This is an info message')
>>> base_logger.warn('And this is a warning')
'''
import logging
import sys
from contextlib import contextmanager
LOG_FILE = '/var/log/univention/appcenter.log'
[docs]def get_base_logger():
'''Returns the base logger for univention.appcenter'''
return logging.getLogger('univention.appcenter')
[docs]class RangeFilter(logging.Filter):
'''A Filter object that filters messages in a certain
range of logging levels'''
def __init__(self, min_level=None, max_level=None):
super(RangeFilter, self).__init__()
self.min_level = min_level
self.max_level = max_level
[docs] def filter(self, record):
if self.max_level is None:
return record.levelno >= self.min_level
if self.min_level is None:
return record.levelno <= self.max_level
return self.min_level <= record.levelno <= self.max_level
[docs]class UMCHandler(logging.Handler):
'''Handler to link a logger to the UMC logging mechanism'''
[docs] def emit(self, record):
try:
from univention.management.console.log import MODULE
except ImportError:
pass
else:
msg = str(self.format(record))
if record.levelno <= logging.DEBUG:
MODULE.info(msg)
elif record.levelno <= logging.INFO:
MODULE.process(msg)
elif record.levelno <= logging.WARN:
MODULE.warn(msg)
else:
MODULE.error(msg)
[docs]class StreamReader(object):
def __init__(self, logger, level):
self.logger = logger
self.level = level
[docs] def write(self, msg):
if self.logger:
self.logger.log(self.level, msg.rstrip('\n'))
[docs]class LogCatcher(object):
def __init__(self, logger=None):
self._original_name = None
self.logger = logger
if logger:
self._original_name = logger.name
self.logs = []
[docs] def getChild(self, name):
if self.logger:
self.logger.name = '%s.%s' % (self.logger.name, name)
return self
def __del__(self):
if self.logger and self._original_name:
self.logger.name = self._original_name
[docs] def debug(self, msg):
if self.logger:
self.logger.debug(msg)
[docs] def info(self, msg):
if self.logger:
self.logger.info(msg)
self.logs.append(('OUT', msg))
[docs] def warn(self, msg):
if self.logger:
self.logger.warn(msg)
self.logs.append(('ERR', msg))
[docs] def fatal(self, msg):
if self.logger:
self.logger.warn(msg)
self.logs.append(('ERR', msg))
[docs] def has_stdout(self):
return any(self.stdout())
[docs] def has_stderr(self):
return any(self.stderr())
[docs] def stdout(self):
for level, msg in self.logs:
if level == 'OUT':
yield msg
[docs] def stderr(self):
for level, msg in self.logs:
if level == 'ERR':
yield msg
[docs] def stdstream(self):
for level, msg in self.logs:
yield msg
def _reverse_umc_module_logger(exclusive=True):
'''Function to redirect UMC logs to the univention.appcenter logger.
Useful when using legacy code when the App Center lib was part of the
UMC module
'''
try:
from univention.management.console.log import MODULE
except ImportError:
pass
else:
logger = MODULE._fallbackLogger # pylint: disable=protected-access
if exclusive:
for handler in logger.handlers:
logger.removeHandler(handler)
logger.parent = get_base_logger()
[docs]@contextmanager
def catch_stdout(logger=None):
'''Helper function to redirect stdout output to a logger. Or, if not
given, suppress completely. Useful when calling other libs that do not
use logging, instead just print statements.
'''
old_stdout = sys.stdout
old_stderr = sys.stderr
sys.stdout = StreamReader(logger, logging.INFO)
sys.stderr = StreamReader(logger, logging.WARN)
try:
yield
finally:
sys.stdout = old_stdout
sys.stderr = old_stderr
[docs]def log_to_stream():
'''Call this function to log to stdout/stderr
stdout: logging.INFO
stderr: logging.WARN and upwards
logging.DEBUG: suppressed
only the message is logged, no further formatting
stderr is logged in red (if its a tty)
'''
if not log_to_stream._already_set_up:
log_to_stream._already_set_up = True
logger = get_base_logger()
handler = logging.StreamHandler(sys.stdout)
handler.addFilter(RangeFilter(min_level=logging.INFO, max_level=logging.INFO))
logger.addHandler(handler)
handler = logging.StreamHandler(sys.stderr)
if sys.stderr.isatty():
formatter = logging.Formatter('\x1b[1;31m%(message)s\x1b[0m') # red
handler.setFormatter(formatter)
handler.addFilter(RangeFilter(min_level=logging.WARN))
logger.addHandler(handler)
log_to_stream._already_set_up = False
[docs]def get_logfile_logger(name):
mylogger = logging.getLogger(name)
mylogger.handlers = list()
log_format = '%(process)6d %(short_name)-32s %(asctime)s [%(levelname)8s]: %(message)s'
log_format_time = '%y-%m-%d %H:%M:%S'
formatter = ShortNameFormatter(log_format, log_format_time)
handler = logging.FileHandler(LOG_FILE)
handler.setFormatter(formatter)
mylogger.addHandler(handler)
mylogger.setLevel(logging.DEBUG)
return mylogger
[docs]def log_to_logfile():
'''Call this function to log to /var/log/univention/appcenter.log
Needs rights to write to it (i.e. should be root)
Formats the message so that it can be analyzed later (i.e. process id)
Logs DEBUG as well
'''
if not log_to_logfile._already_set_up:
log_to_logfile._already_set_up = True
log_format = '%(process)6d %(short_name)-32s %(asctime)s [%(levelname)8s]: ' \
'%(message)s'
log_format_time = '%y-%m-%d %H:%M:%S'
formatter = ShortNameFormatter(log_format, log_format_time)
handler = logging.FileHandler(LOG_FILE)
handler.setFormatter(formatter)
get_base_logger().addHandler(handler)
log_to_logfile._already_set_up = False
get_base_logger().setLevel(logging.DEBUG)
get_base_logger().addHandler(logging.NullHandler()) # this is to prevent warning messages