blob: 6d2b724673f16433d3f22b9501959e45cb96f502 [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.
"""
multistructlog logging module
This module enables structured data to be logged to a single destination, or to
multiple destinations simulataneously. The API consists of a single function:
create_logger, which returns a structlog object. You can invoke it as follows:
log = logger.create_logger(xos_config, level=logging.INFO)
log.info('Entered function', name = '%s' % fn_name)
The default handlers in XOS are the console and Logstash. You can override the
handlers, structlog's processors, or anything else by adding keyword arguments
to create_logger:
log = logger.create_logger(xos_config, level=logging.INFO,
handlers=[logging.StreamHandler(sys.stdout),
logstash.LogstashHandler('somehost', 5617, version=1)])
Each handler depends on a specific renderer (e.g. Logstash needs JSON and
stdout needs ConsoleRenderer) but a structlog instance can enchain only one
renderer. For this reason, we apply renderers at the logging layer, as
logging formatters.
"""
import logging
import logging.config
import logstash
import structlog
import sys
import copy
import inspect
PROCESSOR_MAP = {
'StreamHandler': structlog.dev.ConsoleRenderer(),
'LogstashHandler': structlog.processors.JSONRenderer()
}
class FormatterFactory:
def __init__(self, handler_name):
self.handler_name = handler_name
def __call__(self):
try:
processor = PROCESSOR_MAP[self.handler_name]
except KeyError:
processor = structlog.processors.KeyValueRenderer()
formatter = structlog.stdlib.ProcessorFormatter(processor)
return formatter
class XOSLoggerFactory:
def __init__(self, handlers):
self.handlers = handlers
def __call__(self):
base_logger = logging.getLogger()
base_logger.handlers = []
for h in self.handlers:
formatter = FormatterFactory(h.__class__.__name__)()
h.setFormatter(formatter)
base_logger.addHandler(h)
self.logger = base_logger
return self.logger
""" We expose the Structlog logging interface directly. This should allow callers to
bind contexts incrementally and configure and use other features of structlog directly
The use of structlog in Chameleon was used for reference when writing this code.
"""
CURRENT_LOGGER = None
CURRENT_LOGGER_PARMS = None
def create_logger(_config=None, extra_processors=[],
force_create=False, level=None):
"""
Args:
_config (dict): The standard config for Python's logging module
extra_processors(dict): Custom structlog processors
force_create(bool): Forces creation of the logger
level(logging.loglevel): Overrides logging level
Returns:
log: structlog logger
"""
first_entry_elts = ['Starting']
"""Inherit base options from config"""
if _config:
logging_config = copy.deepcopy(_config)
else:
first_entry_elts.append('Config is empty')
logging_config = {'version': 1}
"""Check if a logger with this configuration has already been created, if so, return that logger
instead of creating a new one"""
global CURRENT_LOGGER
global CURRENT_LOGGER_PARMS
if CURRENT_LOGGER and CURRENT_LOGGER_PARMS == (
logging_config, extra_processors, level) and not force_create:
return CURRENT_LOGGER
if level:
try:
for k, v in logging_config['loggers'].iteritems():
v['level'] = level
except KeyError:
first_entry_elts.append('Level override failed')
logging.config.dictConfig(logging_config)
processors = copy.copy(extra_processors)
processors.extend([
structlog.processors.StackInfoRenderer(),
structlog.processors.format_exc_info,
structlog.stdlib.ProcessorFormatter.wrap_for_formatter
])
caller = inspect.stack()[1]
filename = inspect.getmodule(caller[0]).__name__
default_handlers = [
logging.StreamHandler(sys.stdout),
logging.handlers.RotatingFileHandler(
filename=filename,
maxBytes=10485760,
backupCount=1)
]
configured_handlers = logging.getLogger().handlers
handlers = configured_handlers if configured_handlers else default_handlers
factory = XOSLoggerFactory(handlers)
structlog.configure(
processors=processors,
logger_factory=factory,
)
log = structlog.get_logger()
first_entry = '. '.join(first_entry_elts)
log.info(first_entry, level_override=level, **logging_config)
CURRENT_LOGGER = log
CURRENT_LOGGER_PARMS = (logging_config, extra_processors, level)
return log
if __name__ == '__main__':
l = create_logger(
{'version': 2, 'loggers': {'': {'level': 'INFO'}}}, level="INFO")
l.info("Test OK")