blob: 632b820f8b818e777f680503686d258ce4d6afa7 [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
Zack Williams41132132018-10-19 12:14:45 -070015from six import iteritems
16import copy
Varun Belurf81a5fc2017-08-11 16:52:59 -070017import logging
18import logging.config
Varun Belurf81a5fc2017-08-11 16:52:59 -070019import structlog
Varun Belurf81a5fc2017-08-11 16:52:59 -070020
Zack Williams41132132018-10-19 12:14:45 -070021"""
22We expose the Structlog logging interface directly.
Varun Belurf81a5fc2017-08-11 16:52:59 -070023
Zack Williams41132132018-10-19 12:14:45 -070024This should allow callers to bind contexts incrementally and configure and use
25other features of structlog directly.
Sapan Bhatiab7347742017-09-22 09:41:56 -070026
Zack Williams41132132018-10-19 12:14:45 -070027To create a logging_config dictionary see these docs:
Varun Belurf81a5fc2017-08-11 16:52:59 -070028
Zack Williams41132132018-10-19 12:14:45 -070029 https://docs.python.org/2.7/library/logging.config.html#logging.config.dictConfig
30 http://www.structlog.org/en/stable/standard-library.html#rendering-using-structlog-based-formatters-within-logging
Varun Belurf81a5fc2017-08-11 16:52:59 -070031
Zack Williams41132132018-10-19 12:14:45 -070032When setting log level, the higher of logging_config['logger'][*]['level'] and
33logging_config['handler'][*]['level'] is used.
Varun Belurf81a5fc2017-08-11 16:52:59 -070034
Zack Williams41132132018-10-19 12:14:45 -070035If the handler's level is set to "DEBUG" but the handler's is set to "ERROR",
36the handler will only log "ERROR" level messages.
Varun Belurf81a5fc2017-08-11 16:52:59 -070037
Zack Williams41132132018-10-19 12:14:45 -070038List of logging levels:
39 https://docs.python.org/2.7/library/logging.html#logging-levels
Varun Belurf81a5fc2017-08-11 16:52:59 -070040
Varun Belurf81a5fc2017-08-11 16:52:59 -070041"""
42
Zack Williams41132132018-10-19 12:14:45 -070043# Add a TRACE log level to both structlog and normal python logger
44
45# new level is 5, per these locations:
46# https://github.com/python/cpython/blob/2.7/Lib/logging/__init__.py#L132
47# https://github.com/hynek/structlog/blob/18.2.0/src/structlog/stdlib.py#L296
48structlog.stdlib.TRACE = TRACE_LOGLVL = 5
49structlog.stdlib._NAME_TO_LEVEL['trace'] = TRACE_LOGLVL
50logging.addLevelName(TRACE_LOGLVL, "TRACE")
51
52
53# Create structlog TRACE level and add to BoundLogger object
54# https://github.com/hynek/structlog/blob/18.2.0/src/structlog/stdlib.py#L59
55def trace_structlog(self, event=None, *args, **kw):
56 ''' enable TRACE for structlog '''
57 return self._proxy_to_logger("trace", event, *args, **kw)
58
59
60structlog.stdlib.BoundLogger.trace = trace_structlog
61
62
63# create standard logger TRACE level and add to Logger object
64# https://github.com/python/cpython/blob/2.7/Lib/logging/__init__.py#L1152
65def trace_loglevel(self, message, *args, **kws):
66 ''' enable TRACE for standard logger'''
67 if self.isEnabledFor(TRACE_LOGLVL):
68 self._log(TRACE_LOGLVL, message, args, **kws)
69
70
71logging.Logger.trace = trace_loglevel
72
73# used to return same logger on multiple calls without reconfiguring it
74# may be somewhat redundant with "cache_logger_on_first_use" in structlog
Sapan Bhatia74cd1e42017-08-14 02:00:04 -040075CURRENT_LOGGER = None
Zack Williams41132132018-10-19 12:14:45 -070076CURRENT_LOGGER_PARAMS = None
Varun Belurf81a5fc2017-08-11 16:52:59 -070077
Sapan Bhatiab7347742017-09-22 09:41:56 -070078
Zack Williams41132132018-10-19 12:14:45 -070079def create_logger(logging_config=None, level=None):
Sapan Bhatia9ec41c22017-08-24 05:31:28 -040080 """
81 Args:
Zack Williams41132132018-10-19 12:14:45 -070082 logging_config (dict): Input to logging.config.dictConfig
83 level(logging.loglevel): Overrides logging level for all loggers
Sapan Bhatia9ec41c22017-08-24 05:31:28 -040084
85 Returns:
86 log: structlog logger
87 """
88
Sapan Bhatia74cd1e42017-08-14 02:00:04 -040089 global CURRENT_LOGGER
Zack Williams41132132018-10-19 12:14:45 -070090 global CURRENT_LOGGER_PARAMS
91
Zack Williams940ff792018-11-02 11:11:20 -070092 # if config provided, copy to prevent changes to dict from being pushed
93 # back to caller, otherwise use default config
94 if logging_config:
95 logging_config = copy.deepcopy(logging_config)
96 else:
97 logging_config = {'version': 1, "disable_existing_loggers": False}
98
Zack Williams41132132018-10-19 12:14:45 -070099 if CURRENT_LOGGER and CURRENT_LOGGER_PARAMS == (logging_config, level):
Sapan Bhatia74cd1e42017-08-14 02:00:04 -0400100 return CURRENT_LOGGER
Sapan Bhatiab7347742017-09-22 09:41:56 -0700101
Zack Williams41132132018-10-19 12:14:45 -0700102 # store unmodified config, which is changed later
103 CURRENT_LOGGER_PARAMS = (copy.deepcopy(logging_config), level)
Varun Belurf81a5fc2017-08-11 16:52:59 -0700104
Zack Williams41132132018-10-19 12:14:45 -0700105 # check if formatters exists in logging_config, set defaults if not set
106 if "formatters" not in logging_config:
107
108 logging_config['formatters'] = {
109 'json': {
110 '()': structlog.stdlib.ProcessorFormatter,
111 "processor": structlog.processors.JSONRenderer(),
112 },
113 'structured-color': {
114 '()': structlog.stdlib.ProcessorFormatter,
Zack Williams940ff792018-11-02 11:11:20 -0700115 'processor': structlog.dev.ConsoleRenderer(colors=True,
116 force_colors=True),
Zack Williams41132132018-10-19 12:14:45 -0700117 },
118 'structured': {
119 '()': structlog.stdlib.ProcessorFormatter,
120 'processor': structlog.dev.ConsoleRenderer(colors=False),
121 },
122 }
123
124 # set a default colored output to console handler if none are set
125 if "handlers" not in logging_config:
126
127 logging_config['handlers'] = {
128 'default': {
129 'class': 'logging.StreamHandler',
130 'level': 'DEBUG',
131 },
132 }
133
134 # if a formatter isn't set in a handler, use structured formatter
135 for k, v in iteritems(logging_config['handlers']):
136 if 'formatter' not in v:
137 v['formatter'] = 'structured'
138
139 # add loggers if they don't exist or default '' logger is missing
140 if "loggers" not in logging_config or '' not in logging_config['loggers']:
141
142 # By default, include all handlers in default '' logger
143 handler_l = logging_config['handlers'].keys()
144
145 logging_config['loggers'] = {
146 '': {
147 'handlers': handler_l,
148 'level': 'NOTSET', # don't filter on level in logger
149 'propagate': True
150 },
151 }
152
153 # If level is set, override log level of base loggers
154 if level:
155 for k, v in iteritems(logging_config['loggers']):
156 v['level'] = level
157
158 # configure standard logger
Varun Belurf81a5fc2017-08-11 16:52:59 -0700159 logging.config.dictConfig(logging_config)
160
Zack Williams41132132018-10-19 12:14:45 -0700161 # configure structlog
Varun Belurf81a5fc2017-08-11 16:52:59 -0700162 structlog.configure(
Zack Williams41132132018-10-19 12:14:45 -0700163 processors=[
Zack Williams940ff792018-11-02 11:11:20 -0700164 structlog.stdlib.filter_by_level,
165 structlog.stdlib.add_log_level,
166 structlog.stdlib.PositionalArgumentsFormatter(),
Zack Williams41132132018-10-19 12:14:45 -0700167 structlog.processors.StackInfoRenderer(),
168 structlog.processors.format_exc_info,
Zack Williams940ff792018-11-02 11:11:20 -0700169 structlog.processors.TimeStamper(fmt="iso"),
Zack Williams41132132018-10-19 12:14:45 -0700170 structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
171 ],
172 context_class=dict,
173 logger_factory=structlog.stdlib.LoggerFactory(),
174 wrapper_class=structlog.stdlib.BoundLogger,
175 cache_logger_on_first_use=True,
Varun Belurf81a5fc2017-08-11 16:52:59 -0700176 )
177
178 log = structlog.get_logger()
Varun Belurf81a5fc2017-08-11 16:52:59 -0700179
Sapan Bhatia74cd1e42017-08-14 02:00:04 -0400180 CURRENT_LOGGER = log
Zack Williams41132132018-10-19 12:14:45 -0700181
Varun Belurf81a5fc2017-08-11 16:52:59 -0700182 return log