Repository: optuna/optuna
Pull request: Fix the bug of JournalFileStorage on Windows
<!-- Thank you for creating a pull request! In general, we merge your pull request after it gets two or more approvals. To proceed to the review process by the maintainers, please make sure that the PR meets the following conditions: (1) it passes all CI checks, and (2) it is neither in the draft nor WIP state. If you wish to discuss the PR in the draft state or need any other help, please mention the Optuna development team in the PR. -->

## Motivation
<!-- Describe your motivation why you will submit this PR. This is useful for reviewers to understand the context of PR. -->
To close #4148 

## Description of the changes
<!-- Describe the changes in this PR. -->

The bug is probably caused by JournalStorage using "\n" (LF) as a newline character in the following line. But it should be "\r\n" (CRLF) on Windows platforms. This PR replaces "\n" with `os.linesep`, but I don't have a Windows machine so not yet tested.

Problem statement:
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)
```

Hints from issue discussion:

Commit message: Fix the bug of JournalFileStorage on Windows

Patch to review:
diff --git a/optuna/storages/_journal/file.py b/optuna/storages/_journal/file.py
index 663a3272b26..21dd894dd38 100644
--- a/optuna/storages/_journal/file.py
+++ b/optuna/storages/_journal/file.py
@@ -189,7 +189,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"
+            what_to_write = os.linesep.join([json.dumps(log) for log in logs]) + os.linesep
             with open(self._file_path, "a", encoding="utf-8") as f:
                 f.write(what_to_write)
                 f.flush()


Code review comments:
diff_hunk: @@ -189,7 +189,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"
+            what_to_write = os.linesep.join([json.dumps(log) for log in logs]) + os.linesep
path: optuna/storages/_journal/file.py
text: @user1:
The Python reference does not recommend `os.linesep` when we writing text files.
> Do not use os.linesep as a line terminator when writing files opened in text mode (the default); use a single '\n' instead, on all platforms.

We may stick to `\n` or open the file as binary.

https://docs.python.org/3/library/os.html#os.linesep

@author:
Oh, thanks. Good catch 👍  I will revert this line.