blob: b4aa19b38e78bc1bcede920686087e1cb9b666df [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
45
Varun Belurf81a5fc2017-08-11 16:52:59 -070046PROCESSOR_MAP = {
47 'StreamHandler': structlog.dev.ConsoleRenderer(),
48 'LogstashHandler': structlog.processors.JSONRenderer(),
49}
50
Varun Belurf81a5fc2017-08-11 16:52:59 -070051class FormatterFactory:
52 def __init__(self, handler_name):
53 self.handler_name = handler_name
54
55 def __call__(self):
56 try:
57 processor = PROCESSOR_MAP[self.handler_name]
58 except KeyError:
59 processor = structlog.processors.KeyValueRenderer()
60
61 formatter = structlog.stdlib.ProcessorFormatter(processor)
62
63 return formatter
64
65
66class XOSLoggerFactory:
Varun Belurf81a5fc2017-08-11 16:52:59 -070067 def __call__(self):
68 base_logger = logging.getLogger()
Sapan Bhatiae437cf42017-08-21 22:41:29 -040069 for h in base_logger.handlers:
Varun Belurf81a5fc2017-08-11 16:52:59 -070070 formatter = FormatterFactory(h.__class__.__name__)()
71 h.setFormatter(formatter)
72 base_logger.addHandler(h)
73
74 self.logger = base_logger
75 return self.logger
76
77
78""" We expose the Structlog logging interface directly. This should allow callers to
79 bind contexts incrementally and configure and use other features of structlog directly
80
81 - config is the root xos configuration
82 - overrides override elements of that config, e.g. level=logging.INFO would cause debug messages to be dropped
83 - overrides can contain a 'processors' element, which lets you add processors to structlogs chain
Sapan Bhatia74cd1e42017-08-14 02:00:04 -040084 - overrides can also contain force_create = True which returns a previously created logger. Multiple threads
85 will overwrite the shared logger.
Varun Belurf81a5fc2017-08-11 16:52:59 -070086
87 The use of structlog in Chameleon was used as a reference when writing this code.
88"""
89
Sapan Bhatia74cd1e42017-08-14 02:00:04 -040090CURRENT_LOGGER = None
91CURRENT_LOGGER_PARMS = (None, None)
Varun Belurf81a5fc2017-08-11 16:52:59 -070092
93def create_logger(_config, **overrides):
Sapan Bhatia74cd1e42017-08-14 02:00:04 -040094 first_entry_elts = []
Varun Belurf81a5fc2017-08-11 16:52:59 -070095
96 """Inherit base options from config"""
97 try:
Sapan Bhatiae437cf42017-08-21 22:41:29 -040098 logging_config = copy.deepcopy(_config)
Varun Belurf81a5fc2017-08-11 16:52:59 -070099 except AttributeError:
100 first_entry_elts.append('Config is empty')
101 logging_config = {}
102
Sapan Bhatia74cd1e42017-08-14 02:00:04 -0400103 """Check if a logger with this configuration has already been created, if so, return that logger
104 instead of creating a new one"""
105 global CURRENT_LOGGER
106 global CURRENT_LOGGER_PARMS
107
108 if CURRENT_LOGGER and CURRENT_LOGGER_PARMS == (logging_config, overrides) and not overrides.get('force_create'):
109 return CURRENT_LOGGER
110
111 first_entry_elts.append('Starting')
112 first_entry_struct = {}
113
Varun Belurf81a5fc2017-08-11 16:52:59 -0700114 if overrides:
115 first_entry_struct['overrides'] = overrides
116
117 for k, v in overrides.items():
118 logging_config[k] = v
119
120 default_handlers = [
121 logging.StreamHandler(sys.stdout),
122 logstash.LogstashHandler('localhost', 5617, version=1)
123 ]
124
Varun Belurf81a5fc2017-08-11 16:52:59 -0700125 logging.config.dictConfig(logging_config)
126
127 # Processors
128 processors = overrides.get('processors', [])
129
130 processors.extend([
131 structlog.processors.StackInfoRenderer(),
132 structlog.processors.format_exc_info,
133 structlog.stdlib.ProcessorFormatter.wrap_for_formatter
134 ])
135
Sapan Bhatiae437cf42017-08-21 22:41:29 -0400136 factory = XOSLoggerFactory()
Varun Belurf81a5fc2017-08-11 16:52:59 -0700137
138 structlog.configure(
139 processors=processors,
140 logger_factory=factory,
141 )
142
143 log = structlog.get_logger()
144 first_entry = '. '.join(first_entry_elts)
145 log.info(first_entry, **first_entry_struct)
146
Sapan Bhatia74cd1e42017-08-14 02:00:04 -0400147 CURRENT_LOGGER = log
148 CURRENT_LOGGER_PARMS = (logging_config, overrides)
Varun Belurf81a5fc2017-08-11 16:52:59 -0700149 return log
Sapan Bhatia74cd1e42017-08-14 02:00:04 -0400150
151if __name__ == '__main__':
152 l = create_logger({'logging': {'version': 2, 'loggers':{'':{'level': 'INFO'}}}}, level="INFO")
153 l.info("Test OK")