blob: 93575d89c4b898a49bd38cd524d18d0b62f05c08 [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
Andrew Geisslerc9f78652020-09-18 14:11:35 -050017import os
Patrick Williamsc124f4f2015-09-15 14:41:29 -050018from itertools import groupby
Patrick Williamsc124f4f2015-09-15 14:41:29 -050019import bb
20import bb.event
21
22class BBLogFormatter(logging.Formatter):
23 """Formatter which ensures that our 'plain' messages (logging.INFO + 1) are used as is"""
24
25 DEBUG3 = logging.DEBUG - 2
26 DEBUG2 = logging.DEBUG - 1
27 DEBUG = logging.DEBUG
28 VERBOSE = logging.INFO - 1
29 NOTE = logging.INFO
30 PLAIN = logging.INFO + 1
Brad Bishop1a4b7ee2018-12-16 17:11:34 -080031 VERBNOTE = logging.INFO + 2
Patrick Williamsc124f4f2015-09-15 14:41:29 -050032 ERROR = logging.ERROR
Andrew Geissler7e0e3c02022-02-25 20:34:39 +000033 ERRORONCE = logging.ERROR - 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -050034 WARNING = logging.WARNING
Andrew Geissler7e0e3c02022-02-25 20:34:39 +000035 WARNONCE = logging.WARNING - 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -050036 CRITICAL = logging.CRITICAL
37
38 levelnames = {
39 DEBUG3 : 'DEBUG',
40 DEBUG2 : 'DEBUG',
41 DEBUG : 'DEBUG',
42 VERBOSE: 'NOTE',
43 NOTE : 'NOTE',
44 PLAIN : '',
Brad Bishop1a4b7ee2018-12-16 17:11:34 -080045 VERBNOTE: 'NOTE',
Patrick Williamsc124f4f2015-09-15 14:41:29 -050046 WARNING : 'WARNING',
Andrew Geissler7e0e3c02022-02-25 20:34:39 +000047 WARNONCE : 'WARNING',
Patrick Williamsc124f4f2015-09-15 14:41:29 -050048 ERROR : 'ERROR',
Andrew Geissler7e0e3c02022-02-25 20:34:39 +000049 ERRORONCE : 'ERROR',
Patrick Williamsc124f4f2015-09-15 14:41:29 -050050 CRITICAL: 'ERROR',
51 }
52
53 color_enabled = False
Patrick Williamsc0f7c042017-02-23 20:41:17 -060054 BASECOLOR, BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = list(range(29,38))
Patrick Williamsc124f4f2015-09-15 14:41:29 -050055
56 COLORS = {
57 DEBUG3 : CYAN,
58 DEBUG2 : CYAN,
59 DEBUG : CYAN,
60 VERBOSE : BASECOLOR,
61 NOTE : BASECOLOR,
62 PLAIN : BASECOLOR,
Brad Bishop1a4b7ee2018-12-16 17:11:34 -080063 VERBNOTE: BASECOLOR,
Patrick Williamsc124f4f2015-09-15 14:41:29 -050064 WARNING : YELLOW,
Andrew Geissler7e0e3c02022-02-25 20:34:39 +000065 WARNONCE : YELLOW,
Patrick Williamsc124f4f2015-09-15 14:41:29 -050066 ERROR : RED,
Andrew Geissler7e0e3c02022-02-25 20:34:39 +000067 ERRORONCE : RED,
Patrick Williamsc124f4f2015-09-15 14:41:29 -050068 CRITICAL: RED,
69 }
70
71 BLD = '\033[1;%dm'
72 STD = '\033[%dm'
73 RST = '\033[0m'
74
75 def getLevelName(self, levelno):
76 try:
77 return self.levelnames[levelno]
78 except KeyError:
79 self.levelnames[levelno] = value = 'Level %d' % levelno
80 return value
81
82 def format(self, record):
83 record.levelname = self.getLevelName(record.levelno)
84 if record.levelno == self.PLAIN:
85 msg = record.getMessage()
86 else:
87 if self.color_enabled:
88 record = self.colorize(record)
89 msg = logging.Formatter.format(self, record)
Patrick Williamsc0f7c042017-02-23 20:41:17 -060090 if hasattr(record, 'bb_exc_formatted'):
91 msg += '\n' + ''.join(record.bb_exc_formatted)
92 elif hasattr(record, 'bb_exc_info'):
Patrick Williamsc124f4f2015-09-15 14:41:29 -050093 etype, value, tb = record.bb_exc_info
94 formatted = bb.exceptions.format_exception(etype, value, tb, limit=5)
95 msg += '\n' + ''.join(formatted)
96 return msg
97
98 def colorize(self, record):
99 color = self.COLORS[record.levelno]
100 if self.color_enabled and color is not None:
101 record = copy.copy(record)
102 record.levelname = "".join([self.BLD % color, record.levelname, self.RST])
103 record.msg = "".join([self.STD % color, record.msg, self.RST])
104 return record
105
106 def enable_color(self):
107 self.color_enabled = True
108
Andrew Geissler82c905d2020-04-13 13:39:40 -0500109 def __repr__(self):
110 return "%s fmt='%s' color=%s" % (self.__class__.__name__, self._fmt, "True" if self.color_enabled else "False")
111
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500112class BBLogFilter(object):
113 def __init__(self, handler, level, debug_domains):
114 self.stdlevel = level
115 self.debug_domains = debug_domains
116 loglevel = level
117 for domain in debug_domains:
118 if debug_domains[domain] < loglevel:
119 loglevel = debug_domains[domain]
120 handler.setLevel(loglevel)
121 handler.addFilter(self)
122
123 def filter(self, record):
124 if record.levelno >= self.stdlevel:
125 return True
126 if record.name in self.debug_domains and record.levelno >= self.debug_domains[record.name]:
127 return True
128 return False
129
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000130class LogFilterShowOnce(logging.Filter):
131 def __init__(self):
132 self.seen_warnings = set()
133 self.seen_errors = set()
134
135 def filter(self, record):
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000136 if record.levelno == bb.msg.BBLogFormatter.WARNONCE:
137 if record.msg in self.seen_warnings:
138 return False
139 self.seen_warnings.add(record.msg)
140 if record.levelno == bb.msg.BBLogFormatter.ERRORONCE:
141 if record.msg in self.seen_errors:
142 return False
143 self.seen_errors.add(record.msg)
144 return True
145
Andrew Geissler82c905d2020-04-13 13:39:40 -0500146class LogFilterGEQLevel(logging.Filter):
147 def __init__(self, level):
148 self.strlevel = str(level)
149 self.level = stringToLevel(level)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500150
Andrew Geissler82c905d2020-04-13 13:39:40 -0500151 def __repr__(self):
152 return "%s level >= %s (%d)" % (self.__class__.__name__, self.strlevel, self.level)
153
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500154 def filter(self, record):
Andrew Geissler82c905d2020-04-13 13:39:40 -0500155 return (record.levelno >= self.level)
156
157class LogFilterLTLevel(logging.Filter):
158 def __init__(self, level):
159 self.strlevel = str(level)
160 self.level = stringToLevel(level)
161
162 def __repr__(self):
163 return "%s level < %s (%d)" % (self.__class__.__name__, self.strlevel, self.level)
164
165 def filter(self, record):
166 return (record.levelno < self.level)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500167
168# Message control functions
169#
170
Andrew Geissler82c905d2020-04-13 13:39:40 -0500171loggerDefaultLogLevel = BBLogFormatter.NOTE
Andrew Geissler82c905d2020-04-13 13:39:40 -0500172loggerDefaultDomains = {}
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500173
174def init_msgconfig(verbose, debug, debug_domains=None):
175 """
176 Set default verbosity and debug levels config the logger
177 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500178 if debug:
Andrew Geissler82c905d2020-04-13 13:39:40 -0500179 bb.msg.loggerDefaultLogLevel = BBLogFormatter.DEBUG - debug + 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500180 elif verbose:
Andrew Geissler82c905d2020-04-13 13:39:40 -0500181 bb.msg.loggerDefaultLogLevel = BBLogFormatter.VERBOSE
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500182 else:
Andrew Geissler82c905d2020-04-13 13:39:40 -0500183 bb.msg.loggerDefaultLogLevel = BBLogFormatter.NOTE
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500184
Andrew Geissler82c905d2020-04-13 13:39:40 -0500185 bb.msg.loggerDefaultDomains = {}
186 if debug_domains:
187 for (domainarg, iterator) in groupby(debug_domains):
188 dlevel = len(tuple(iterator))
189 bb.msg.loggerDefaultDomains["BitBake.%s" % domainarg] = logging.DEBUG - dlevel + 1
190
191def constructLogOptions():
192 return loggerDefaultLogLevel, loggerDefaultDomains
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500193
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600194def addDefaultlogFilter(handler, cls = BBLogFilter, forcelevel=None):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500195 level, debug_domains = constructLogOptions()
196
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600197 if forcelevel is not None:
198 level = forcelevel
199
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500200 cls(handler, level, debug_domains)
201
Andrew Geissler82c905d2020-04-13 13:39:40 -0500202def stringToLevel(level):
203 try:
204 return int(level)
205 except ValueError:
206 pass
207
208 try:
209 return getattr(logging, level)
210 except AttributeError:
211 pass
212
213 return getattr(BBLogFormatter, level)
214
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500215#
216# Message handling functions
217#
218
219def fatal(msgdomain, msg):
220 if msgdomain:
221 logger = logging.getLogger("BitBake.%s" % msgdomain)
222 else:
223 logger = logging.getLogger("BitBake")
224 logger.critical(msg)
225 sys.exit(1)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500226
227def logger_create(name, output=sys.stderr, level=logging.INFO, preserve_handlers=False, color='auto'):
228 """Standalone logger creation function"""
229 logger = logging.getLogger(name)
230 console = logging.StreamHandler(output)
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000231 console.addFilter(bb.msg.LogFilterShowOnce())
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500232 format = bb.msg.BBLogFormatter("%(levelname)s: %(message)s")
233 if color == 'always' or (color == 'auto' and output.isatty()):
234 format.enable_color()
235 console.setFormatter(format)
236 if preserve_handlers:
237 logger.addHandler(console)
238 else:
239 logger.handlers = [console]
240 logger.setLevel(level)
241 return logger
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500242
243def has_console_handler(logger):
244 for handler in logger.handlers:
245 if isinstance(handler, logging.StreamHandler):
246 if handler.stream in [sys.stderr, sys.stdout]:
247 return True
248 return False
Andrew Geissler82c905d2020-04-13 13:39:40 -0500249
250def mergeLoggingConfig(logconfig, userconfig):
251 logconfig = copy.deepcopy(logconfig)
252 userconfig = copy.deepcopy(userconfig)
253
254 # Merge config with the default config
255 if userconfig.get('version') != logconfig['version']:
256 raise BaseException("Bad user configuration version. Expected %r, got %r" % (logconfig['version'], userconfig.get('version')))
257
258 # Set some defaults to make merging easier
259 userconfig.setdefault("loggers", {})
260
261 # If a handler, formatter, or filter is defined in the user
262 # config, it will replace an existing one in the default config
263 for k in ("handlers", "formatters", "filters"):
264 logconfig.setdefault(k, {}).update(userconfig.get(k, {}))
265
266 seen_loggers = set()
267 for name, l in logconfig["loggers"].items():
268 # If the merge option is set, merge the handlers and
269 # filters. Otherwise, if it is False, this logger won't get
270 # add to the set of seen loggers and will replace the
271 # existing one
272 if l.get('bitbake_merge', True):
273 ulogger = userconfig["loggers"].setdefault(name, {})
274 ulogger.setdefault("handlers", [])
275 ulogger.setdefault("filters", [])
276
277 # Merge lists
278 l.setdefault("handlers", []).extend(ulogger["handlers"])
279 l.setdefault("filters", []).extend(ulogger["filters"])
280
281 # Replace other properties if present
282 if "level" in ulogger:
283 l["level"] = ulogger["level"]
284
285 if "propagate" in ulogger:
286 l["propagate"] = ulogger["propagate"]
287
288 seen_loggers.add(name)
289
290 # Add all loggers present in the user config, but not any that
291 # have already been processed
292 for name in set(userconfig["loggers"].keys()) - seen_loggers:
293 logconfig["loggers"][name] = userconfig["loggers"][name]
294
295 return logconfig
296
297def setLoggingConfig(defaultconfig, userconfigfile=None):
298 logconfig = copy.deepcopy(defaultconfig)
299
300 if userconfigfile:
Andrew Geissler475cb722020-07-10 16:00:51 -0500301 with open(os.path.normpath(userconfigfile), 'r') as f:
Andrew Geissler82c905d2020-04-13 13:39:40 -0500302 if userconfigfile.endswith('.yml') or userconfigfile.endswith('.yaml'):
303 import yaml
Andrew Geissler09209ee2020-12-13 08:44:15 -0600304 userconfig = yaml.safe_load(f)
Andrew Geissler82c905d2020-04-13 13:39:40 -0500305 elif userconfigfile.endswith('.json') or userconfigfile.endswith('.cfg'):
306 import json
307 userconfig = json.load(f)
308 else:
309 raise BaseException("Unrecognized file format: %s" % userconfigfile)
310
311 if userconfig.get('bitbake_merge', True):
312 logconfig = mergeLoggingConfig(logconfig, userconfig)
313 else:
314 # Replace the entire default config
315 logconfig = userconfig
316
317 # Convert all level parameters to integers in case users want to use the
318 # bitbake defined level names
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000319 for name, h in logconfig["handlers"].items():
Andrew Geissler82c905d2020-04-13 13:39:40 -0500320 if "level" in h:
321 h["level"] = bb.msg.stringToLevel(h["level"])
322
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000323 # Every handler needs its own instance of the once filter.
324 once_filter_name = name + ".showonceFilter"
325 logconfig.setdefault("filters", {})[once_filter_name] = {
326 "()": "bb.msg.LogFilterShowOnce",
327 }
328 h.setdefault("filters", []).append(once_filter_name)
329
Andrew Geissler82c905d2020-04-13 13:39:40 -0500330 for l in logconfig["loggers"].values():
331 if "level" in l:
332 l["level"] = bb.msg.stringToLevel(l["level"])
333
334 conf = logging.config.dictConfigClass(logconfig)
335 conf.configure()
336
337 # The user may have specified logging domains they want at a higher debug
338 # level than the standard.
339 for name, l in logconfig["loggers"].items():
340 if not name.startswith("BitBake."):
341 continue
342
343 if not "level" in l:
344 continue
345
346 curlevel = bb.msg.loggerDefaultDomains.get(name)
347 # Note: level parameter should already be a int because of conversion
348 # above
349 newlevel = int(l["level"])
350 if curlevel is None or newlevel < curlevel:
351 bb.msg.loggerDefaultDomains[name] = newlevel
352
353 # TODO: I don't think that setting the global log level should be necessary
354 #if newlevel < bb.msg.loggerDefaultLogLevel:
355 # bb.msg.loggerDefaultLogLevel = newlevel
356
357 return conf