blob: a92753ee5bab647fcb8d4a047db4f774429a8ba0 [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
35renderer. For this reason, we apply the renderer at the logging layer, as
36logging formatters.
37"""
38
39import logging
40import logging.config
41import logstash
42import structlog
43import sys
44import copy
45
46
47PROCESSOR_MAP = {
48 'StreamHandler': structlog.dev.ConsoleRenderer(),
49 'LogstashHandler': structlog.processors.JSONRenderer(),
50}
51
52
53class 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:
69 def __init__(self, handlers):
70 self.handlers = handlers
71
72 def __call__(self):
73 base_logger = logging.getLogger()
74 for h in self.handlers:
75 formatter = FormatterFactory(h.__class__.__name__)()
76 h.setFormatter(formatter)
77 base_logger.addHandler(h)
78
79 self.logger = base_logger
80 return self.logger
81
82
83""" We expose the Structlog logging interface directly. This should allow callers to
84 bind contexts incrementally and configure and use other features of structlog directly
85
86 - config is the root xos configuration
87 - overrides override elements of that config, e.g. level=logging.INFO would cause debug messages to be dropped
88 - overrides can contain a 'processors' element, which lets you add processors to structlogs chain
89
90 The use of structlog in Chameleon was used as a reference when writing this code.
91"""
92
93
94def create_logger(_config, **overrides):
95 first_entry_elts = ['Starting']
96 first_entry_struct = {}
97
98 """Inherit base options from config"""
99 try:
100 logging_config = copy.deepcopy(_config.get('logging'))
101 except AttributeError:
102 first_entry_elts.append('Config is empty')
103 logging_config = {}
104
105 if overrides:
106 first_entry_struct['overrides'] = overrides
107
108 for k, v in overrides.items():
109 logging_config[k] = v
110
111 default_handlers = [
112 logging.StreamHandler(sys.stdout),
113 logstash.LogstashHandler('localhost', 5617, version=1)
114 ]
115
116 handlers = logging_config.get('handlers', default_handlers)
117 logging.config.dictConfig(logging_config)
118
119 # Processors
120 processors = overrides.get('processors', [])
121
122 processors.extend([
123 structlog.processors.StackInfoRenderer(),
124 structlog.processors.format_exc_info,
125 structlog.stdlib.ProcessorFormatter.wrap_for_formatter
126 ])
127
128 factory = XOSLoggerFactory(handlers)
129
130 structlog.configure(
131 processors=processors,
132 logger_factory=factory,
133 )
134
135 log = structlog.get_logger()
136 first_entry = '. '.join(first_entry_elts)
137 log.info(first_entry, **first_entry_struct)
138
139 return log