Repository: pytorch/ignite

Issue:
Add optional arg `encoding = None` for `ignite.utils.setup_logger`
## 🚀 Feature

Add optional arg `encoding = None` for `ignite.utils.setup_logger`

## Motivation

I encountered garbled text when loggering messages that contains CJK characters using `logger.info()`. For example, 

```python
from ignite.utils import setup_logger
logger = setup_logger(name="theName", filepath='filename.log')

logger.info('你好') # garbled text in the written .log file
```

In the .log file, garbled text is printed.

```text
�й���׼ʱ��.
```

## Solution

This can be addressed by simply passing `encoding = "utf-8"` to the filehandler

https://github.com/pytorch/ignite/blob/f431e60b09743dc8d99b7e5f32e234f46a2a920d/ignite/utils.py#L268

```python
def setup_logger(
    name: Optional[str] = "ignite",
    level: int = logging.INFO,
    stream: Optional[TextIO] = None,
    format: str = "%(asctime)s %(name)s %(levelname)s: %(message)s",
    filepath: Optional[str] = None,
	encoding: Optional[str] = None, # add this optional arg
    distributed_rank: Optional[int] = None,
    reset: bool = False,
) -> logging.Logger:


        if filepath is not None:
            fh = logging.FileHandler(filepath, encoding = encoding) # pass encoding to the file handler
            fh.setLevel(level)
            fh.setFormatter(formatter)
            logger.addHandler(fh)
```

Looking forward to a discussion about this before I create a PR.


Discussion:
Thanks for the suggestion of improvement, @sjiang95 !
If you would like to add this feature and a test in a draft PR, it would be awesome!

Few notes in case you would like to send a PR:
- to keep BC in terms of function signature, let's set `encoding: Optional[str] = None, # add this optional arg` as the last argument
```python
def setup_logger(
    name: Optional[str] = "ignite",
    level: int = logging.INFO,
    stream: Optional[TextIO] = None,
    format: str = "%(asctime)s %(name)s %(levelname)s: %(message)s",
    filepath: Optional[str] = None,
    distributed_rank: Optional[int] = None,
    reset: bool = False,
    encoding: Optional[str] = None, # add this optional arg
) -> logging.Logger:
```
- maybe, we can set `encoding="utf-8"` by default, such that users should not think about that and get correct logging by default ?
Hello @vfdev-5 ,

thanks for your response.

I also want to set `encoding="utf-8"` by default.

Resolving patch:
diff --git a/ignite/utils.py b/ignite/utils.py
--- a/ignite/utils.py
+++ b/ignite/utils.py
@@ -163,6 +163,7 @@ def setup_logger(
     filepath: Optional[str] = None,
     distributed_rank: Optional[int] = None,
     reset: bool = False,
+    encoding: Optional[str] = "utf-8",
 ) -> logging.Logger:
     """Setups logger: name, level, format etc.
 
@@ -175,6 +176,7 @@ def setup_logger(
         distributed_rank: Optional, rank in distributed configuration to avoid logger setup for workers.
             If None, distributed_rank is initialized to the rank of process.
         reset: if True, reset an existing logger rather than keep format, handlers, and level.
+        encoding: open the file with the encoding. By default, 'utf-8'.
 
     Returns:
         logging.Logger
@@ -228,6 +230,9 @@ def setup_logger(
 
     .. versionchanged:: 0.4.5
         Added ``reset`` parameter.
+
+    .. versionchanged:: 0.5.1
+        Argument ``encoding`` added to correctly handle special characters in the file, default "utf-8".
     """
     # check if the logger already exists
     existing = name is None or name in logging.root.manager.loggerDict
@@ -265,7 +270,7 @@ def setup_logger(
         logger.addHandler(ch)
 
         if filepath is not None:
-            fh = logging.FileHandler(filepath)
+            fh = logging.FileHandler(filepath, encoding=encoding)
             fh.setLevel(level)
             fh.setFormatter(formatter)
             logger.addHandler(fh)


Test patch:
diff --git a/tests/ignite/test_utils.py b/tests/ignite/test_utils.py
--- a/tests/ignite/test_utils.py
+++ b/tests/ignite/test_utils.py
@@ -1,4 +1,5 @@
 import logging
+import platform
 import sys
 from collections import namedtuple
 
@@ -174,6 +175,29 @@ def test_override_setup_logger(capsys):
     logging.shutdown()
 
 
+@pytest.mark.parametrize("encoding", [None, "utf-8"])
+def test_setup_logger_encoding(encoding, dirname):
+    fp = dirname / "log.txt"
+    logger = setup_logger(name="logger", filepath=fp, encoding=encoding, reset=True)
+    test_words = ["say hello", "say 你好", "say こんにちわ", "say 안녕하세요", "say привет"]
+    for w in test_words:
+        logger.info(w)
+    logging.shutdown()
+
+    with open(fp, "r", encoding=encoding) as h:
+        data = h.readlines()
+
+    if platform.system() == "Windows" and encoding is None:
+        flatten_data = "\n".join(data)
+        assert test_words[0] in flatten_data
+        for word in test_words[1:]:
+            assert word not in flatten_data
+    else:
+        assert len(data) == len(test_words)
+        for expected, output in zip(test_words, data):
+            assert expected in output
+
+
 def test_deprecated():
     # Test on function without docs, @deprecated without reasons
     @deprecated("0.4.2", "0.6.0")