We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Add test for `LogWriterSaveFunc`
Add tests check if the output of `LogWriterSaveFunc` follows the specified format.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `pytorch_pfn_extras/training/extensions/log_report.py`
Content:
```
1 import json
2 
3 from pytorch_pfn_extras import reporting
4 from pytorch_pfn_extras.training import extension
5 from pytorch_pfn_extras.training import trigger as trigger_module
6 
7 try:
8     import pandas
9 
10     _pandas_available = True
11 except ImportError:
12     _pandas_available = False
13 
14 
15 class LogWriterSaveFunc:
16 
17     def __init__(self, format, append):
18         self._format = format
19         self._append = append
20 
21     def __call__(self, target, file_o):
22         if self._format == 'json':
23             if self._append:
24                 raise ValueError(
25                     'LogReport does not support json format with append mode.')
26             log = json.dumps(target, indent=4)
27         elif self._format == 'json-lines':
28             if self._append:
29                 target = target[-1]
30             log = '\n'.join([json.dumps(x) for x in target])
31         elif self._format == 'yaml':
32             if self._append:
33                 target = [target[-1]]
34             import yaml
35             log = yaml.dump(target)
36         else:
37             raise ValueError('Unknown format: {}'.format(self._format))
38         file_o.write(bytes(log.encode('ascii')))
39 
40 
41 class LogReport(extension.Extension):
42 
43     """__init__(\
44 keys=None, trigger=(1, 'epoch'), postprocess=None, filename='log', writer=None)
45 
46     An extension to output the accumulated results to a log file.
47 
48     This extension accumulates the observations of the manager to
49     :class:`~pytorch_pfn_extras.DictSummary` at a regular interval specified
50     by a supplied trigger, and writes them into a log file in JSON format.
51 
52     There are two triggers to handle this extension. One is the trigger to
53     invoke this extension, which is used to handle the timing of accumulating
54     the results. It is set to ``1, 'iteration'`` by default. The other is the
55     trigger to determine when to emit the result. When this trigger returns
56     True, this extension appends the summary of accumulated values to the list
57     of past summaries, and writes the list to the log file. Then, this
58     extension makes a new fresh summary object which is used until the next
59     time that the trigger fires.
60 
61     It also adds some entries to each result dictionary.
62 
63     - ``'epoch'`` and ``'iteration'`` are the epoch and iteration counts at the
64       output, respectively.
65     - ``'elapsed_time'`` is the elapsed time in seconds since the training
66       begins. The value is taken from :attr:`ExtensionsManager.elapsed_time`.
67 
68     Args:
69         keys (iterable of strs): Keys of values to accumulate. If this is None,
70             all the values are accumulated and output to the log file.
71         trigger: Trigger that decides when to aggregate the result and output
72             the values. This is distinct from the trigger of this extension
73             itself. If it is a tuple in the form ``<int>, 'epoch'`` or
74             ``<int>, 'iteration'``, it is passed to :class:`IntervalTrigger`.
75         postprocess: Callback to postprocess the result dictionaries. Each
76             result dictionary is passed to this callback on the output. This
77             callback can modify the result dictionaries, which are used to
78             output to the log file.
79         filename (str): Name of the log file under the output directory. It can
80             be a format string: the last result dictionary is passed for the
81             formatting. For example, users can use '{iteration}' to separate
82             the log files for different iterations. If the log name is None, it
83             does not output the log to any file.
84             For historical reasons ``log_name`` is also accepted as an alias
85             of this argument.
86         writer (writer object, optional): must be callable.
87             object to dump the log to. If specified, it needs to have a correct
88             `savefun` defined. The writer can override the save location in
89             the :class:`pytorch_pfn_extras.training.ExtensionsManager` object
90 
91     """
92 
93     def __init__(self, keys=None, trigger=(1, 'epoch'), postprocess=None,
94                  filename=None, append=False, format=None, **kwargs):
95         self._keys = keys
96         self._trigger = trigger_module.get_trigger(trigger)
97         self._postprocess = postprocess
98         self._log = []
99         # When using a writer, it needs to have a savefun defined
100         # to deal with a string.
101         self._writer = kwargs.get('writer', None)
102 
103         log_name = kwargs.get('log_name', 'log')
104         if filename is None:
105             filename = log_name
106         del log_name  # avoid accidental use
107         self._log_name = filename
108 
109         if format is None and filename is not None:
110             if filename.endswith('.jsonl'):
111                 format = 'json-lines'
112             elif filename.endswith('.yaml'):
113                 format = 'yaml'
114             else:
115                 format = 'json'
116 
117         self._append = append
118         self._format = format
119         self._init_summary()
120 
121     def __call__(self, manager):
122         # accumulate the observations
123         keys = self._keys
124         observation = manager.observation
125         summary = self._summary
126 
127         if keys is None:
128             summary.add(observation)
129         else:
130             summary.add({k: observation[k] for k in keys if k in observation})
131 
132         writer = manager.writer if self._writer is None else self._writer
133 
134         if manager.is_before_training or self._trigger(manager):
135             # output the result
136             stats = self._summary.compute_mean()
137             stats_cpu = {}
138             for name, value in stats.items():
139                 stats_cpu[name] = float(value)  # copy to CPU
140 
141             stats_cpu['epoch'] = manager.epoch
142             stats_cpu['iteration'] = manager.iteration
143             stats_cpu['elapsed_time'] = manager.elapsed_time
144 
145             if self._postprocess is not None:
146                 self._postprocess(stats_cpu)
147 
148             self._log.append(stats_cpu)
149 
150             # write to the log file
151             if self._log_name is not None:
152                 log_name = self._log_name.format(**stats_cpu)
153                 out = manager.out
154                 savefun = LogWriterSaveFunc(self._format, self._append)
155                 writer(log_name, out, self._log,
156                        savefun=savefun, append=self._append)
157 
158             # reset the summary for the next output
159             self._init_summary()
160 
161     @property
162     def log(self):
163         """The current list of observation dictionaries."""
164         return self._log
165 
166     def state_dict(self):
167         state = {}
168         if hasattr(self._trigger, 'state_dict'):
169             state['_trigger'] = self._trigger.state_dict()
170 
171         try:
172             state['_summary'] = self._summary.state_dict()
173         except KeyError:
174             pass
175         state['_log'] = json.dumps(self._log)
176         return state
177 
178     def load_state_dict(self, to_load):
179         if hasattr(self._trigger, 'load_state_dict'):
180             self._trigger.load_state_dict(to_load['_trigger'])
181         self._summary.load_state_dict(to_load['_summary'])
182         self._log = json.loads(to_load['_log'])
183 
184     def _init_summary(self):
185         self._summary = reporting.DictSummary()
186 
187     def to_dataframe(self):
188         if not _pandas_available:
189             raise ImportError(
190                 "Need to install pandas to use `to_dataframe` method."
191             )
192         return pandas.DataFrame(self._log)
193
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/pytorch_pfn_extras/training/extensions/log_report.py b/pytorch_pfn_extras/training/extensions/log_report.py
--- a/pytorch_pfn_extras/training/extensions/log_report.py
+++ b/pytorch_pfn_extras/training/extensions/log_report.py
@@ -26,8 +26,9 @@
             log = json.dumps(target, indent=4)
         elif self._format == 'json-lines':
             if self._append:
-                target = target[-1]
-            log = '\n'.join([json.dumps(x) for x in target])
+                target = [target[-1]]
+            # Add a new line at the end for subsequent appends
+            log = '\n'.join([json.dumps(x) for x in target]) + '\n'
         elif self._format == 'yaml':
             if self._append:
                 target = [target[-1]]

{"golden_diff": "diff --git a/pytorch_pfn_extras/training/extensions/log_report.py b/pytorch_pfn_extras/training/extensions/log_report.py\n--- a/pytorch_pfn_extras/training/extensions/log_report.py\n+++ b/pytorch_pfn_extras/training/extensions/log_report.py\n@@ -26,8 +26,9 @@\n             log = json.dumps(target, indent=4)\n         elif self._format == 'json-lines':\n             if self._append:\n-                target = target[-1]\n-            log = '\\n'.join([json.dumps(x) for x in target])\n+                target = [target[-1]]\n+            # Add a new line at the end for subsequent appends\n+            log = '\\n'.join([json.dumps(x) for x in target]) + '\\n'\n         elif self._format == 'yaml':\n             if self._append:\n                 target = [target[-1]]\n", "issue": "Add test for `LogWriterSaveFunc`\nAdd tests check if the output of `LogWriterSaveFunc` follows the specified format.\n", "before_files": [{"content": "import json\n\nfrom pytorch_pfn_extras import reporting\nfrom pytorch_pfn_extras.training import extension\nfrom pytorch_pfn_extras.training import trigger as trigger_module\n\ntry:\n    import pandas\n\n    _pandas_available = True\nexcept ImportError:\n    _pandas_available = False\n\n\nclass LogWriterSaveFunc:\n\n    def __init__(self, format, append):\n        self._format = format\n        self._append = append\n\n    def __call__(self, target, file_o):\n        if self._format == 'json':\n            if self._append:\n                raise ValueError(\n                    'LogReport does not support json format with append mode.')\n            log = json.dumps(target, indent=4)\n        elif self._format == 'json-lines':\n            if self._append:\n                target = target[-1]\n            log = '\\n'.join([json.dumps(x) for x in target])\n        elif self._format == 'yaml':\n            if self._append:\n                target = [target[-1]]\n            import yaml\n            log = yaml.dump(target)\n        else:\n            raise ValueError('Unknown format: {}'.format(self._format))\n        file_o.write(bytes(log.encode('ascii')))\n\n\nclass LogReport(extension.Extension):\n\n    \"\"\"__init__(\\\nkeys=None, trigger=(1, 'epoch'), postprocess=None, filename='log', writer=None)\n\n    An extension to output the accumulated results to a log file.\n\n    This extension accumulates the observations of the manager to\n    :class:`~pytorch_pfn_extras.DictSummary` at a regular interval specified\n    by a supplied trigger, and writes them into a log file in JSON format.\n\n    There are two triggers to handle this extension. One is the trigger to\n    invoke this extension, which is used to handle the timing of accumulating\n    the results. It is set to ``1, 'iteration'`` by default. The other is the\n    trigger to determine when to emit the result. When this trigger returns\n    True, this extension appends the summary of accumulated values to the list\n    of past summaries, and writes the list to the log file. Then, this\n    extension makes a new fresh summary object which is used until the next\n    time that the trigger fires.\n\n    It also adds some entries to each result dictionary.\n\n    - ``'epoch'`` and ``'iteration'`` are the epoch and iteration counts at the\n      output, respectively.\n    - ``'elapsed_time'`` is the elapsed time in seconds since the training\n      begins. The value is taken from :attr:`ExtensionsManager.elapsed_time`.\n\n    Args:\n        keys (iterable of strs): Keys of values to accumulate. If this is None,\n            all the values are accumulated and output to the log file.\n        trigger: Trigger that decides when to aggregate the result and output\n            the values. This is distinct from the trigger of this extension\n            itself. If it is a tuple in the form ``<int>, 'epoch'`` or\n            ``<int>, 'iteration'``, it is passed to :class:`IntervalTrigger`.\n        postprocess: Callback to postprocess the result dictionaries. Each\n            result dictionary is passed to this callback on the output. This\n            callback can modify the result dictionaries, which are used to\n            output to the log file.\n        filename (str): Name of the log file under the output directory. It can\n            be a format string: the last result dictionary is passed for the\n            formatting. For example, users can use '{iteration}' to separate\n            the log files for different iterations. If the log name is None, it\n            does not output the log to any file.\n            For historical reasons ``log_name`` is also accepted as an alias\n            of this argument.\n        writer (writer object, optional): must be callable.\n            object to dump the log to. If specified, it needs to have a correct\n            `savefun` defined. The writer can override the save location in\n            the :class:`pytorch_pfn_extras.training.ExtensionsManager` object\n\n    \"\"\"\n\n    def __init__(self, keys=None, trigger=(1, 'epoch'), postprocess=None,\n                 filename=None, append=False, format=None, **kwargs):\n        self._keys = keys\n        self._trigger = trigger_module.get_trigger(trigger)\n        self._postprocess = postprocess\n        self._log = []\n        # When using a writer, it needs to have a savefun defined\n        # to deal with a string.\n        self._writer = kwargs.get('writer', None)\n\n        log_name = kwargs.get('log_name', 'log')\n        if filename is None:\n            filename = log_name\n        del log_name  # avoid accidental use\n        self._log_name = filename\n\n        if format is None and filename is not None:\n            if filename.endswith('.jsonl'):\n                format = 'json-lines'\n            elif filename.endswith('.yaml'):\n                format = 'yaml'\n            else:\n                format = 'json'\n\n        self._append = append\n        self._format = format\n        self._init_summary()\n\n    def __call__(self, manager):\n        # accumulate the observations\n        keys = self._keys\n        observation = manager.observation\n        summary = self._summary\n\n        if keys is None:\n            summary.add(observation)\n        else:\n            summary.add({k: observation[k] for k in keys if k in observation})\n\n        writer = manager.writer if self._writer is None else self._writer\n\n        if manager.is_before_training or self._trigger(manager):\n            # output the result\n            stats = self._summary.compute_mean()\n            stats_cpu = {}\n            for name, value in stats.items():\n                stats_cpu[name] = float(value)  # copy to CPU\n\n            stats_cpu['epoch'] = manager.epoch\n            stats_cpu['iteration'] = manager.iteration\n            stats_cpu['elapsed_time'] = manager.elapsed_time\n\n            if self._postprocess is not None:\n                self._postprocess(stats_cpu)\n\n            self._log.append(stats_cpu)\n\n            # write to the log file\n            if self._log_name is not None:\n                log_name = self._log_name.format(**stats_cpu)\n                out = manager.out\n                savefun = LogWriterSaveFunc(self._format, self._append)\n                writer(log_name, out, self._log,\n                       savefun=savefun, append=self._append)\n\n            # reset the summary for the next output\n            self._init_summary()\n\n    @property\n    def log(self):\n        \"\"\"The current list of observation dictionaries.\"\"\"\n        return self._log\n\n    def state_dict(self):\n        state = {}\n        if hasattr(self._trigger, 'state_dict'):\n            state['_trigger'] = self._trigger.state_dict()\n\n        try:\n            state['_summary'] = self._summary.state_dict()\n        except KeyError:\n            pass\n        state['_log'] = json.dumps(self._log)\n        return state\n\n    def load_state_dict(self, to_load):\n        if hasattr(self._trigger, 'load_state_dict'):\n            self._trigger.load_state_dict(to_load['_trigger'])\n        self._summary.load_state_dict(to_load['_summary'])\n        self._log = json.loads(to_load['_log'])\n\n    def _init_summary(self):\n        self._summary = reporting.DictSummary()\n\n    def to_dataframe(self):\n        if not _pandas_available:\n            raise ImportError(\n                \"Need to install pandas to use `to_dataframe` method.\"\n            )\n        return pandas.DataFrame(self._log)\n", "path": "pytorch_pfn_extras/training/extensions/log_report.py"}], "after_files": [{"content": "import json\n\nfrom pytorch_pfn_extras import reporting\nfrom pytorch_pfn_extras.training import extension\nfrom pytorch_pfn_extras.training import trigger as trigger_module\n\ntry:\n    import pandas\n\n    _pandas_available = True\nexcept ImportError:\n    _pandas_available = False\n\n\nclass LogWriterSaveFunc:\n\n    def __init__(self, format, append):\n        self._format = format\n        self._append = append\n\n    def __call__(self, target, file_o):\n        if self._format == 'json':\n            if self._append:\n                raise ValueError(\n                    'LogReport does not support json format with append mode.')\n            log = json.dumps(target, indent=4)\n        elif self._format == 'json-lines':\n            if self._append:\n                target = [target[-1]]\n            # Add a new line at the end for subsequent appends\n            log = '\\n'.join([json.dumps(x) for x in target]) + '\\n'\n        elif self._format == 'yaml':\n            if self._append:\n                target = [target[-1]]\n            import yaml\n            log = yaml.dump(target)\n        else:\n            raise ValueError('Unknown format: {}'.format(self._format))\n        file_o.write(bytes(log.encode('ascii')))\n\n\nclass LogReport(extension.Extension):\n\n    \"\"\"__init__(\\\nkeys=None, trigger=(1, 'epoch'), postprocess=None, filename='log', writer=None)\n\n    An extension to output the accumulated results to a log file.\n\n    This extension accumulates the observations of the manager to\n    :class:`~pytorch_pfn_extras.DictSummary` at a regular interval specified\n    by a supplied trigger, and writes them into a log file in JSON format.\n\n    There are two triggers to handle this extension. One is the trigger to\n    invoke this extension, which is used to handle the timing of accumulating\n    the results. It is set to ``1, 'iteration'`` by default. The other is the\n    trigger to determine when to emit the result. When this trigger returns\n    True, this extension appends the summary of accumulated values to the list\n    of past summaries, and writes the list to the log file. Then, this\n    extension makes a new fresh summary object which is used until the next\n    time that the trigger fires.\n\n    It also adds some entries to each result dictionary.\n\n    - ``'epoch'`` and ``'iteration'`` are the epoch and iteration counts at the\n      output, respectively.\n    - ``'elapsed_time'`` is the elapsed time in seconds since the training\n      begins. The value is taken from :attr:`ExtensionsManager.elapsed_time`.\n\n    Args:\n        keys (iterable of strs): Keys of values to accumulate. If this is None,\n            all the values are accumulated and output to the log file.\n        trigger: Trigger that decides when to aggregate the result and output\n            the values. This is distinct from the trigger of this extension\n            itself. If it is a tuple in the form ``<int>, 'epoch'`` or\n            ``<int>, 'iteration'``, it is passed to :class:`IntervalTrigger`.\n        postprocess: Callback to postprocess the result dictionaries. Each\n            result dictionary is passed to this callback on the output. This\n            callback can modify the result dictionaries, which are used to\n            output to the log file.\n        filename (str): Name of the log file under the output directory. It can\n            be a format string: the last result dictionary is passed for the\n            formatting. For example, users can use '{iteration}' to separate\n            the log files for different iterations. If the log name is None, it\n            does not output the log to any file.\n            For historical reasons ``log_name`` is also accepted as an alias\n            of this argument.\n        writer (writer object, optional): must be callable.\n            object to dump the log to. If specified, it needs to have a correct\n            `savefun` defined. The writer can override the save location in\n            the :class:`pytorch_pfn_extras.training.ExtensionsManager` object\n\n    \"\"\"\n\n    def __init__(self, keys=None, trigger=(1, 'epoch'), postprocess=None,\n                 filename=None, append=False, format=None, **kwargs):\n        self._keys = keys\n        self._trigger = trigger_module.get_trigger(trigger)\n        self._postprocess = postprocess\n        self._log = []\n        # When using a writer, it needs to have a savefun defined\n        # to deal with a string.\n        self._writer = kwargs.get('writer', None)\n\n        log_name = kwargs.get('log_name', 'log')\n        if filename is None:\n            filename = log_name\n        del log_name  # avoid accidental use\n        self._log_name = filename\n\n        if format is None and filename is not None:\n            if filename.endswith('.jsonl'):\n                format = 'json-lines'\n            elif filename.endswith('.yaml'):\n                format = 'yaml'\n            else:\n                format = 'json'\n\n        self._append = append\n        self._format = format\n        self._init_summary()\n\n    def __call__(self, manager):\n        # accumulate the observations\n        keys = self._keys\n        observation = manager.observation\n        summary = self._summary\n\n        if keys is None:\n            summary.add(observation)\n        else:\n            summary.add({k: observation[k] for k in keys if k in observation})\n\n        writer = manager.writer if self._writer is None else self._writer\n\n        if manager.is_before_training or self._trigger(manager):\n            # output the result\n            stats = self._summary.compute_mean()\n            stats_cpu = {}\n            for name, value in stats.items():\n                stats_cpu[name] = float(value)  # copy to CPU\n\n            stats_cpu['epoch'] = manager.epoch\n            stats_cpu['iteration'] = manager.iteration\n            stats_cpu['elapsed_time'] = manager.elapsed_time\n\n            if self._postprocess is not None:\n                self._postprocess(stats_cpu)\n\n            self._log.append(stats_cpu)\n\n            # write to the log file\n            if self._log_name is not None:\n                log_name = self._log_name.format(**stats_cpu)\n                out = manager.out\n                savefun = LogWriterSaveFunc(self._format, self._append)\n                writer(log_name, out, self._log,\n                       savefun=savefun, append=self._append)\n\n            # reset the summary for the next output\n            self._init_summary()\n\n    @property\n    def log(self):\n        \"\"\"The current list of observation dictionaries.\"\"\"\n        return self._log\n\n    def state_dict(self):\n        state = {}\n        if hasattr(self._trigger, 'state_dict'):\n            state['_trigger'] = self._trigger.state_dict()\n\n        try:\n            state['_summary'] = self._summary.state_dict()\n        except KeyError:\n            pass\n        state['_log'] = json.dumps(self._log)\n        return state\n\n    def load_state_dict(self, to_load):\n        if hasattr(self._trigger, 'load_state_dict'):\n            self._trigger.load_state_dict(to_load['_trigger'])\n        self._summary.load_state_dict(to_load['_summary'])\n        self._log = json.loads(to_load['_log'])\n\n    def _init_summary(self):\n        self._summary = reporting.DictSummary()\n\n    def to_dataframe(self):\n        if not _pandas_available:\n            raise ImportError(\n                \"Need to install pandas to use `to_dataframe` method.\"\n            )\n        return pandas.DataFrame(self._log)\n", "path": "pytorch_pfn_extras/training/extensions/log_report.py"}]}