Repository: kivy/kivy

Issue:
Kivy Logger doesn't take into account non-string messages
**Software Versions**
* Python: *
* OS: *
* Kivy: `master`
* Kivy installation method: `source`

**Describe the bug**

Kivy Logger doesn't take into account non-string messages, and fails with:
`AttributeError: ... object has no attribute 'replace'`


**Expected behavior**
As specified in [Python Docs](https://docs.python.org/3/howto/logging.html#using-arbitrary-objects-as-messages), also an arbitrary object could be used as a message.

**To Reproduce**
```python
from kivy.logger import Logger
...
...
Logger.info(dict())
...
...
```



**Code and Logs and screenshots**
```python
   File "/Users/mirko/Documents/projects/kivy/kivy/logger.py", line 433, in format
     self._coloring_cls(ColonSplittingLogRecord(record)))
   File "/Users/mirko/Documents/projects/kivy/kivy/logger.py", line 390, in __init__
     self.msg = self._format_message(self.msg)
   File "/Users/mirko/Documents/projects/kivy/kivy/logger.py", line 364, in _format_message
     return message.replace(
 AttributeError: 'dict' object has no attribute 'replace'
```

**Additional context**
Add any other context about the problem here.

CC: @Julian-O 

Discussion:

Patch:
diff --git a/kivy/logger.py b/kivy/logger.py
index 55dcfbc4c..7ed494ca5 100644
--- a/kivy/logger.py
+++ b/kivy/logger.py
@@ -361,7 +361,7 @@ class ColoredLogRecord(logging.LogRecord):
 
     @classmethod
     def _format_message(cls, message):
-        return message.replace(
+        return str(message).replace(
             "$RESET", cls.RESET_SEQ).replace("$BOLD", cls.BOLD_SEQ)
 
     @classmethod
@@ -401,7 +401,7 @@ class UncoloredLogRecord(logging.LogRecord):
 
     @classmethod
     def _format_message(cls, message):
-        return message.replace("$RESET", "").replace("$BOLD", "")
+        return str(message).replace("$RESET", "").replace("$BOLD", "")
 
     def __init__(self, logrecord):
         super().__init__(


Test patch:
diff --git a/kivy/tests/test_logger.py b/kivy/tests/test_logger.py
index 2976af52a..a5951c327 100644
--- a/kivy/tests/test_logger.py
+++ b/kivy/tests/test_logger.py
@@ -177,6 +177,22 @@ def test_colonsplittinglogrecord_without_colon():
     # No colons means no change.
     assert str(originallogrecord) == str(shimmedlogrecord)
 
+    # Try a non-string (Issue #7984)
+    originallogrecord = logging.LogRecord(
+        name="kivy.test",
+        level=logging.DEBUG,
+        pathname="test.py",
+        lineno=1,
+        msg=1,
+        args=None,
+        exc_info=None,
+        func="test_colon_splitting",
+        sinfo=None,
+    )
+    shimmedlogrecord = ColonSplittingLogRecord(originallogrecord)
+    # No colons means no change.
+    assert str(originallogrecord) == str(shimmedlogrecord)
+
 
 def test_uncoloredlogrecord_without_markup():
     from kivy.logger import UncoloredLogRecord
@@ -196,6 +212,21 @@ def test_uncoloredlogrecord_without_markup():
     # No markup means no change.
     assert str(originallogrecord) == str(shimmedlogrecord)
 
+    # Try a non-string (Issue #7984)
+    originallogrecord = logging.LogRecord(
+        name="kivy.test",
+        level=logging.DEBUG,
+        pathname="test.py",
+        lineno=1,
+        msg=1,
+        args=None,
+        exc_info=None,
+        func="test_colon_splitting",
+        sinfo=None,
+    )
+    shimmedlogrecord = UncoloredLogRecord(originallogrecord)
+    assert str(originallogrecord) == str(shimmedlogrecord)
+
 
 def test_uncoloredlogrecord_with_markup():
     from kivy.logger import UncoloredLogRecord
@@ -240,6 +271,21 @@ def test_coloredlogrecord_without_markup():
     assert originallogrecord.levelname != shimmedlogrecord.levelname
     assert shimmedlogrecord.levelname == "\x1b[1;36mDEBUG\x1b[0m"
 
+    # Try a non-string (Issue #7984)
+    originallogrecord = logging.LogRecord(
+        name="kivy.test",
+        level=logging.DEBUG,
+        pathname="test.py",
+        lineno=1,
+        msg=1,
+        args=None,
+        exc_info=None,
+        func="test_colon_splitting",
+        sinfo=None,
+    )
+    shimmedlogrecord = ColoredLogRecord(originallogrecord)
+    assert str(originallogrecord) == str(shimmedlogrecord)
+
 
 def test_coloredlogrecord_with_markup():
     from kivy.logger import ColoredLogRecord