diff mbox series

[RFC,1/3] utils: Extend LogCatcher to record raw messages and levels

Message ID 20261024-mathieu-debug-replay-v4-v1-1-3f7f52a3b8e6@bootlin.com
State New
Headers show
Series Preparatory work for sstate cache log replay | expand

Commit Message

Mathieu Dubois-Briand Oct. 24, 2026, 12:33 p.m. UTC
LogCatcher stores only formatted messages, which strips the severity. Also
record each log record's raw message and level, so callers can later
replay them with their original severity. Lower the handler minimum level
to WARNONCE so warnonce and erroronce messages are captured too.

Add unit tests covering the captured levels and raw messages, the
formatted output and the level filtering.

AI-Generated: Uses opencode (DeepSeek V4 Flash)
Signed-off-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
---
 lib/bb/tests/utils.py | 42 ++++++++++++++++++++++++++++++++++++++++++
 lib/bb/utils.py       |  4 +++-
 2 files changed, 45 insertions(+), 1 deletion(-)
diff mbox series

Patch

diff --git a/lib/bb/tests/utils.py b/lib/bb/tests/utils.py
index 52b7bf85bf47..a34f6d698225 100644
--- a/lib/bb/tests/utils.py
+++ b/lib/bb/tests/utils.py
@@ -8,9 +8,11 @@ 
 
 import unittest
 import bb
+import bb.build
 import os
 import tempfile
 import re
+import logging
 
 class VerCmpString(unittest.TestCase):
 
@@ -693,6 +695,46 @@  class EnvironmentTests(unittest.TestCase):
         self.assertEqual(os.environ["A"], "this is A")
         self.assertNotIn("B", os.environ)
 
+class LogCatcherTests(unittest.TestCase):
+    def setUp(self):
+        self.logger = logging.getLogger("BitBake")
+        self.oldlevel = self.logger.level
+        self.logger.setLevel(logging.DEBUG)
+        self.catcher = bb.utils.LogCatcher()
+        self.logger.addHandler(self.catcher)
+
+    def tearDown(self):
+        self.logger.removeHandler(self.catcher)
+        self.logger.setLevel(self.oldlevel)
+
+    def test_records_capture_level_and_raw_message(self):
+        bb.warn("a warning")
+        bb.warnonce("a warn once")
+        bb.error("an error")
+        bb.erroronce("an error once")
+
+        self.assertEqual(self.catcher.records, [
+            (logging.WARNING, "a warning"),
+            (bb.msg.BBLogFormatter.WARNONCE, "a warn once"),
+            (logging.ERROR, "an error"),
+            (bb.msg.BBLogFormatter.ERRORONCE, "an error once"),
+        ])
+
+    def test_messages_are_formatted(self):
+        bb.warn("a warning")
+        self.assertEqual(self.catcher.messages, ["WARNING: a warning"])
+        self.assertTrue(self.catcher.contains("WARNING: a warning"))
+
+    def test_below_minimum_level_is_ignored(self):
+        bb.debug(1, "a debug")
+        bb.plain("a plain")
+        bb.note("a note")
+        bb.verbnote("a verb note")
+
+        self.assertEqual(self.catcher.records, [])
+        self.assertEqual(self.catcher.messages, [])
+
+
 class FilemodeTests(unittest.TestCase):
     def test_filemode_convert(self):
         self.assertEqual(0o775, bb.utils.to_filemode("0o775"))
diff --git a/lib/bb/utils.py b/lib/bb/utils.py
index ec6eb2181aa8..4a4e911b0f8a 100644
--- a/lib/bb/utils.py
+++ b/lib/bb/utils.py
@@ -2154,9 +2154,11 @@  class LogCatcher(logging.Handler):
     """Logging handler for collecting logged messages so you can check them later"""
     def __init__(self):
         self.messages = []
-        logging.Handler.__init__(self, logging.WARNING)
+        self.records = []
+        logging.Handler.__init__(self, bb.msg.BBLogFormatter.WARNONCE)
     def emit(self, record):
         self.messages.append(bb.build.logformatter.format(record))
+        self.records.append((record.levelno, record.getMessage()))
     def contains(self, message):
         return (message in self.messages)