blob: 6d2b724673f16433d3f22b9501959e45cb96f502 [file] [log] [blame]
Varun Belurf81a5fc2017-08-11 16:52:59 -07001# Copyright 2017-present Open Networking Foundation
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""
16multistructlog logging module
17
18This module enables structured data to be logged to a single destination, or to
19multiple destinations simulataneously. The API consists of a single function:
20create_logger, which returns a structlog object. You can invoke it as follows:
21
22 log = logger.create_logger(xos_config, level=logging.INFO)
23 log.info('Entered function', name = '%s' % fn_name)
24
25The default handlers in XOS are the console and Logstash. You can override the
26handlers, structlog's processors, or anything else by adding keyword arguments
27to create_logger:
28
29 log = logger.create_logger(xos_config, level=logging.INFO,
30 handlers=[logging.StreamHandler(sys.stdout),
31 logstash.LogstashHandler('somehost', 5617, version=1)])
32
33Each handler depends on a specific renderer (e.g. Logstash needs JSON and
34stdout needs ConsoleRenderer) but a structlog instance can enchain only one
Sapan Bhatia74cd1e42017-08-14 02:00:04 -040035renderer. For this reason, we apply renderers at the logging layer, as
Varun Belurf81a5fc2017-08-11 16:52:59 -070036logging formatters.
37"""
38
39import logging
40import logging.config
41import logstash
42import structlog
43import sys
44import copy
Sapan Bhatiab7347742017-09-22 09:41:56 -070045import inspect
Varun Belurf81a5fc2017-08-11 16:52:59 -070046
Varun Belurf81a5fc2017-08-11 16:52:59 -070047PROCESSOR_MAP = {
48 'StreamHandler': structlog.dev.ConsoleRenderer(),
Sapan Bhatiab7347742017-09-22 09:41:56 -070049 'LogstashHandler': structlog.processors.JSONRenderer()
Varun Belurf81a5fc2017-08-11 16:52:59 -070050}
51
Sapan Bhatiab7347742017-09-22 09:41:56 -070052
Varun Belurf81a5fc2017-08-11 16:52:59 -070053class FormatterFactory:
54 def __init__(self, handler_name):
55 self.handler_name = handler_name
56
57 def __call__(self):
58 try:
59 processor = PROCESSOR_MAP[self.handler_name]
60 except KeyError:
61 processor = structlog.processors.KeyValueRenderer()
62
63 formatter = structlog.stdlib.ProcessorFormatter(processor)
64
65 return formatter
66
67
68class XOSLoggerFactory:
Sapan Bhatia9ec41c22017-08-24 05:31:28 -040069 def __init__(self, handlers):
70 self.handlers = handlers
71
Varun Belurf81a5fc2017-08-11 16:52:59 -070072 def __call__(self):
73 base_logger = logging.getLogger()
Sapan Bhatia9ec41c22017-08-24 05:31:28 -040074 base_logger.handlers = []
75 for h in self.handlers:
Varun Belurf81a5fc2017-08-11 16:52:59 -070076 formatter = FormatterFactory(h.__class__.__name__)()
77 h.setFormatter(formatter)
78 base_logger.addHandler(h)
79
80 self.logger = base_logger
81 return self.logger
82
83
84""" We expose the Structlog logging interface directly. This should allow callers to
Sapan Bhatiab7347742017-09-22 09:41:56 -070085 bind contexts incrementally and configure and use other features of structlog directly
Varun Belurf81a5fc2017-08-11 16:52:59 -070086
Sapan Bhatia9ec41c22017-08-24 05:31:28 -040087 The use of structlog in Chameleon was used for reference when writing this code.
Varun Belurf81a5fc2017-08-11 16:52:59 -070088"""
89
Sapan Bhatia74cd1e42017-08-14 02:00:04 -040090CURRENT_LOGGER = None
Sapan Bhatia9ec41c22017-08-24 05:31:28 -040091CURRENT_LOGGER_PARMS = None
Varun Belurf81a5fc2017-08-11 16:52:59 -070092
Sapan Bhatiab7347742017-09-22 09:41:56 -070093
94def create_logger(_config=None, extra_processors=[],
95 force_create=False, level=None):
Sapan Bhatia9ec41c22017-08-24 05:31:28 -040096 """
97 Args:
98 _config (dict): The standard config for Python's logging module
99 extra_processors(dict): Custom structlog processors
Sapan Bhatiab7347742017-09-22 09:41:56 -0700100 force_create(bool): Forces creation of the logger
Sapan Bhatia9ec41c22017-08-24 05:31:28 -0400101 level(logging.loglevel): Overrides logging level
102
103 Returns:
104 log: structlog logger
105 """
106
107 first_entry_elts = ['Starting']
Varun Belurf81a5fc2017-08-11 16:52:59 -0700108
109 """Inherit base options from config"""
Sapan Bhatia9ec41c22017-08-24 05:31:28 -0400110 if _config:
Sapan Bhatiae437cf42017-08-21 22:41:29 -0400111 logging_config = copy.deepcopy(_config)
Sapan Bhatia9ec41c22017-08-24 05:31:28 -0400112 else:
Varun Belurf81a5fc2017-08-11 16:52:59 -0700113 first_entry_elts.append('Config is empty')
Sapan Bhatia9ec41c22017-08-24 05:31:28 -0400114 logging_config = {'version': 1}
Varun Belurf81a5fc2017-08-11 16:52:59 -0700115
Sapan Bhatiab7347742017-09-22 09:41:56 -0700116 """Check if a logger with this configuration has already been created, if so, return that logger
Sapan Bhatia74cd1e42017-08-14 02:00:04 -0400117 instead of creating a new one"""
118 global CURRENT_LOGGER
119 global CURRENT_LOGGER_PARMS
Sapan Bhatiab7347742017-09-22 09:41:56 -0700120 if CURRENT_LOGGER and CURRENT_LOGGER_PARMS == (
121 logging_config, extra_processors, level) and not force_create:
Sapan Bhatia74cd1e42017-08-14 02:00:04 -0400122 return CURRENT_LOGGER
Sapan Bhatiab7347742017-09-22 09:41:56 -0700123
Sapan Bhatia9ec41c22017-08-24 05:31:28 -0400124 if level:
125 try:
Sapan Bhatiab7347742017-09-22 09:41:56 -0700126 for k, v in logging_config['loggers'].iteritems():
Sapan Bhatia9ec41c22017-08-24 05:31:28 -0400127 v['level'] = level
128 except KeyError:
129 first_entry_elts.append('Level override failed')
Varun Belurf81a5fc2017-08-11 16:52:59 -0700130
Varun Belurf81a5fc2017-08-11 16:52:59 -0700131 logging.config.dictConfig(logging_config)
132
Sapan Bhatia9ec41c22017-08-24 05:31:28 -0400133 processors = copy.copy(extra_processors)
Varun Belurf81a5fc2017-08-11 16:52:59 -0700134 processors.extend([
135 structlog.processors.StackInfoRenderer(),
136 structlog.processors.format_exc_info,
137 structlog.stdlib.ProcessorFormatter.wrap_for_formatter
138 ])
Sapan Bhatiab7347742017-09-22 09:41:56 -0700139
140 caller = inspect.stack()[1]
141 filename = inspect.getmodule(caller[0]).__name__
142
Sapan Bhatia9ec41c22017-08-24 05:31:28 -0400143 default_handlers = [
144 logging.StreamHandler(sys.stdout),
Sapan Bhatiab7347742017-09-22 09:41:56 -0700145 logging.handlers.RotatingFileHandler(
146 filename=filename,
147 maxBytes=10485760,
148 backupCount=1)
Sapan Bhatia9ec41c22017-08-24 05:31:28 -0400149 ]
Varun Belurf81a5fc2017-08-11 16:52:59 -0700150
Sapan Bhatia9ec41c22017-08-24 05:31:28 -0400151 configured_handlers = logging.getLogger().handlers
152 handlers = configured_handlers if configured_handlers else default_handlers
153 factory = XOSLoggerFactory(handlers)
Varun Belurf81a5fc2017-08-11 16:52:59 -0700154
155 structlog.configure(
156 processors=processors,
157 logger_factory=factory,
158 )
159
160 log = structlog.get_logger()
161 first_entry = '. '.join(first_entry_elts)
Sapan Bhatiab7347742017-09-22 09:41:56 -0700162 log.info(first_entry, level_override=level, **logging_config)
Varun Belurf81a5fc2017-08-11 16:52:59 -0700163
Sapan Bhatia74cd1e42017-08-14 02:00:04 -0400164 CURRENT_LOGGER = log
Sapan Bhatia9ec41c22017-08-24 05:31:28 -0400165 CURRENT_LOGGER_PARMS = (logging_config, extra_processors, level)
Varun Belurf81a5fc2017-08-11 16:52:59 -0700166 return log
Sapan Bhatia74cd1e42017-08-14 02:00:04 -0400167
Sapan Bhatiab7347742017-09-22 09:41:56 -0700168
Sapan Bhatia74cd1e42017-08-14 02:00:04 -0400169if __name__ == '__main__':
Sapan Bhatiab7347742017-09-22 09:41:56 -0700170 l = create_logger(
171 {'version': 2, 'loggers': {'': {'level': 'INFO'}}}, level="INFO")
Sapan Bhatia74cd1e42017-08-14 02:00:04 -0400172 l.info("Test OK")