blob: 6f17b6acc7822e743105bb96f555dbf27acc5eab [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
33 WARNING = logging.WARNING
34 CRITICAL = logging.CRITICAL
35
36 levelnames = {
37 DEBUG3 : 'DEBUG',
38 DEBUG2 : 'DEBUG',
39 DEBUG : 'DEBUG',
40 VERBOSE: 'NOTE',
41 NOTE : 'NOTE',
42 PLAIN : '',
Brad Bishop1a4b7ee2018-12-16 17:11:34 -080043 VERBNOTE: 'NOTE',
Patrick Williamsc124f4f2015-09-15 14:41:29 -050044 WARNING : 'WARNING',
45 ERROR : 'ERROR',
46 CRITICAL: 'ERROR',
47 }
48
49 color_enabled = False
Patrick Williamsc0f7c042017-02-23 20:41:17 -060050 BASECOLOR, BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = list(range(29,38))
Patrick Williamsc124f4f2015-09-15 14:41:29 -050051
52 COLORS = {
53 DEBUG3 : CYAN,
54 DEBUG2 : CYAN,
55 DEBUG : CYAN,
56 VERBOSE : BASECOLOR,
57 NOTE : BASECOLOR,
58 PLAIN : BASECOLOR,
Brad Bishop1a4b7ee2018-12-16 17:11:34 -080059 VERBNOTE: BASECOLOR,
Patrick Williamsc124f4f2015-09-15 14:41:29 -050060 WARNING : YELLOW,
61 ERROR : RED,
62 CRITICAL: RED,
63 }
64
65 BLD = '\033[1;%dm'
66 STD = '\033[%dm'
67 RST = '\033[0m'
68
69 def getLevelName(self, levelno):
70 try:
71 return self.levelnames[levelno]
72 except KeyError:
73 self.levelnames[levelno] = value = 'Level %d' % levelno
74 return value
75
76 def format(self, record):
77 record.levelname = self.getLevelName(record.levelno)
78 if record.levelno == self.PLAIN:
79 msg = record.getMessage()
80 else:
81 if self.color_enabled:
82 record = self.colorize(record)
83 msg = logging.Formatter.format(self, record)
Patrick Williamsc0f7c042017-02-23 20:41:17 -060084 if hasattr(record, 'bb_exc_formatted'):
85 msg += '\n' + ''.join(record.bb_exc_formatted)
86 elif hasattr(record, 'bb_exc_info'):
Patrick Williamsc124f4f2015-09-15 14:41:29 -050087 etype, value, tb = record.bb_exc_info
88 formatted = bb.exceptions.format_exception(etype, value, tb, limit=5)
89 msg += '\n' + ''.join(formatted)
90 return msg
91
92 def colorize(self, record):
93 color = self.COLORS[record.levelno]
94 if self.color_enabled and color is not None:
95 record = copy.copy(record)
96 record.levelname = "".join([self.BLD % color, record.levelname, self.RST])
97 record.msg = "".join([self.STD % color, record.msg, self.RST])
98 return record
99
100 def enable_color(self):
101 self.color_enabled = True
102
Andrew Geissler82c905d2020-04-13 13:39:40 -0500103 def __repr__(self):
104 return "%s fmt='%s' color=%s" % (self.__class__.__name__, self._fmt, "True" if self.color_enabled else "False")
105
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500106class BBLogFilter(object):
107 def __init__(self, handler, level, debug_domains):
108 self.stdlevel = level
109 self.debug_domains = debug_domains
110 loglevel = level
111 for domain in debug_domains:
112 if debug_domains[domain] < loglevel:
113 loglevel = debug_domains[domain]
114 handler.setLevel(loglevel)
115 handler.addFilter(self)
116
117 def filter(self, record):
118 if record.levelno >= self.stdlevel:
119 return True
120 if record.name in self.debug_domains and record.levelno >= self.debug_domains[record.name]:
121 return True
122 return False
123
Andrew Geissler82c905d2020-04-13 13:39:40 -0500124class LogFilterGEQLevel(logging.Filter):
125 def __init__(self, level):
126 self.strlevel = str(level)
127 self.level = stringToLevel(level)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500128
Andrew Geissler82c905d2020-04-13 13:39:40 -0500129 def __repr__(self):
130 return "%s level >= %s (%d)" % (self.__class__.__name__, self.strlevel, self.level)
131
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500132 def filter(self, record):
Andrew Geissler82c905d2020-04-13 13:39:40 -0500133 return (record.levelno >= self.level)
134
135class LogFilterLTLevel(logging.Filter):
136 def __init__(self, level):
137 self.strlevel = str(level)
138 self.level = stringToLevel(level)
139
140 def __repr__(self):
141 return "%s level < %s (%d)" % (self.__class__.__name__, self.strlevel, self.level)
142
143 def filter(self, record):
144 return (record.levelno < self.level)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500145
146# Message control functions
147#
148
Andrew Geissler82c905d2020-04-13 13:39:40 -0500149loggerDefaultLogLevel = BBLogFormatter.NOTE
Andrew Geissler82c905d2020-04-13 13:39:40 -0500150loggerDefaultDomains = {}
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500151
152def init_msgconfig(verbose, debug, debug_domains=None):
153 """
154 Set default verbosity and debug levels config the logger
155 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500156 if debug:
Andrew Geissler82c905d2020-04-13 13:39:40 -0500157 bb.msg.loggerDefaultLogLevel = BBLogFormatter.DEBUG - debug + 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500158 elif verbose:
Andrew Geissler82c905d2020-04-13 13:39:40 -0500159 bb.msg.loggerDefaultLogLevel = BBLogFormatter.VERBOSE
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500160 else:
Andrew Geissler82c905d2020-04-13 13:39:40 -0500161 bb.msg.loggerDefaultLogLevel = BBLogFormatter.NOTE
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500162
Andrew Geissler82c905d2020-04-13 13:39:40 -0500163 bb.msg.loggerDefaultDomains = {}
164 if debug_domains:
165 for (domainarg, iterator) in groupby(debug_domains):
166 dlevel = len(tuple(iterator))
167 bb.msg.loggerDefaultDomains["BitBake.%s" % domainarg] = logging.DEBUG - dlevel + 1
168
169def constructLogOptions():
170 return loggerDefaultLogLevel, loggerDefaultDomains
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500171
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600172def addDefaultlogFilter(handler, cls = BBLogFilter, forcelevel=None):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500173 level, debug_domains = constructLogOptions()
174
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600175 if forcelevel is not None:
176 level = forcelevel
177
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500178 cls(handler, level, debug_domains)
179
Andrew Geissler82c905d2020-04-13 13:39:40 -0500180def stringToLevel(level):
181 try:
182 return int(level)
183 except ValueError:
184 pass
185
186 try:
187 return getattr(logging, level)
188 except AttributeError:
189 pass
190
191 return getattr(BBLogFormatter, level)
192
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500193#
194# Message handling functions
195#
196
197def fatal(msgdomain, msg):
198 if msgdomain:
199 logger = logging.getLogger("BitBake.%s" % msgdomain)
200 else:
201 logger = logging.getLogger("BitBake")
202 logger.critical(msg)
203 sys.exit(1)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500204
205def logger_create(name, output=sys.stderr, level=logging.INFO, preserve_handlers=False, color='auto'):
206 """Standalone logger creation function"""
207 logger = logging.getLogger(name)
208 console = logging.StreamHandler(output)
209 format = bb.msg.BBLogFormatter("%(levelname)s: %(message)s")
210 if color == 'always' or (color == 'auto' and output.isatty()):
211 format.enable_color()
212 console.setFormatter(format)
213 if preserve_handlers:
214 logger.addHandler(console)
215 else:
216 logger.handlers = [console]
217 logger.setLevel(level)
218 return logger
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500219
220def has_console_handler(logger):
221 for handler in logger.handlers:
222 if isinstance(handler, logging.StreamHandler):
223 if handler.stream in [sys.stderr, sys.stdout]:
224 return True
225 return False
Andrew Geissler82c905d2020-04-13 13:39:40 -0500226
227def mergeLoggingConfig(logconfig, userconfig):
228 logconfig = copy.deepcopy(logconfig)
229 userconfig = copy.deepcopy(userconfig)
230
231 # Merge config with the default config
232 if userconfig.get('version') != logconfig['version']:
233 raise BaseException("Bad user configuration version. Expected %r, got %r" % (logconfig['version'], userconfig.get('version')))
234
235 # Set some defaults to make merging easier
236 userconfig.setdefault("loggers", {})
237
238 # If a handler, formatter, or filter is defined in the user
239 # config, it will replace an existing one in the default config
240 for k in ("handlers", "formatters", "filters"):
241 logconfig.setdefault(k, {}).update(userconfig.get(k, {}))
242
243 seen_loggers = set()
244 for name, l in logconfig["loggers"].items():
245 # If the merge option is set, merge the handlers and
246 # filters. Otherwise, if it is False, this logger won't get
247 # add to the set of seen loggers and will replace the
248 # existing one
249 if l.get('bitbake_merge', True):
250 ulogger = userconfig["loggers"].setdefault(name, {})
251 ulogger.setdefault("handlers", [])
252 ulogger.setdefault("filters", [])
253
254 # Merge lists
255 l.setdefault("handlers", []).extend(ulogger["handlers"])
256 l.setdefault("filters", []).extend(ulogger["filters"])
257
258 # Replace other properties if present
259 if "level" in ulogger:
260 l["level"] = ulogger["level"]
261
262 if "propagate" in ulogger:
263 l["propagate"] = ulogger["propagate"]
264
265 seen_loggers.add(name)
266
267 # Add all loggers present in the user config, but not any that
268 # have already been processed
269 for name in set(userconfig["loggers"].keys()) - seen_loggers:
270 logconfig["loggers"][name] = userconfig["loggers"][name]
271
272 return logconfig
273
274def setLoggingConfig(defaultconfig, userconfigfile=None):
275 logconfig = copy.deepcopy(defaultconfig)
276
277 if userconfigfile:
Andrew Geissler475cb722020-07-10 16:00:51 -0500278 with open(os.path.normpath(userconfigfile), 'r') as f:
Andrew Geissler82c905d2020-04-13 13:39:40 -0500279 if userconfigfile.endswith('.yml') or userconfigfile.endswith('.yaml'):
280 import yaml
281 userconfig = yaml.load(f)
282 elif userconfigfile.endswith('.json') or userconfigfile.endswith('.cfg'):
283 import json
284 userconfig = json.load(f)
285 else:
286 raise BaseException("Unrecognized file format: %s" % userconfigfile)
287
288 if userconfig.get('bitbake_merge', True):
289 logconfig = mergeLoggingConfig(logconfig, userconfig)
290 else:
291 # Replace the entire default config
292 logconfig = userconfig
293
294 # Convert all level parameters to integers in case users want to use the
295 # bitbake defined level names
296 for h in logconfig["handlers"].values():
297 if "level" in h:
298 h["level"] = bb.msg.stringToLevel(h["level"])
299
300 for l in logconfig["loggers"].values():
301 if "level" in l:
302 l["level"] = bb.msg.stringToLevel(l["level"])
303
304 conf = logging.config.dictConfigClass(logconfig)
305 conf.configure()
306
307 # The user may have specified logging domains they want at a higher debug
308 # level than the standard.
309 for name, l in logconfig["loggers"].items():
310 if not name.startswith("BitBake."):
311 continue
312
313 if not "level" in l:
314 continue
315
316 curlevel = bb.msg.loggerDefaultDomains.get(name)
317 # Note: level parameter should already be a int because of conversion
318 # above
319 newlevel = int(l["level"])
320 if curlevel is None or newlevel < curlevel:
321 bb.msg.loggerDefaultDomains[name] = newlevel
322
323 # TODO: I don't think that setting the global log level should be necessary
324 #if newlevel < bb.msg.loggerDefaultLogLevel:
325 # bb.msg.loggerDefaultLogLevel = newlevel
326
327 return conf