Repository: optuna/optuna

Issue:
JournalStorage not working on Windows10
### Expected behavior

I was using the minimal JournalStorage example from the optuna documentation on my Windows10 PC. 
However the script is not able to run and throws every time json decoding errors.

According to the latest documentation I didn´t see any limitations regarding the OS on which JournalStorage can be used. But maybe I was missing something.


### Environment

- Optuna version:3.1.0.dev
- Python version:3.10.6
- OS:Windows-10-10.0.19044-SP0
- (Optional) Other libraries and their versions:


### Error messages, stack traces, or logs

```shell
C:\1_Files\M\optuna_trial.py:9: ExperimentalWarning: JournalStorage is experimental (supported from v3.1.0). The interface can change in the future.
  storage = optuna.storages.JournalStorage(
Traceback (most recent call last):
  File "C:\1_Files\M\optuna_trial.py", line 12, in <module>
    study = optuna.create_study(storage=storage)
  File "C:\Users\Prometheus\miniconda3\envs\py310\lib\site-packages\optuna\_convert_positional_args.py", line 63, in converter_wrapper
    return func(**kwargs)
  File "C:\Users\Prometheus\miniconda3\envs\py310\lib\site-packages\optuna\study\study.py", line 1155, in create_study
    study_id = storage.create_new_study(study_name)
  File "C:\Users\Prometheus\miniconda3\envs\py310\lib\site-packages\optuna\storages\_journal\storage.py", line 111, in create_new_study
    self._sync_with_backend()
  File "C:\Users\Prometheus\miniconda3\envs\py310\lib\site-packages\optuna\storages\_journal\storage.py", line 103, in _sync_with_backend
    logs = self._backend.read_logs(self._replay_result.log_number_read)
  File "C:\Users\Prometheus\miniconda3\envs\py310\lib\site-packages\optuna\storages\_journal\file.py", line 174, in read_logs
    raise last_decode_error
  File "C:\Users\Prometheus\miniconda3\envs\py310\lib\site-packages\optuna\storages\_journal\file.py", line 184, in read_logs
    logs.append(json.loads(line))
  File "C:\Users\Prometheus\miniconda3\envs\py310\lib\json\__init__.py", line 346, in loads
    return _default_decoder.decode(s)
  File "C:\Users\Prometheus\miniconda3\envs\py310\lib\json\decoder.py", line 337, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
  File "C:\Users\Prometheus\miniconda3\envs\py310\lib\json\decoder.py", line 353, in raw_decode
    obj, end = self.scan_once(s, idx)
json.decoder.JSONDecodeError: Invalid control character at: line 1 column 3 (char 2)
```


### Steps to reproduce

1. Run any JournalStorage example on a Windows10 PC
2. Python finishes with error
3.
```python
import optuna


def objective(trial):
    x = trial.suggest_float("x", -10, 10)
    return (x - 2) ** 2


storage = optuna.storages.JournalStorage(
    optuna.storages.JournalFileStorage("./log_file"))

study = optuna.create_study(storage=storage)
study.optimize(objective)
```


### Additional context (optional)

running the script as an admin yields a slightly different error

```
  File "C:\Users\Prometheus\miniconda3\envs\py310\lib\json\decoder.py", line 355, in raw_decode
    raise JSONDecodeError("Expecting value", s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 2 column 1 (char 1)
```


Discussion:

Resolving patch:
diff --git a/optuna/storages/_journal/file.py b/optuna/storages/_journal/file.py
--- a/optuna/storages/_journal/file.py
+++ b/optuna/storages/_journal/file.py
@@ -157,12 +157,12 @@ class JournalFileStorage(BaseJournalLogStorage):
     def __init__(self, file_path: str, lock_obj: Optional[JournalFileBaseLock] = None) -> None:
         self._file_path: str = file_path
         self._lock = lock_obj or JournalFileSymlinkLock(self._file_path)
-        open(self._file_path, "a").close()  # Create a file if it does not exist
+        open(self._file_path, "ab").close()  # Create a file if it does not exist
         self._log_number_offset: Dict[int, int] = {0: 0}
 
     def read_logs(self, log_number_from: int) -> List[Dict[str, Any]]:
         logs = []
-        with open(self._file_path, "r") as f:
+        with open(self._file_path, "rb") as f:
             log_number_start = 0
             if log_number_from in self._log_number_offset:
                 f.seek(self._log_number_offset[log_number_from])
@@ -173,7 +173,7 @@ def read_logs(self, log_number_from: int) -> List[Dict[str, Any]]:
                 if last_decode_error is not None:
                     raise last_decode_error
                 if log_number + 1 not in self._log_number_offset:
-                    byte_len = len(line.encode("utf-8"))
+                    byte_len = len(line)
                     self._log_number_offset[log_number + 1] = (
                         self._log_number_offset[log_number] + byte_len
                     )
@@ -190,7 +190,7 @@ def read_logs(self, log_number_from: int) -> List[Dict[str, Any]]:
     def append_logs(self, logs: List[Dict[str, Any]]) -> None:
         with get_lock_file(self._lock):
             what_to_write = "\n".join([json.dumps(log) for log in logs]) + "\n"
-            with open(self._file_path, "a", encoding="utf-8") as f:
-                f.write(what_to_write)
+            with open(self._file_path, "ab") as f:
+                f.write(what_to_write.encode("utf-8"))
                 f.flush()
                 os.fsync(f.fileno())


Test patch: