summaryrefslogtreecommitdiffstats
path: root/bitbake/lib/bb/msg.py
diff options
context:
space:
mode:
Diffstat (limited to 'bitbake/lib/bb/msg.py')
-rw-r--r--bitbake/lib/bb/msg.py223
1 files changed, 182 insertions, 41 deletions
diff --git a/bitbake/lib/bb/msg.py b/bitbake/lib/bb/msg.py
index 6216eb3bc4..3e18596faa 100644
--- a/bitbake/lib/bb/msg.py
+++ b/bitbake/lib/bb/msg.py
@@ -13,9 +13,9 @@ Message handling infrastructure for bitbake
import sys
import copy
import logging
-import collections
+import logging.config
+import os
from itertools import groupby
-import warnings
import bb
import bb.event
@@ -30,7 +30,9 @@ class BBLogFormatter(logging.Formatter):
PLAIN = logging.INFO + 1
VERBNOTE = logging.INFO + 2
ERROR = logging.ERROR
+ ERRORONCE = logging.ERROR - 1
WARNING = logging.WARNING
+ WARNONCE = logging.WARNING - 1
CRITICAL = logging.CRITICAL
levelnames = {
@@ -42,7 +44,9 @@ class BBLogFormatter(logging.Formatter):
PLAIN : '',
VERBNOTE: 'NOTE',
WARNING : 'WARNING',
+ WARNONCE : 'WARNING',
ERROR : 'ERROR',
+ ERRORONCE : 'ERROR',
CRITICAL: 'ERROR',
}
@@ -58,7 +62,9 @@ class BBLogFormatter(logging.Formatter):
PLAIN : BASECOLOR,
VERBNOTE: BASECOLOR,
WARNING : YELLOW,
+ WARNONCE : YELLOW,
ERROR : RED,
+ ERRORONCE : RED,
CRITICAL: RED,
}
@@ -100,6 +106,9 @@ class BBLogFormatter(logging.Formatter):
def enable_color(self):
self.color_enabled = True
+ def __repr__(self):
+ return "%s fmt='%s' color=%s" % (self.__class__.__name__, self._fmt, "True" if self.color_enabled else "False")
+
class BBLogFilter(object):
def __init__(self, handler, level, debug_domains):
self.stdlevel = level
@@ -118,60 +127,69 @@ class BBLogFilter(object):
return True
return False
-class BBLogFilterStdErr(BBLogFilter):
+class LogFilterShowOnce(logging.Filter):
+ def __init__(self):
+ self.seen_warnings = set()
+ self.seen_errors = set()
+
def filter(self, record):
- if not BBLogFilter.filter(self, record):
- return False
- if record.levelno >= logging.ERROR:
- return True
- return False
+ if record.levelno == bb.msg.BBLogFormatter.WARNONCE:
+ if record.msg in self.seen_warnings:
+ return False
+ self.seen_warnings.add(record.msg)
+ if record.levelno == bb.msg.BBLogFormatter.ERRORONCE:
+ if record.msg in self.seen_errors:
+ return False
+ self.seen_errors.add(record.msg)
+ return True
+
+class LogFilterGEQLevel(logging.Filter):
+ def __init__(self, level):
+ self.strlevel = str(level)
+ self.level = stringToLevel(level)
+
+ def __repr__(self):
+ return "%s level >= %s (%d)" % (self.__class__.__name__, self.strlevel, self.level)
-class BBLogFilterStdOut(BBLogFilter):
def filter(self, record):
- if not BBLogFilter.filter(self, record):
- return False
- if record.levelno < logging.ERROR:
- return True
- return False
+ return (record.levelno >= self.level)
+
+class LogFilterLTLevel(logging.Filter):
+ def __init__(self, level):
+ self.strlevel = str(level)
+ self.level = stringToLevel(level)
+
+ def __repr__(self):
+ return "%s level < %s (%d)" % (self.__class__.__name__, self.strlevel, self.level)
+
+ def filter(self, record):
+ return (record.levelno < self.level)
# Message control functions
#
-loggerDefaultDebugLevel = 0
-loggerDefaultVerbose = False
-loggerVerboseLogs = False
-loggerDefaultDomains = []
+loggerDefaultLogLevel = BBLogFormatter.NOTE
+loggerDefaultDomains = {}
def init_msgconfig(verbose, debug, debug_domains=None):
"""
Set default verbosity and debug levels config the logger
"""
- bb.msg.loggerDefaultDebugLevel = debug
- bb.msg.loggerDefaultVerbose = verbose
- if verbose:
- bb.msg.loggerVerboseLogs = True
- if debug_domains:
- bb.msg.loggerDefaultDomains = debug_domains
- else:
- bb.msg.loggerDefaultDomains = []
-
-def constructLogOptions():
- debug = loggerDefaultDebugLevel
- verbose = loggerDefaultVerbose
- domains = loggerDefaultDomains
-
if debug:
- level = BBLogFormatter.DEBUG - debug + 1
+ bb.msg.loggerDefaultLogLevel = BBLogFormatter.DEBUG - debug + 1
elif verbose:
- level = BBLogFormatter.VERBOSE
+ bb.msg.loggerDefaultLogLevel = BBLogFormatter.VERBOSE
else:
- level = BBLogFormatter.NOTE
+ bb.msg.loggerDefaultLogLevel = BBLogFormatter.NOTE
- debug_domains = {}
- for (domainarg, iterator) in groupby(domains):
- dlevel = len(tuple(iterator))
- debug_domains["BitBake.%s" % domainarg] = logging.DEBUG - dlevel + 1
- return level, debug_domains
+ bb.msg.loggerDefaultDomains = {}
+ if debug_domains:
+ for (domainarg, iterator) in groupby(debug_domains):
+ dlevel = len(tuple(iterator))
+ bb.msg.loggerDefaultDomains["BitBake.%s" % domainarg] = logging.DEBUG - dlevel + 1
+
+def constructLogOptions():
+ return loggerDefaultLogLevel, loggerDefaultDomains
def addDefaultlogFilter(handler, cls = BBLogFilter, forcelevel=None):
level, debug_domains = constructLogOptions()
@@ -181,6 +199,19 @@ def addDefaultlogFilter(handler, cls = BBLogFilter, forcelevel=None):
cls(handler, level, debug_domains)
+def stringToLevel(level):
+ try:
+ return int(level)
+ except ValueError:
+ pass
+
+ try:
+ return getattr(logging, level)
+ except AttributeError:
+ pass
+
+ return getattr(BBLogFormatter, level)
+
#
# Message handling functions
#
@@ -197,8 +228,9 @@ def logger_create(name, output=sys.stderr, level=logging.INFO, preserve_handlers
"""Standalone logger creation function"""
logger = logging.getLogger(name)
console = logging.StreamHandler(output)
+ console.addFilter(bb.msg.LogFilterShowOnce())
format = bb.msg.BBLogFormatter("%(levelname)s: %(message)s")
- if color == 'always' or (color == 'auto' and output.isatty()):
+ if color == 'always' or (color == 'auto' and output.isatty() and os.environ.get('NO_COLOR', '') == ''):
format.enable_color()
console.setFormatter(format)
if preserve_handlers:
@@ -214,3 +246,112 @@ def has_console_handler(logger):
if handler.stream in [sys.stderr, sys.stdout]:
return True
return False
+
+def mergeLoggingConfig(logconfig, userconfig):
+ logconfig = copy.deepcopy(logconfig)
+ userconfig = copy.deepcopy(userconfig)
+
+ # Merge config with the default config
+ if userconfig.get('version') != logconfig['version']:
+ raise BaseException("Bad user configuration version. Expected %r, got %r" % (logconfig['version'], userconfig.get('version')))
+
+ # Set some defaults to make merging easier
+ userconfig.setdefault("loggers", {})
+
+ # If a handler, formatter, or filter is defined in the user
+ # config, it will replace an existing one in the default config
+ for k in ("handlers", "formatters", "filters"):
+ logconfig.setdefault(k, {}).update(userconfig.get(k, {}))
+
+ seen_loggers = set()
+ for name, l in logconfig["loggers"].items():
+ # If the merge option is set, merge the handlers and
+ # filters. Otherwise, if it is False, this logger won't get
+ # add to the set of seen loggers and will replace the
+ # existing one
+ if l.get('bitbake_merge', True):
+ ulogger = userconfig["loggers"].setdefault(name, {})
+ ulogger.setdefault("handlers", [])
+ ulogger.setdefault("filters", [])
+
+ # Merge lists
+ l.setdefault("handlers", []).extend(ulogger["handlers"])
+ l.setdefault("filters", []).extend(ulogger["filters"])
+
+ # Replace other properties if present
+ if "level" in ulogger:
+ l["level"] = ulogger["level"]
+
+ if "propagate" in ulogger:
+ l["propagate"] = ulogger["propagate"]
+
+ seen_loggers.add(name)
+
+ # Add all loggers present in the user config, but not any that
+ # have already been processed
+ for name in set(userconfig["loggers"].keys()) - seen_loggers:
+ logconfig["loggers"][name] = userconfig["loggers"][name]
+
+ return logconfig
+
+def setLoggingConfig(defaultconfig, userconfigfile=None):
+ logconfig = copy.deepcopy(defaultconfig)
+
+ if userconfigfile:
+ with open(os.path.normpath(userconfigfile), 'r') as f:
+ if userconfigfile.endswith('.yml') or userconfigfile.endswith('.yaml'):
+ import yaml
+ userconfig = yaml.safe_load(f)
+ elif userconfigfile.endswith('.json') or userconfigfile.endswith('.cfg'):
+ import json
+ userconfig = json.load(f)
+ else:
+ raise BaseException("Unrecognized file format: %s" % userconfigfile)
+
+ if userconfig.get('bitbake_merge', True):
+ logconfig = mergeLoggingConfig(logconfig, userconfig)
+ else:
+ # Replace the entire default config
+ logconfig = userconfig
+
+ # Convert all level parameters to integers in case users want to use the
+ # bitbake defined level names
+ for name, h in logconfig["handlers"].items():
+ if "level" in h:
+ h["level"] = bb.msg.stringToLevel(h["level"])
+
+ # Every handler needs its own instance of the once filter.
+ once_filter_name = name + ".showonceFilter"
+ logconfig.setdefault("filters", {})[once_filter_name] = {
+ "()": "bb.msg.LogFilterShowOnce",
+ }
+ h.setdefault("filters", []).append(once_filter_name)
+
+ for l in logconfig["loggers"].values():
+ if "level" in l:
+ l["level"] = bb.msg.stringToLevel(l["level"])
+
+ conf = logging.config.dictConfigClass(logconfig)
+ conf.configure()
+
+ # The user may have specified logging domains they want at a higher debug
+ # level than the standard.
+ for name, l in logconfig["loggers"].items():
+ if not name.startswith("BitBake."):
+ continue
+
+ if not "level" in l:
+ continue
+
+ curlevel = bb.msg.loggerDefaultDomains.get(name)
+ # Note: level parameter should already be a int because of conversion
+ # above
+ newlevel = int(l["level"])
+ if curlevel is None or newlevel < curlevel:
+ bb.msg.loggerDefaultDomains[name] = newlevel
+
+ # TODO: I don't think that setting the global log level should be necessary
+ #if newlevel < bb.msg.loggerDefaultLogLevel:
+ # bb.msg.loggerDefaultLogLevel = newlevel
+
+ return conf