We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Task logs don't end
### Describe the bug

Task logs contain the normal server logs, even though the task has stopped. This leads to duplicated logs in all created files.

### Steps to reproduce

1. `cobbler mkloaders` (or any other task)
2. Repeat step 1
3. See logs being appended to completed tasks

### Expected behavior

The files should only contain the logs for the task.

### Cobbler version

Commit: 2a5c7ce9c1533bbf6f6b9050198d98aca45a06ae

````paste below
Cobbler 3.4.0
  source: ?, ?
  build time: Fri Oct 14 14:17:09 2022
````

### Operating system

```
e62e937a2de9:/var/log/cobbler/tasks # cat /etc/os-release 
NAME="openSUSE Leap"
VERSION="15.3"
ID="opensuse-leap"
ID_LIKE="suse opensuse"
VERSION_ID="15.3"
PRETTY_NAME="openSUSE Leap 15.3"
ANSI_COLOR="0;32"
CPE_NAME="cpe:/o:opensuse:leap:15.3"
BUG_REPORT_URL="https://bugs.opensuse.org"
HOME_URL="https://www.opensuse.org/"
```

### Cobbler log

Not relevant

### Screenshots

Not relevant

### Additional information

Found by accident during another debugging session


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `cobbler/utils/thread.py`
Content:
```
1 """
2 This module is responsible for managing the custom common threading logic Cobbler has.
3 """
4 
5 import logging
6 import pathlib
7 from threading import Thread
8 from typing import Callable
9 
10 from cobbler import enums
11 from cobbler import utils
12 
13 
14 class CobblerThread(Thread):
15     """
16     This is a custom thread that has a custom logger as well as logic to execute Cobbler triggers.
17     """
18 
19     def __init__(
20         self,
21         event_id: str,
22         remote,
23         options: dict,
24         task_name: str,
25         api,
26         run: Callable,
27         on_done: Callable = None,
28     ):
29         """
30         This constructor creates a Cobbler thread which then may be run by calling ``run()``.
31 
32         :param event_id: The event-id which is associated with this thread. Also used as thread name
33         :param remote: The Cobbler remote object to execute actions with.
34         :param options: Additional options which can be passed into the Thread.
35         :param task_name: The high level task name which is used to trigger pre- and post-task triggers
36         :param api: The Cobbler api object to resolve information with.
37         :param run: The callable that is going to be executed with this thread.
38         :param on_done: An optional callable that is going to be executed after ``run`` but before the triggers.
39         """
40         super().__init__(name=event_id)
41         self.event_id = event_id
42         self.remote = remote
43         self.logger = logging.getLogger()
44         self.__setup_logger()
45         self._run = run
46         self.on_done = on_done
47         if options is None:
48             options = {}
49         self.options = options
50         self.task_name = task_name
51         self.api = api
52 
53     def __setup_logger(self):
54         """
55         Utility function that will set up the Python logger for the tasks in a special directory.
56         """
57         filename = pathlib.Path("/var/log/cobbler/tasks") / f"{self.event_id}.log"
58         task_log_handler = logging.FileHandler(str(filename), encoding="utf-8")
59         task_log_formatter = logging.Formatter(
60             "[%(threadName)s] %(asctime)s - %(levelname)s | %(message)s"
61         )
62         task_log_handler.setFormatter(task_log_formatter)
63         self.logger.setLevel(logging.INFO)
64         self.logger.addHandler(task_log_handler)
65 
66     def _set_task_state(self, new_state: enums.EventStatus):
67         """
68         Set the state of the task. (For internal use only)
69 
70         :param new_state: The new state of the task.
71         """
72         if not isinstance(new_state, enums.EventStatus):
73             raise TypeError('"new_state" needs to be of type enums.EventStatus!')
74         if self.event_id not in self.remote.events:
75             raise ValueError('"event_id" not existing!')
76         self.remote.events[self.event_id].state = new_state
77         # clear the list of who has read it
78         self.remote.events[self.event_id].read_by_who = []
79         if new_state == enums.EventStatus.COMPLETE:
80             self.logger.info("### TASK COMPLETE ###")
81         elif new_state == enums.EventStatus.FAILED:
82             self.logger.error("### TASK FAILED ###")
83 
84     def run(self):
85         """
86         Run the thread.
87 
88         :return: The return code of the action. This may a boolean or a Linux return code.
89         """
90         self.logger.info("start_task(%s); event_id(%s)", self.task_name, self.event_id)
91         try:
92             if utils.run_triggers(
93                 api=self.api,
94                 globber=f"/var/lib/cobbler/triggers/task/{self.task_name}/pre/*",
95                 additional=self.options,
96             ):
97                 self._set_task_state(enums.EventStatus.FAILED)
98                 return False
99             rc = self._run(self)
100             if rc is not None and not rc:
101                 self._set_task_state(enums.EventStatus.FAILED)
102             else:
103                 self._set_task_state(enums.EventStatus.COMPLETE)
104                 if self.on_done is not None:
105                     self.on_done()
106                 utils.run_triggers(
107                     api=self.api,
108                     globber=f"/var/lib/cobbler/triggers/task/{self.task_name}/post/*",
109                     additional=self.options,
110                 )
111             return rc
112         except Exception:
113             utils.log_exc()
114             self._set_task_state(enums.EventStatus.FAILED)
115             return False
116
```
--- 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/cobbler/utils/thread.py b/cobbler/utils/thread.py
--- a/cobbler/utils/thread.py
+++ b/cobbler/utils/thread.py
@@ -41,6 +41,7 @@
         self.event_id = event_id
         self.remote = remote
         self.logger = logging.getLogger()
+        self.__task_log_handler = None
         self.__setup_logger()
         self._run = run
         self.on_done = on_done
@@ -55,13 +56,13 @@
         Utility function that will set up the Python logger for the tasks in a special directory.
         """
         filename = pathlib.Path("/var/log/cobbler/tasks") / f"{self.event_id}.log"
-        task_log_handler = logging.FileHandler(str(filename), encoding="utf-8")
+        self.__task_log_handler = logging.FileHandler(str(filename), encoding="utf-8")
         task_log_formatter = logging.Formatter(
             "[%(threadName)s] %(asctime)s - %(levelname)s | %(message)s"
         )
-        task_log_handler.setFormatter(task_log_formatter)
+        self.__task_log_handler.setFormatter(task_log_formatter)
         self.logger.setLevel(logging.INFO)
-        self.logger.addHandler(task_log_handler)
+        self.logger.addHandler(self.__task_log_handler)
 
     def _set_task_state(self, new_state: enums.EventStatus):
         """
@@ -113,3 +114,5 @@
             utils.log_exc()
             self._set_task_state(enums.EventStatus.FAILED)
             return False
+        finally:
+            self.logger.removeHandler(self.__task_log_handler)

{"golden_diff": "diff --git a/cobbler/utils/thread.py b/cobbler/utils/thread.py\n--- a/cobbler/utils/thread.py\n+++ b/cobbler/utils/thread.py\n@@ -41,6 +41,7 @@\n         self.event_id = event_id\n         self.remote = remote\n         self.logger = logging.getLogger()\n+        self.__task_log_handler = None\n         self.__setup_logger()\n         self._run = run\n         self.on_done = on_done\n@@ -55,13 +56,13 @@\n         Utility function that will set up the Python logger for the tasks in a special directory.\n         \"\"\"\n         filename = pathlib.Path(\"/var/log/cobbler/tasks\") / f\"{self.event_id}.log\"\n-        task_log_handler = logging.FileHandler(str(filename), encoding=\"utf-8\")\n+        self.__task_log_handler = logging.FileHandler(str(filename), encoding=\"utf-8\")\n         task_log_formatter = logging.Formatter(\n             \"[%(threadName)s] %(asctime)s - %(levelname)s | %(message)s\"\n         )\n-        task_log_handler.setFormatter(task_log_formatter)\n+        self.__task_log_handler.setFormatter(task_log_formatter)\n         self.logger.setLevel(logging.INFO)\n-        self.logger.addHandler(task_log_handler)\n+        self.logger.addHandler(self.__task_log_handler)\n \n     def _set_task_state(self, new_state: enums.EventStatus):\n         \"\"\"\n@@ -113,3 +114,5 @@\n             utils.log_exc()\n             self._set_task_state(enums.EventStatus.FAILED)\n             return False\n+        finally:\n+            self.logger.removeHandler(self.__task_log_handler)\n", "issue": "Task logs don't end\n### Describe the bug\r\n\r\nTask logs contain the normal server logs, even though the task has stopped. This leads to duplicated logs in all created files.\r\n\r\n### Steps to reproduce\r\n\r\n1. `cobbler mkloaders` (or any other task)\r\n2. Repeat step 1\r\n3. See logs being appended to completed tasks\r\n\r\n### Expected behavior\r\n\r\nThe files should only contain the logs for the task.\r\n\r\n### Cobbler version\r\n\r\nCommit: 2a5c7ce9c1533bbf6f6b9050198d98aca45a06ae\r\n\r\n````paste below\r\nCobbler 3.4.0\r\n  source: ?, ?\r\n  build time: Fri Oct 14 14:17:09 2022\r\n````\r\n\r\n### Operating system\r\n\r\n```\r\ne62e937a2de9:/var/log/cobbler/tasks # cat /etc/os-release \r\nNAME=\"openSUSE Leap\"\r\nVERSION=\"15.3\"\r\nID=\"opensuse-leap\"\r\nID_LIKE=\"suse opensuse\"\r\nVERSION_ID=\"15.3\"\r\nPRETTY_NAME=\"openSUSE Leap 15.3\"\r\nANSI_COLOR=\"0;32\"\r\nCPE_NAME=\"cpe:/o:opensuse:leap:15.3\"\r\nBUG_REPORT_URL=\"https://bugs.opensuse.org\"\r\nHOME_URL=\"https://www.opensuse.org/\"\r\n```\r\n\r\n### Cobbler log\r\n\r\nNot relevant\r\n\r\n### Screenshots\r\n\r\nNot relevant\r\n\r\n### Additional information\r\n\r\nFound by accident during another debugging session\r\n\n", "before_files": [{"content": "\"\"\"\nThis module is responsible for managing the custom common threading logic Cobbler has.\n\"\"\"\n\nimport logging\nimport pathlib\nfrom threading import Thread\nfrom typing import Callable\n\nfrom cobbler import enums\nfrom cobbler import utils\n\n\nclass CobblerThread(Thread):\n    \"\"\"\n    This is a custom thread that has a custom logger as well as logic to execute Cobbler triggers.\n    \"\"\"\n\n    def __init__(\n        self,\n        event_id: str,\n        remote,\n        options: dict,\n        task_name: str,\n        api,\n        run: Callable,\n        on_done: Callable = None,\n    ):\n        \"\"\"\n        This constructor creates a Cobbler thread which then may be run by calling ``run()``.\n\n        :param event_id: The event-id which is associated with this thread. Also used as thread name\n        :param remote: The Cobbler remote object to execute actions with.\n        :param options: Additional options which can be passed into the Thread.\n        :param task_name: The high level task name which is used to trigger pre- and post-task triggers\n        :param api: The Cobbler api object to resolve information with.\n        :param run: The callable that is going to be executed with this thread.\n        :param on_done: An optional callable that is going to be executed after ``run`` but before the triggers.\n        \"\"\"\n        super().__init__(name=event_id)\n        self.event_id = event_id\n        self.remote = remote\n        self.logger = logging.getLogger()\n        self.__setup_logger()\n        self._run = run\n        self.on_done = on_done\n        if options is None:\n            options = {}\n        self.options = options\n        self.task_name = task_name\n        self.api = api\n\n    def __setup_logger(self):\n        \"\"\"\n        Utility function that will set up the Python logger for the tasks in a special directory.\n        \"\"\"\n        filename = pathlib.Path(\"/var/log/cobbler/tasks\") / f\"{self.event_id}.log\"\n        task_log_handler = logging.FileHandler(str(filename), encoding=\"utf-8\")\n        task_log_formatter = logging.Formatter(\n            \"[%(threadName)s] %(asctime)s - %(levelname)s | %(message)s\"\n        )\n        task_log_handler.setFormatter(task_log_formatter)\n        self.logger.setLevel(logging.INFO)\n        self.logger.addHandler(task_log_handler)\n\n    def _set_task_state(self, new_state: enums.EventStatus):\n        \"\"\"\n        Set the state of the task. (For internal use only)\n\n        :param new_state: The new state of the task.\n        \"\"\"\n        if not isinstance(new_state, enums.EventStatus):\n            raise TypeError('\"new_state\" needs to be of type enums.EventStatus!')\n        if self.event_id not in self.remote.events:\n            raise ValueError('\"event_id\" not existing!')\n        self.remote.events[self.event_id].state = new_state\n        # clear the list of who has read it\n        self.remote.events[self.event_id].read_by_who = []\n        if new_state == enums.EventStatus.COMPLETE:\n            self.logger.info(\"### TASK COMPLETE ###\")\n        elif new_state == enums.EventStatus.FAILED:\n            self.logger.error(\"### TASK FAILED ###\")\n\n    def run(self):\n        \"\"\"\n        Run the thread.\n\n        :return: The return code of the action. This may a boolean or a Linux return code.\n        \"\"\"\n        self.logger.info(\"start_task(%s); event_id(%s)\", self.task_name, self.event_id)\n        try:\n            if utils.run_triggers(\n                api=self.api,\n                globber=f\"/var/lib/cobbler/triggers/task/{self.task_name}/pre/*\",\n                additional=self.options,\n            ):\n                self._set_task_state(enums.EventStatus.FAILED)\n                return False\n            rc = self._run(self)\n            if rc is not None and not rc:\n                self._set_task_state(enums.EventStatus.FAILED)\n            else:\n                self._set_task_state(enums.EventStatus.COMPLETE)\n                if self.on_done is not None:\n                    self.on_done()\n                utils.run_triggers(\n                    api=self.api,\n                    globber=f\"/var/lib/cobbler/triggers/task/{self.task_name}/post/*\",\n                    additional=self.options,\n                )\n            return rc\n        except Exception:\n            utils.log_exc()\n            self._set_task_state(enums.EventStatus.FAILED)\n            return False\n", "path": "cobbler/utils/thread.py"}], "after_files": [{"content": "\"\"\"\nThis module is responsible for managing the custom common threading logic Cobbler has.\n\"\"\"\n\nimport logging\nimport pathlib\nfrom threading import Thread\nfrom typing import Callable\n\nfrom cobbler import enums\nfrom cobbler import utils\n\n\nclass CobblerThread(Thread):\n    \"\"\"\n    This is a custom thread that has a custom logger as well as logic to execute Cobbler triggers.\n    \"\"\"\n\n    def __init__(\n        self,\n        event_id: str,\n        remote,\n        options: dict,\n        task_name: str,\n        api,\n        run: Callable,\n        on_done: Callable = None,\n    ):\n        \"\"\"\n        This constructor creates a Cobbler thread which then may be run by calling ``run()``.\n\n        :param event_id: The event-id which is associated with this thread. Also used as thread name\n        :param remote: The Cobbler remote object to execute actions with.\n        :param options: Additional options which can be passed into the Thread.\n        :param task_name: The high level task name which is used to trigger pre- and post-task triggers\n        :param api: The Cobbler api object to resolve information with.\n        :param run: The callable that is going to be executed with this thread.\n        :param on_done: An optional callable that is going to be executed after ``run`` but before the triggers.\n        \"\"\"\n        super().__init__(name=event_id)\n        self.event_id = event_id\n        self.remote = remote\n        self.logger = logging.getLogger()\n        self.__task_log_handler = None\n        self.__setup_logger()\n        self._run = run\n        self.on_done = on_done\n        if options is None:\n            options = {}\n        self.options = options\n        self.task_name = task_name\n        self.api = api\n\n    def __setup_logger(self):\n        \"\"\"\n        Utility function that will set up the Python logger for the tasks in a special directory.\n        \"\"\"\n        filename = pathlib.Path(\"/var/log/cobbler/tasks\") / f\"{self.event_id}.log\"\n        self.__task_log_handler = logging.FileHandler(str(filename), encoding=\"utf-8\")\n        task_log_formatter = logging.Formatter(\n            \"[%(threadName)s] %(asctime)s - %(levelname)s | %(message)s\"\n        )\n        self.__task_log_handler.setFormatter(task_log_formatter)\n        self.logger.setLevel(logging.INFO)\n        self.logger.addHandler(self.__task_log_handler)\n\n    def _set_task_state(self, new_state: enums.EventStatus):\n        \"\"\"\n        Set the state of the task. (For internal use only)\n\n        :param new_state: The new state of the task.\n        \"\"\"\n        if not isinstance(new_state, enums.EventStatus):\n            raise TypeError('\"new_state\" needs to be of type enums.EventStatus!')\n        if self.event_id not in self.remote.events:\n            raise ValueError('\"event_id\" not existing!')\n        self.remote.events[self.event_id].state = new_state\n        # clear the list of who has read it\n        self.remote.events[self.event_id].read_by_who = []\n        if new_state == enums.EventStatus.COMPLETE:\n            self.logger.info(\"### TASK COMPLETE ###\")\n        elif new_state == enums.EventStatus.FAILED:\n            self.logger.error(\"### TASK FAILED ###\")\n\n    def run(self):\n        \"\"\"\n        Run the thread.\n\n        :return: The return code of the action. This may a boolean or a Linux return code.\n        \"\"\"\n        self.logger.info(\"start_task(%s); event_id(%s)\", self.task_name, self.event_id)\n        try:\n            if utils.run_triggers(\n                api=self.api,\n                globber=f\"/var/lib/cobbler/triggers/task/{self.task_name}/pre/*\",\n                additional=self.options,\n            ):\n                self._set_task_state(enums.EventStatus.FAILED)\n                return False\n            rc = self._run(self)\n            if rc is not None and not rc:\n                self._set_task_state(enums.EventStatus.FAILED)\n            else:\n                self._set_task_state(enums.EventStatus.COMPLETE)\n                if self.on_done is not None:\n                    self.on_done()\n                utils.run_triggers(\n                    api=self.api,\n                    globber=f\"/var/lib/cobbler/triggers/task/{self.task_name}/post/*\",\n                    additional=self.options,\n                )\n            return rc\n        except Exception:\n            utils.log_exc()\n            self._set_task_state(enums.EventStatus.FAILED)\n            return False\n        finally:\n            self.logger.removeHandler(self.__task_log_handler)\n", "path": "cobbler/utils/thread.py"}]}