| # Copyright 2017-present Open Networking Foundation |
| # |
| # 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. |
| |
| from six import iteritems |
| import copy |
| import logging |
| import logging.config |
| import structlog |
| |
| """ |
| We expose the Structlog logging interface directly. |
| |
| This should allow callers to bind contexts incrementally and configure and use |
| other features of structlog directly. |
| |
| To create a logging_config dictionary see these docs: |
| |
| https://docs.python.org/2.7/library/logging.config.html#logging.config.dictConfig |
| http://www.structlog.org/en/stable/standard-library.html#rendering-using-structlog-based-formatters-within-logging |
| |
| When setting log level, the higher of logging_config['logger'][*]['level'] and |
| logging_config['handler'][*]['level'] is used. |
| |
| If the handler's level is set to "DEBUG" but the handler's is set to "ERROR", |
| the handler will only log "ERROR" level messages. |
| |
| List of logging levels: |
| https://docs.python.org/2.7/library/logging.html#logging-levels |
| |
| """ |
| |
| # Add a TRACE log level to both structlog and normal python logger |
| |
| # new level is 5, per these locations: |
| # https://github.com/python/cpython/blob/2.7/Lib/logging/__init__.py#L132 |
| # https://github.com/hynek/structlog/blob/18.2.0/src/structlog/stdlib.py#L296 |
| structlog.stdlib.TRACE = TRACE_LOGLVL = 5 |
| structlog.stdlib._NAME_TO_LEVEL['trace'] = TRACE_LOGLVL |
| logging.addLevelName(TRACE_LOGLVL, "TRACE") |
| |
| |
| # Create structlog TRACE level and add to BoundLogger object |
| # https://github.com/hynek/structlog/blob/18.2.0/src/structlog/stdlib.py#L59 |
| def trace_structlog(self, event=None, *args, **kw): |
| ''' enable TRACE for structlog ''' |
| return self._proxy_to_logger("trace", event, *args, **kw) |
| |
| |
| structlog.stdlib.BoundLogger.trace = trace_structlog |
| |
| |
| # create standard logger TRACE level and add to Logger object |
| # https://github.com/python/cpython/blob/2.7/Lib/logging/__init__.py#L1152 |
| def trace_loglevel(self, message, *args, **kws): |
| ''' enable TRACE for standard logger''' |
| if self.isEnabledFor(TRACE_LOGLVL): |
| self._log(TRACE_LOGLVL, message, args, **kws) |
| |
| |
| logging.Logger.trace = trace_loglevel |
| |
| # used to return same logger on multiple calls without reconfiguring it |
| # may be somewhat redundant with "cache_logger_on_first_use" in structlog |
| CURRENT_LOGGER = None |
| CURRENT_LOGGER_PARAMS = None |
| |
| |
| def create_logger(logging_config=None, level=None): |
| """ |
| Args: |
| logging_config (dict): Input to logging.config.dictConfig |
| level(logging.loglevel): Overrides logging level for all loggers |
| |
| Returns: |
| log: structlog logger |
| """ |
| |
| global CURRENT_LOGGER |
| global CURRENT_LOGGER_PARAMS |
| |
| if CURRENT_LOGGER and CURRENT_LOGGER_PARAMS == (logging_config, level): |
| return CURRENT_LOGGER |
| |
| # store unmodified config, which is changed later |
| CURRENT_LOGGER_PARAMS = (copy.deepcopy(logging_config), level) |
| |
| # set bare minimum config if not set |
| if not logging_config: |
| logging_config = {'version': 1} |
| |
| # check if formatters exists in logging_config, set defaults if not set |
| if "formatters" not in logging_config: |
| |
| logging_config['formatters'] = { |
| 'json': { |
| '()': structlog.stdlib.ProcessorFormatter, |
| "processor": structlog.processors.JSONRenderer(), |
| }, |
| 'structured-color': { |
| '()': structlog.stdlib.ProcessorFormatter, |
| 'processor': structlog.dev.ConsoleRenderer(colors=True), |
| }, |
| 'structured': { |
| '()': structlog.stdlib.ProcessorFormatter, |
| 'processor': structlog.dev.ConsoleRenderer(colors=False), |
| }, |
| } |
| |
| # set a default colored output to console handler if none are set |
| if "handlers" not in logging_config: |
| |
| logging_config['handlers'] = { |
| 'default': { |
| 'class': 'logging.StreamHandler', |
| 'level': 'DEBUG', |
| }, |
| } |
| |
| # if a formatter isn't set in a handler, use structured formatter |
| for k, v in iteritems(logging_config['handlers']): |
| if 'formatter' not in v: |
| v['formatter'] = 'structured' |
| |
| # add loggers if they don't exist or default '' logger is missing |
| if "loggers" not in logging_config or '' not in logging_config['loggers']: |
| |
| # By default, include all handlers in default '' logger |
| handler_l = logging_config['handlers'].keys() |
| |
| logging_config['loggers'] = { |
| '': { |
| 'handlers': handler_l, |
| 'level': 'NOTSET', # don't filter on level in logger |
| 'propagate': True |
| }, |
| } |
| |
| # If level is set, override log level of base loggers |
| if level: |
| for k, v in iteritems(logging_config['loggers']): |
| v['level'] = level |
| |
| # configure standard logger |
| logging.config.dictConfig(logging_config) |
| |
| # configure structlog |
| structlog.configure( |
| processors=[ |
| structlog.processors.StackInfoRenderer(), |
| structlog.processors.format_exc_info, |
| structlog.stdlib.ProcessorFormatter.wrap_for_formatter, |
| ], |
| context_class=dict, |
| logger_factory=structlog.stdlib.LoggerFactory(), |
| wrapper_class=structlog.stdlib.BoundLogger, |
| cache_logger_on_first_use=True, |
| ) |
| |
| log = structlog.get_logger() |
| |
| CURRENT_LOGGER = log |
| |
| return log |