blob: c0b344e3233cf988c36b908dcaffb446776353cb [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001"""
2BitBake 'msg' implementation
3
4Message handling infrastructure for bitbake
5
6"""
7
8# Copyright (C) 2006 Richard Purdie
9#
Brad Bishopc342db32019-05-15 21:57:59 -040010# SPDX-License-Identifier: GPL-2.0-only
Patrick Williamsc124f4f2015-09-15 14:41:29 -050011#
Patrick Williamsc124f4f2015-09-15 14:41:29 -050012
13import sys
14import copy
15import logging
Andrew Geissler82c905d2020-04-13 13:39:40 -050016import logging.config
Patrick Williamsc124f4f2015-09-15 14:41:29 -050017from itertools import groupby
Patrick Williamsc124f4f2015-09-15 14:41:29 -050018import bb
19import bb.event
20
21class BBLogFormatter(logging.Formatter):
22 """Formatter which ensures that our 'plain' messages (logging.INFO + 1) are used as is"""
23
24 DEBUG3 = logging.DEBUG - 2
25 DEBUG2 = logging.DEBUG - 1
26 DEBUG = logging.DEBUG
27 VERBOSE = logging.INFO - 1
28 NOTE = logging.INFO
29 PLAIN = logging.INFO + 1
Brad Bishop1a4b7ee2018-12-16 17:11:34 -080030 VERBNOTE = logging.INFO + 2
Patrick Williamsc124f4f2015-09-15 14:41:29 -050031 ERROR = logging.ERROR
32 WARNING = logging.WARNING
33 CRITICAL = logging.CRITICAL
34
35 levelnames = {
36 DEBUG3 : 'DEBUG',
37 DEBUG2 : 'DEBUG',
38 DEBUG : 'DEBUG',
39 VERBOSE: 'NOTE',
40 NOTE : 'NOTE',
41 PLAIN : '',
Brad Bishop1a4b7ee2018-12-16 17:11:34 -080042 VERBNOTE: 'NOTE',
Patrick Williamsc124f4f2015-09-15 14:41:29 -050043 WARNING : 'WARNING',
44 ERROR : 'ERROR',
45 CRITICAL: 'ERROR',
46 }
47
48 color_enabled = False
Patrick Williamsc0f7c042017-02-23 20:41:17 -060049 BASECOLOR, BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = list(range(29,38))
Patrick Williamsc124f4f2015-09-15 14:41:29 -050050
51 COLORS = {
52 DEBUG3 : CYAN,
53 DEBUG2 : CYAN,
54 DEBUG : CYAN,
55 VERBOSE : BASECOLOR,
56 NOTE : BASECOLOR,
57 PLAIN : BASECOLOR,
Brad Bishop1a4b7ee2018-12-16 17:11:34 -080058 VERBNOTE: BASECOLOR,
Patrick Williamsc124f4f2015-09-15 14:41:29 -050059 WARNING : YELLOW,
60 ERROR : RED,
61 CRITICAL: RED,
62 }
63
64 BLD = '\033[1;%dm'
65 STD = '\033[%dm'
66 RST = '\033[0m'
67
68 def getLevelName(self, levelno):
69 try:
70 return self.levelnames[levelno]
71 except KeyError:
72 self.levelnames[levelno] = value = 'Level %d' % levelno
73 return value
74
75 def format(self, record):
76 record.levelname = self.getLevelName(record.levelno)
77 if record.levelno == self.PLAIN:
78 msg = record.getMessage()
79 else:
80 if self.color_enabled:
81 record = self.colorize(record)
82 msg = logging.Formatter.format(self, record)
Patrick Williamsc0f7c042017-02-23 20:41:17 -060083 if hasattr(record, 'bb_exc_formatted'):
84 msg += '\n' + ''.join(record.bb_exc_formatted)
85 elif hasattr(record, 'bb_exc_info'):
Patrick Williamsc124f4f2015-09-15 14:41:29 -050086 etype, value, tb = record.bb_exc_info
87 formatted = bb.exceptions.format_exception(etype, value, tb, limit=5)
88 msg += '\n' + ''.join(formatted)
89 return msg
90
91 def colorize(self, record):
92 color = self.COLORS[record.levelno]
93 if self.color_enabled and color is not None:
94 record = copy.copy(record)
95 record.levelname = "".join([self.BLD % color, record.levelname, self.RST])
96 record.msg = "".join([self.STD % color, record.msg, self.RST])
97 return record
98
99 def enable_color(self):
100 self.color_enabled = True
101
Andrew Geissler82c905d2020-04-13 13:39:40 -0500102 def __repr__(self):
103 return "%s fmt='%s' color=%s" % (self.__class__.__name__, self._fmt, "True" if self.color_enabled else "False")
104
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500105class BBLogFilter(object):
106 def __init__(self, handler, level, debug_domains):
107 self.stdlevel = level
108 self.debug_domains = debug_domains
109 loglevel = level
110 for domain in debug_domains:
111 if debug_domains[domain] < loglevel:
112 loglevel = debug_domains[domain]
113 handler.setLevel(loglevel)
114 handler.addFilter(self)
115
116 def filter(self, record):
117 if record.levelno >= self.stdlevel:
118 return True
119 if record.name in self.debug_domains and record.levelno >= self.debug_domains[record.name]:
120 return True
121 return False
122
Andrew Geissler82c905d2020-04-13 13:39:40 -0500123class LogFilterGEQLevel(logging.Filter):
124 def __init__(self, level):
125 self.strlevel = str(level)
126 self.level = stringToLevel(level)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500127
Andrew Geissler82c905d2020-04-13 13:39:40 -0500128 def __repr__(self):
129 return "%s level >= %s (%d)" % (self.__class__.__name__, self.strlevel, self.level)
130
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500131 def filter(self, record):
Andrew Geissler82c905d2020-04-13 13:39:40 -0500132 return (record.levelno >= self.level)
133
134class LogFilterLTLevel(logging.Filter):
135 def __init__(self, level):
136 self.strlevel = str(level)
137 self.level = stringToLevel(level)
138
139 def __repr__(self):
140 return "%s level < %s (%d)" % (self.__class__.__name__, self.strlevel, self.level)
141
142 def filter(self, record):
143 return (record.levelno < self.level)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500144
145# Message control functions
146#
147
Andrew Geissler82c905d2020-04-13 13:39:40 -0500148loggerDefaultLogLevel = BBLogFormatter.NOTE
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500149loggerDefaultVerbose = False
150loggerVerboseLogs = False
Andrew Geissler82c905d2020-04-13 13:39:40 -0500151loggerDefaultDomains = {}
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500152
153def init_msgconfig(verbose, debug, debug_domains=None):
154 """
155 Set default verbosity and debug levels config the logger
156 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500157 bb.msg.loggerDefaultVerbose = verbose
158 if verbose:
159 bb.msg.loggerVerboseLogs = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500160
161 if debug:
Andrew Geissler82c905d2020-04-13 13:39:40 -0500162 bb.msg.loggerDefaultLogLevel = BBLogFormatter.DEBUG - debug + 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500163 elif verbose:
Andrew Geissler82c905d2020-04-13 13:39:40 -0500164 bb.msg.loggerDefaultLogLevel = BBLogFormatter.VERBOSE
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500165 else:
Andrew Geissler82c905d2020-04-13 13:39:40 -0500166 bb.msg.loggerDefaultLogLevel = BBLogFormatter.NOTE
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500167
Andrew Geissler82c905d2020-04-13 13:39:40 -0500168 bb.msg.loggerDefaultDomains = {}
169 if debug_domains:
170 for (domainarg, iterator) in groupby(debug_domains):
171 dlevel = len(tuple(iterator))
172 bb.msg.loggerDefaultDomains["BitBake.%s" % domainarg] = logging.DEBUG - dlevel + 1
173
174def constructLogOptions():
175 return loggerDefaultLogLevel, loggerDefaultDomains
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500176
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600177def addDefaultlogFilter(handler, cls = BBLogFilter, forcelevel=None):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500178 level, debug_domains = constructLogOptions()
179
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600180 if forcelevel is not None:
181 level = forcelevel
182
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500183 cls(handler, level, debug_domains)
184
Andrew Geissler82c905d2020-04-13 13:39:40 -0500185def stringToLevel(level):
186 try:
187 return int(level)
188 except ValueError:
189 pass
190
191 try:
192 return getattr(logging, level)
193 except AttributeError:
194 pass
195
196 return getattr(BBLogFormatter, level)
197
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500198#
199# Message handling functions
200#
201
202def fatal(msgdomain, msg):
203 if msgdomain:
204 logger = logging.getLogger("BitBake.%s" % msgdomain)
205 else:
206 logger = logging.getLogger("BitBake")
207 logger.critical(msg)
208 sys.exit(1)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500209
210def logger_create(name, output=sys.stderr, level=logging.INFO, preserve_handlers=False, color='auto'):
211 """Standalone logger creation function"""
212 logger = logging.getLogger(name)
213 console = logging.StreamHandler(output)
214 format = bb.msg.BBLogFormatter("%(levelname)s: %(message)s")
215 if color == 'always' or (color == 'auto' and output.isatty()):
216 format.enable_color()
217 console.setFormatter(format)
218 if preserve_handlers:
219 logger.addHandler(console)
220 else:
221 logger.handlers = [console]
222 logger.setLevel(level)
223 return logger
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500224
225def has_console_handler(logger):
226 for handler in logger.handlers:
227 if isinstance(handler, logging.StreamHandler):
228 if handler.stream in [sys.stderr, sys.stdout]:
229 return True
230 return False
Andrew Geissler82c905d2020-04-13 13:39:40 -0500231
232def mergeLoggingConfig(logconfig, userconfig):
233 logconfig = copy.deepcopy(logconfig)
234 userconfig = copy.deepcopy(userconfig)
235
236 # Merge config with the default config
237 if userconfig.get('version') != logconfig['version']:
238 raise BaseException("Bad user configuration version. Expected %r, got %r" % (logconfig['version'], userconfig.get('version')))
239
240 # Set some defaults to make merging easier
241 userconfig.setdefault("loggers", {})
242
243 # If a handler, formatter, or filter is defined in the user
244 # config, it will replace an existing one in the default config
245 for k in ("handlers", "formatters", "filters"):
246 logconfig.setdefault(k, {}).update(userconfig.get(k, {}))
247
248 seen_loggers = set()
249 for name, l in logconfig["loggers"].items():
250 # If the merge option is set, merge the handlers and
251 # filters. Otherwise, if it is False, this logger won't get
252 # add to the set of seen loggers and will replace the
253 # existing one
254 if l.get('bitbake_merge', True):
255 ulogger = userconfig["loggers"].setdefault(name, {})
256 ulogger.setdefault("handlers", [])
257 ulogger.setdefault("filters", [])
258
259 # Merge lists
260 l.setdefault("handlers", []).extend(ulogger["handlers"])
261 l.setdefault("filters", []).extend(ulogger["filters"])
262
263 # Replace other properties if present
264 if "level" in ulogger:
265 l["level"] = ulogger["level"]
266
267 if "propagate" in ulogger:
268 l["propagate"] = ulogger["propagate"]
269
270 seen_loggers.add(name)
271
272 # Add all loggers present in the user config, but not any that
273 # have already been processed
274 for name in set(userconfig["loggers"].keys()) - seen_loggers:
275 logconfig["loggers"][name] = userconfig["loggers"][name]
276
277 return logconfig
278
279def setLoggingConfig(defaultconfig, userconfigfile=None):
280 logconfig = copy.deepcopy(defaultconfig)
281
282 if userconfigfile:
283 with open(userconfigfile, 'r') as f:
284 if userconfigfile.endswith('.yml') or userconfigfile.endswith('.yaml'):
285 import yaml
286 userconfig = yaml.load(f)
287 elif userconfigfile.endswith('.json') or userconfigfile.endswith('.cfg'):
288 import json
289 userconfig = json.load(f)
290 else:
291 raise BaseException("Unrecognized file format: %s" % userconfigfile)
292
293 if userconfig.get('bitbake_merge', True):
294 logconfig = mergeLoggingConfig(logconfig, userconfig)
295 else:
296 # Replace the entire default config
297 logconfig = userconfig
298
299 # Convert all level parameters to integers in case users want to use the
300 # bitbake defined level names
301 for h in logconfig["handlers"].values():
302 if "level" in h:
303 h["level"] = bb.msg.stringToLevel(h["level"])
304
305 for l in logconfig["loggers"].values():
306 if "level" in l:
307 l["level"] = bb.msg.stringToLevel(l["level"])
308
309 conf = logging.config.dictConfigClass(logconfig)
310 conf.configure()
311
312 # The user may have specified logging domains they want at a higher debug
313 # level than the standard.
314 for name, l in logconfig["loggers"].items():
315 if not name.startswith("BitBake."):
316 continue
317
318 if not "level" in l:
319 continue
320
321 curlevel = bb.msg.loggerDefaultDomains.get(name)
322 # Note: level parameter should already be a int because of conversion
323 # above
324 newlevel = int(l["level"])
325 if curlevel is None or newlevel < curlevel:
326 bb.msg.loggerDefaultDomains[name] = newlevel
327
328 # TODO: I don't think that setting the global log level should be necessary
329 #if newlevel < bb.msg.loggerDefaultLogLevel:
330 # bb.msg.loggerDefaultLogLevel = newlevel
331
332 return conf