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