blob: 49338d0eb43d5eaf9ed05db6d4989b4ec3bcc28c [file] [log] [blame]
# 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