Repository: tianocore/edk2-pytool-library

Issue:
Pytool injecting extra list elements during logging
When logging level is higher than warning level, the ANSI handler from pytool will inject extra element to the message. https://github.com/tianocore/edk2-pytool-library/blob/0eac598e871e8f74068ac00089bf39f09d1264eb/edk2toollib/log/ansi_handler.py#L201

This is a problem when the incoming message is a list as is, since the input list will be updated by this statement and the next consumer using the same list will have an updated list with ANSI information inside.

Comments:

Patch:
diff --git a/edk2toollib/log/ansi_handler.py b/edk2toollib/log/ansi_handler.py
index 76f88fc..2076355 100644
--- a/edk2toollib/log/ansi_handler.py
+++ b/edk2toollib/log/ansi_handler.py
@@ -212,7 +212,7 @@ def format(self, record):
             # otherwise color the wholes message
             else:
                 levelname_color = get_ansi_string(ColoredFormatter.COLORS[levelname]) + levelname
-                record.msg += get_ansi_string()
+                record.msg = str(org_message) + get_ansi_string()
             record.levelname = levelname_color
 
         if self.use_azure and levelname in ColoredFormatter.AZURE_COLORS:


Test patch:
diff --git a/tests.unit/test_ansi_handler.py b/tests.unit/test_ansi_handler.py
index 346a38a..20f4f0d 100644
--- a/tests.unit/test_ansi_handler.py
+++ b/tests.unit/test_ansi_handler.py
@@ -5,10 +5,10 @@
 #
 # SPDX-License-Identifier: BSD-2-Clause-Patent
 ##
-import unittest
 import logging
-from edk2toollib.log.ansi_handler import ColoredFormatter
-from edk2toollib.log.ansi_handler import ColoredStreamHandler
+import unittest
+
+from edk2toollib.log.ansi_handler import ColoredFormatter, ColoredStreamHandler
 
 try:
     from StringIO import StringIO
@@ -26,6 +26,15 @@ class AnsiHandlerTest(unittest.TestCase):
     record2 = logging.makeLogRecord({"name": "", "level": logging.INFO, "levelno": logging.INFO,
                                      "levelname": "INFO", "path": "test_path", "lineno": 0,
                                      "msg": "Test message"})
+    record3 = logging.makeLogRecord({"name": "", "level": logging.ERROR, "levelno": logging.ERROR,
+                                     "levelname": "ERROR", "path": "test_path", "lineno": 0,
+                                     "msg": ['Logging', 'A', 'List']})
+    record4 = logging.makeLogRecord({"name": "", "level": logging.ERROR, "levelno": logging.ERROR,
+                                     "levelname": "ERROR", "path": "test_path", "lineno": 0,
+                                     "msg": ('Logging', 'A', 'Tuple')})
+    record5 = logging.makeLogRecord({"name": "", "level": logging.ERROR, "levelno": logging.ERROR,
+                                     "levelname": "ERROR", "path": "test_path", "lineno": 0,
+                                     "msg": "Testing This Works: %s", "args": ("Test",)})
 
     def test_colored_formatter_init(self):
         formatter = ColoredFormatter("%(levelname)s - %(message)s")
@@ -82,3 +91,23 @@ def test_color_handler_not_strip_ansi(self):
             if CSI in line:
                 found_csi = True
         self.assertTrue(found_csi, "We are supposed to to have found an ANSI control character %s" % lines)
+
+    def test_ansi_handler_with_list(self):
+        """Tests that the ANSI handler can handle Iterables in the message."""
+        stream = StringIO()
+        formatter = ColoredFormatter("%(levelname)s - %(message)s")
+        handler = ColoredStreamHandler(stream, strip=False, convert=False)
+        handler.setFormatter(formatter)
+        handler.setLevel(logging.INFO)
+
+        handler.emit(AnsiHandlerTest.record3)
+        handler.emit(AnsiHandlerTest.record4)
+        handler.emit(AnsiHandlerTest.record5)
+        handler.flush()
+
+        stream.seek(0)
+        lines = stream.readlines()
+        CSI = '\033[31m' # Red
+        CSI2 = '\033[39m' # Reset
+        for line in lines:
+            assert CSI in line and CSI2 in line