msg.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. """
  2. BitBake 'msg' implementation
  3. Message handling infrastructure for bitbake
  4. """
  5. # Copyright (C) 2006 Richard Purdie
  6. #
  7. # SPDX-License-Identifier: GPL-2.0-only
  8. #
  9. import sys
  10. import copy
  11. import logging
  12. import logging.config
  13. from itertools import groupby
  14. import bb
  15. import bb.event
  16. class BBLogFormatter(logging.Formatter):
  17. """Formatter which ensures that our 'plain' messages (logging.INFO + 1) are used as is"""
  18. DEBUG3 = logging.DEBUG - 2
  19. DEBUG2 = logging.DEBUG - 1
  20. DEBUG = logging.DEBUG
  21. VERBOSE = logging.INFO - 1
  22. NOTE = logging.INFO
  23. PLAIN = logging.INFO + 1
  24. VERBNOTE = logging.INFO + 2
  25. ERROR = logging.ERROR
  26. WARNING = logging.WARNING
  27. CRITICAL = logging.CRITICAL
  28. levelnames = {
  29. DEBUG3 : 'DEBUG',
  30. DEBUG2 : 'DEBUG',
  31. DEBUG : 'DEBUG',
  32. VERBOSE: 'NOTE',
  33. NOTE : 'NOTE',
  34. PLAIN : '',
  35. VERBNOTE: 'NOTE',
  36. WARNING : 'WARNING',
  37. ERROR : 'ERROR',
  38. CRITICAL: 'ERROR',
  39. }
  40. color_enabled = False
  41. BASECOLOR, BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = list(range(29,38))
  42. COLORS = {
  43. DEBUG3 : CYAN,
  44. DEBUG2 : CYAN,
  45. DEBUG : CYAN,
  46. VERBOSE : BASECOLOR,
  47. NOTE : BASECOLOR,
  48. PLAIN : BASECOLOR,
  49. VERBNOTE: BASECOLOR,
  50. WARNING : YELLOW,
  51. ERROR : RED,
  52. CRITICAL: RED,
  53. }
  54. BLD = '\033[1;%dm'
  55. STD = '\033[%dm'
  56. RST = '\033[0m'
  57. def getLevelName(self, levelno):
  58. try:
  59. return self.levelnames[levelno]
  60. except KeyError:
  61. self.levelnames[levelno] = value = 'Level %d' % levelno
  62. return value
  63. def format(self, record):
  64. record.levelname = self.getLevelName(record.levelno)
  65. if record.levelno == self.PLAIN:
  66. msg = record.getMessage()
  67. else:
  68. if self.color_enabled:
  69. record = self.colorize(record)
  70. msg = logging.Formatter.format(self, record)
  71. if hasattr(record, 'bb_exc_formatted'):
  72. msg += '\n' + ''.join(record.bb_exc_formatted)
  73. elif hasattr(record, 'bb_exc_info'):
  74. etype, value, tb = record.bb_exc_info
  75. formatted = bb.exceptions.format_exception(etype, value, tb, limit=5)
  76. msg += '\n' + ''.join(formatted)
  77. return msg
  78. def colorize(self, record):
  79. color = self.COLORS[record.levelno]
  80. if self.color_enabled and color is not None:
  81. record = copy.copy(record)
  82. record.levelname = "".join([self.BLD % color, record.levelname, self.RST])
  83. record.msg = "".join([self.STD % color, record.msg, self.RST])
  84. return record
  85. def enable_color(self):
  86. self.color_enabled = True
  87. def __repr__(self):
  88. return "%s fmt='%s' color=%s" % (self.__class__.__name__, self._fmt, "True" if self.color_enabled else "False")
  89. class BBLogFilter(object):
  90. def __init__(self, handler, level, debug_domains):
  91. self.stdlevel = level
  92. self.debug_domains = debug_domains
  93. loglevel = level
  94. for domain in debug_domains:
  95. if debug_domains[domain] < loglevel:
  96. loglevel = debug_domains[domain]
  97. handler.setLevel(loglevel)
  98. handler.addFilter(self)
  99. def filter(self, record):
  100. if record.levelno >= self.stdlevel:
  101. return True
  102. if record.name in self.debug_domains and record.levelno >= self.debug_domains[record.name]:
  103. return True
  104. return False
  105. class LogFilterGEQLevel(logging.Filter):
  106. def __init__(self, level):
  107. self.strlevel = str(level)
  108. self.level = stringToLevel(level)
  109. def __repr__(self):
  110. return "%s level >= %s (%d)" % (self.__class__.__name__, self.strlevel, self.level)
  111. def filter(self, record):
  112. return (record.levelno >= self.level)
  113. class LogFilterLTLevel(logging.Filter):
  114. def __init__(self, level):
  115. self.strlevel = str(level)
  116. self.level = stringToLevel(level)
  117. def __repr__(self):
  118. return "%s level < %s (%d)" % (self.__class__.__name__, self.strlevel, self.level)
  119. def filter(self, record):
  120. return (record.levelno < self.level)
  121. # Message control functions
  122. #
  123. loggerDefaultLogLevel = BBLogFormatter.NOTE
  124. loggerDefaultVerbose = False
  125. loggerVerboseLogs = False
  126. loggerDefaultDomains = {}
  127. def init_msgconfig(verbose, debug, debug_domains=None):
  128. """
  129. Set default verbosity and debug levels config the logger
  130. """
  131. bb.msg.loggerDefaultVerbose = verbose
  132. if verbose:
  133. bb.msg.loggerVerboseLogs = True
  134. if debug:
  135. bb.msg.loggerDefaultLogLevel = BBLogFormatter.DEBUG - debug + 1
  136. elif verbose:
  137. bb.msg.loggerDefaultLogLevel = BBLogFormatter.VERBOSE
  138. else:
  139. bb.msg.loggerDefaultLogLevel = BBLogFormatter.NOTE
  140. bb.msg.loggerDefaultDomains = {}
  141. if debug_domains:
  142. for (domainarg, iterator) in groupby(debug_domains):
  143. dlevel = len(tuple(iterator))
  144. bb.msg.loggerDefaultDomains["BitBake.%s" % domainarg] = logging.DEBUG - dlevel + 1
  145. def constructLogOptions():
  146. return loggerDefaultLogLevel, loggerDefaultDomains
  147. def addDefaultlogFilter(handler, cls = BBLogFilter, forcelevel=None):
  148. level, debug_domains = constructLogOptions()
  149. if forcelevel is not None:
  150. level = forcelevel
  151. cls(handler, level, debug_domains)
  152. def stringToLevel(level):
  153. try:
  154. return int(level)
  155. except ValueError:
  156. pass
  157. try:
  158. return getattr(logging, level)
  159. except AttributeError:
  160. pass
  161. return getattr(BBLogFormatter, level)
  162. #
  163. # Message handling functions
  164. #
  165. def fatal(msgdomain, msg):
  166. if msgdomain:
  167. logger = logging.getLogger("BitBake.%s" % msgdomain)
  168. else:
  169. logger = logging.getLogger("BitBake")
  170. logger.critical(msg)
  171. sys.exit(1)
  172. def logger_create(name, output=sys.stderr, level=logging.INFO, preserve_handlers=False, color='auto'):
  173. """Standalone logger creation function"""
  174. logger = logging.getLogger(name)
  175. console = logging.StreamHandler(output)
  176. format = bb.msg.BBLogFormatter("%(levelname)s: %(message)s")
  177. if color == 'always' or (color == 'auto' and output.isatty()):
  178. format.enable_color()
  179. console.setFormatter(format)
  180. if preserve_handlers:
  181. logger.addHandler(console)
  182. else:
  183. logger.handlers = [console]
  184. logger.setLevel(level)
  185. return logger
  186. def has_console_handler(logger):
  187. for handler in logger.handlers:
  188. if isinstance(handler, logging.StreamHandler):
  189. if handler.stream in [sys.stderr, sys.stdout]:
  190. return True
  191. return False
  192. def mergeLoggingConfig(logconfig, userconfig):
  193. logconfig = copy.deepcopy(logconfig)
  194. userconfig = copy.deepcopy(userconfig)
  195. # Merge config with the default config
  196. if userconfig.get('version') != logconfig['version']:
  197. raise BaseException("Bad user configuration version. Expected %r, got %r" % (logconfig['version'], userconfig.get('version')))
  198. # Set some defaults to make merging easier
  199. userconfig.setdefault("loggers", {})
  200. # If a handler, formatter, or filter is defined in the user
  201. # config, it will replace an existing one in the default config
  202. for k in ("handlers", "formatters", "filters"):
  203. logconfig.setdefault(k, {}).update(userconfig.get(k, {}))
  204. seen_loggers = set()
  205. for name, l in logconfig["loggers"].items():
  206. # If the merge option is set, merge the handlers and
  207. # filters. Otherwise, if it is False, this logger won't get
  208. # add to the set of seen loggers and will replace the
  209. # existing one
  210. if l.get('bitbake_merge', True):
  211. ulogger = userconfig["loggers"].setdefault(name, {})
  212. ulogger.setdefault("handlers", [])
  213. ulogger.setdefault("filters", [])
  214. # Merge lists
  215. l.setdefault("handlers", []).extend(ulogger["handlers"])
  216. l.setdefault("filters", []).extend(ulogger["filters"])
  217. # Replace other properties if present
  218. if "level" in ulogger:
  219. l["level"] = ulogger["level"]
  220. if "propagate" in ulogger:
  221. l["propagate"] = ulogger["propagate"]
  222. seen_loggers.add(name)
  223. # Add all loggers present in the user config, but not any that
  224. # have already been processed
  225. for name in set(userconfig["loggers"].keys()) - seen_loggers:
  226. logconfig["loggers"][name] = userconfig["loggers"][name]
  227. return logconfig
  228. def setLoggingConfig(defaultconfig, userconfigfile=None):
  229. logconfig = copy.deepcopy(defaultconfig)
  230. if userconfigfile:
  231. with open(os.path.normpath(userconfigfile), 'r') as f:
  232. if userconfigfile.endswith('.yml') or userconfigfile.endswith('.yaml'):
  233. import yaml
  234. userconfig = yaml.load(f)
  235. elif userconfigfile.endswith('.json') or userconfigfile.endswith('.cfg'):
  236. import json
  237. userconfig = json.load(f)
  238. else:
  239. raise BaseException("Unrecognized file format: %s" % userconfigfile)
  240. if userconfig.get('bitbake_merge', True):
  241. logconfig = mergeLoggingConfig(logconfig, userconfig)
  242. else:
  243. # Replace the entire default config
  244. logconfig = userconfig
  245. # Convert all level parameters to integers in case users want to use the
  246. # bitbake defined level names
  247. for h in logconfig["handlers"].values():
  248. if "level" in h:
  249. h["level"] = bb.msg.stringToLevel(h["level"])
  250. for l in logconfig["loggers"].values():
  251. if "level" in l:
  252. l["level"] = bb.msg.stringToLevel(l["level"])
  253. conf = logging.config.dictConfigClass(logconfig)
  254. conf.configure()
  255. # The user may have specified logging domains they want at a higher debug
  256. # level than the standard.
  257. for name, l in logconfig["loggers"].items():
  258. if not name.startswith("BitBake."):
  259. continue
  260. if not "level" in l:
  261. continue
  262. curlevel = bb.msg.loggerDefaultDomains.get(name)
  263. # Note: level parameter should already be a int because of conversion
  264. # above
  265. newlevel = int(l["level"])
  266. if curlevel is None or newlevel < curlevel:
  267. bb.msg.loggerDefaultDomains[name] = newlevel
  268. # TODO: I don't think that setting the global log level should be necessary
  269. #if newlevel < bb.msg.loggerDefaultLogLevel:
  270. # bb.msg.loggerDefaultLogLevel = newlevel
  271. return conf