# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import logging import logging.config import logging.handlers import sys import traceback import weakref from daiquiri import output class KeywordArgumentAdapter(logging.LoggerAdapter): """Logger adapter to add keyword arguments to log record's extra data Keywords passed to the log call are added to the "extra" dictionary passed to the underlying logger so they are emitted with the log message and available to the format string. Special keywords: extra An existing dictionary of extra values to be passed to the logger. If present, the dictionary is copied and extended. """ def process(self, msg, kwargs): # Make a new extra dictionary combining the values we were # given when we were constructed and anything from kwargs. extra = self.extra.copy() if 'extra' in kwargs: extra.update(kwargs.pop('extra')) # Move any unknown keyword arguments into the extra # dictionary. for name in list(kwargs.keys()): if name == 'exc_info': continue extra[name] = kwargs.pop(name) extra['_daiquiri_extra_keys'] = set(extra.keys()) kwargs['extra'] = extra return msg, kwargs if sys.version_info.major == 2: def setLevel(self, level): """ Set the specified level on the underlying logger. """ self.logger.setLevel(level) _LOGGERS = weakref.WeakValueDictionary() def getLogger(name=None, **kwargs): """Build a logger with the given name. :param name: The name for the logger. This is usually the module name, ``__name__``. :type name: string """ adapter = _LOGGERS.get(name) if not adapter: # NOTE(jd) Keep using the `adapter' variable here because so it's not # collected by Python since _LOGGERS contains only a weakref adapter = KeywordArgumentAdapter(logging.getLogger(name), kwargs) _LOGGERS[name] = adapter return adapter def setup(level=logging.WARNING, outputs=[output.STDERR], program_name=None, capture_warnings=True): """Setup Python logging. This will setup basic handlers for Python logging. :param level: Root log level. :param outputs: Iterable of outputs to log to. :param program_name: The name of the program. Auto-detected if not set. :param capture_warnings: Capture warnings from the `warnings' module. """ root_logger = logging.getLogger(None) # Remove all handlers for handler in list(root_logger.handlers): root_logger.removeHandler(handler) # Add configured handlers for out in outputs: if isinstance(out, str): out = output.preconfigured.get(out) if out is None: raise RuntimeError("Output {} is not available".format(out)) out.add_to_logger(root_logger) root_logger.setLevel(level) program_logger = logging.getLogger(program_name) def logging_excepthook(exc_type, value, tb): program_logger.critical( "".join(traceback.format_exception(exc_type, value, tb))) sys.excepthook = logging_excepthook if capture_warnings: logging.captureWarnings(True) def parse_and_set_default_log_levels(default_log_levels, separator='='): """Set default log levels for some loggers. :param default_log_levels: List of strings with format """ return set_default_log_levels((pair.split(separator, 1) for pair in default_log_levels)) def set_default_log_levels(loggers_and_log_levels): """Set default log levels for some loggers. :param loggers_and_log_levels: List of tuple (logger name, level). """ for logger, level in loggers_and_log_levels: if isinstance(level, str): level = level.upper() logging.getLogger(logger).setLevel(level)