We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Fix multiple file support in linux.file_watch.line + black + fstring
When multiple file_watch rules are defined, the last defined file reference is used for all files being watched.  This causes trigger-instances to fail rule enforcement.

Adding the reference to the logging shows `test1.log` has the reference ending with `8c505`
```
2021-11-30 18:50:40,434 140243179888112 INFO file_watch_sensor [-] Added file "/var/log/test1.log" with reference linux.7e55ad75-b10c-44db-b53e-95164a18c505
2021-11-30 18:50:41,459 140243179888112 INFO file_watch_sensor [-] Added file "/var/log/test2.log" with reference linux.590de8c1-c578-4125-9082-2cee03b030a9
```

When the file contents are updated a trigger is emitted by the sensor using the reference of `test2.log` ending in `b030a9`
```
root@u1804:~# st2 trigger-instance get 61a6649f164625c2d94dccb8 -y
id: 61a6649f164625c2d94dccb8
occurrence_time: '2021-11-30T17:51:27.294000Z'
payload:
    file_name: test1.log
    file_path: /var/log/test1.log
    line: Tue Nov 30 18:51:27 CET 2021 dhcp
status: processed
trigger: linux.590de8c1-c578-4125-9082-2cee03b030a9
```

This PR consists of adding a dictionary that is used to track the `path_name` and `reference` pair and looks up the reference for the file that was altered when creating the trigger.

The code is formatted with black and updated to use fstrings since all instances will be using Python 3.6+

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `contrib/linux/sensors/file_watch_sensor.py`
Content:
```
1 # Copyright 2020 The StackStorm Authors.
2 # Copyright 2019 Extreme Networks, Inc.
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 #     http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 
16 import os
17 
18 import eventlet
19 
20 from logshipper.tail import Tail
21 
22 from st2reactor.sensor.base import Sensor
23 
24 
25 class FileWatchSensor(Sensor):
26     def __init__(self, sensor_service, config=None):
27         super(FileWatchSensor, self).__init__(
28             sensor_service=sensor_service, config=config
29         )
30         self._trigger = None
31         self._logger = self._sensor_service.get_logger(__name__)
32         self._tail = None
33 
34     def setup(self):
35         self._tail = Tail(filenames=[])
36         self._tail.handler = self._handle_line
37         self._tail.should_run = True
38 
39     def run(self):
40         self._tail.run()
41 
42     def cleanup(self):
43         if self._tail:
44             self._tail.should_run = False
45 
46             try:
47                 self._tail.notifier.stop()
48             except Exception:
49                 self._logger.exception("Unable to stop the tail notifier")
50 
51     def add_trigger(self, trigger):
52         file_path = trigger["parameters"].get("file_path", None)
53 
54         if not file_path:
55             self._logger.error('Received trigger type without "file_path" field.')
56             return
57 
58         self._trigger = trigger.get("ref", None)
59 
60         if not self._trigger:
61             raise Exception("Trigger %s did not contain a ref." % trigger)
62 
63         # Wait a bit to avoid initialization race in logshipper library
64         eventlet.sleep(1.0)
65 
66         self._tail.add_file(filename=file_path)
67         self._logger.info('Added file "%s"' % (file_path))
68 
69     def update_trigger(self, trigger):
70         pass
71 
72     def remove_trigger(self, trigger):
73         file_path = trigger["parameters"].get("file_path", None)
74 
75         if not file_path:
76             self._logger.error('Received trigger type without "file_path" field.')
77             return
78 
79         self._tail.remove_file(filename=file_path)
80         self._trigger = None
81 
82         self._logger.info('Removed file "%s"' % (file_path))
83 
84     def _handle_line(self, file_path, line):
85         trigger = self._trigger
86         payload = {
87             "file_path": file_path,
88             "file_name": os.path.basename(file_path),
89             "line": line,
90         }
91         self._logger.debug(
92             "Sending payload %s for trigger %s to sensor_service.", payload, trigger
93         )
94         self.sensor_service.dispatch(trigger=trigger, payload=payload)
95
```
--- 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/contrib/linux/sensors/file_watch_sensor.py b/contrib/linux/sensors/file_watch_sensor.py
--- a/contrib/linux/sensors/file_watch_sensor.py
+++ b/contrib/linux/sensors/file_watch_sensor.py
@@ -14,7 +14,6 @@
 # limitations under the License.
 
 import os
-
 import eventlet
 
 from logshipper.tail import Tail
@@ -27,44 +26,46 @@
         super(FileWatchSensor, self).__init__(
             sensor_service=sensor_service, config=config
         )
-        self._trigger = None
-        self._logger = self._sensor_service.get_logger(__name__)
-        self._tail = None
+        self.log = self._sensor_service.get_logger(__name__)
+        self.tail = None
+        self.file_ref = {}
 
     def setup(self):
-        self._tail = Tail(filenames=[])
-        self._tail.handler = self._handle_line
-        self._tail.should_run = True
+        self.tail = Tail(filenames=[])
+        self.tail.handler = self._handle_line
+        self.tail.should_run = True
 
     def run(self):
-        self._tail.run()
+        self.tail.run()
 
     def cleanup(self):
-        if self._tail:
-            self._tail.should_run = False
+        if self.tail:
+            self.tail.should_run = False
 
             try:
-                self._tail.notifier.stop()
+                self.tail.notifier.stop()
             except Exception:
-                self._logger.exception("Unable to stop the tail notifier")
+                self.log.exception("Unable to stop the tail notifier")
 
     def add_trigger(self, trigger):
         file_path = trigger["parameters"].get("file_path", None)
 
         if not file_path:
-            self._logger.error('Received trigger type without "file_path" field.')
+            self.log.error('Received trigger type without "file_path" field.')
             return
 
-        self._trigger = trigger.get("ref", None)
+        trigger = trigger.get("ref", None)
 
-        if not self._trigger:
-            raise Exception("Trigger %s did not contain a ref." % trigger)
+        if not trigger:
+            raise Exception(f"Trigger {trigger} did not contain a ref.")
 
         # Wait a bit to avoid initialization race in logshipper library
         eventlet.sleep(1.0)
 
-        self._tail.add_file(filename=file_path)
-        self._logger.info('Added file "%s"' % (file_path))
+        self.tail.add_file(filename=file_path)
+        self.file_ref[file_path] = trigger
+
+        self.log.info(f"Added file '{file_path}' ({trigger}) to watch list.")
 
     def update_trigger(self, trigger):
         pass
@@ -73,22 +74,28 @@
         file_path = trigger["parameters"].get("file_path", None)
 
         if not file_path:
-            self._logger.error('Received trigger type without "file_path" field.')
+            self.log.error("Received trigger type without 'file_path' field.")
             return
 
-        self._tail.remove_file(filename=file_path)
-        self._trigger = None
+        self.tail.remove_file(filename=file_path)
+        self.file_ref.pop(file_path)
 
-        self._logger.info('Removed file "%s"' % (file_path))
+        self.log.info(f"Removed file '{file_path}' ({trigger}) from watch list.")
 
     def _handle_line(self, file_path, line):
-        trigger = self._trigger
+        if file_path not in self.file_ref:
+            self.log.error(
+                f"No reference found for {file_path}, unable to emit trigger!"
+            )
+            return
+
+        trigger = self.file_ref[file_path]
         payload = {
             "file_path": file_path,
             "file_name": os.path.basename(file_path),
             "line": line,
         }
-        self._logger.debug(
-            "Sending payload %s for trigger %s to sensor_service.", payload, trigger
+        self.log.debug(
+            f"Sending payload {payload} for trigger {trigger} to sensor_service."
         )
         self.sensor_service.dispatch(trigger=trigger, payload=payload)

{"golden_diff": "diff --git a/contrib/linux/sensors/file_watch_sensor.py b/contrib/linux/sensors/file_watch_sensor.py\n--- a/contrib/linux/sensors/file_watch_sensor.py\n+++ b/contrib/linux/sensors/file_watch_sensor.py\n@@ -14,7 +14,6 @@\n # limitations under the License.\n \n import os\n-\n import eventlet\n \n from logshipper.tail import Tail\n@@ -27,44 +26,46 @@\n         super(FileWatchSensor, self).__init__(\n             sensor_service=sensor_service, config=config\n         )\n-        self._trigger = None\n-        self._logger = self._sensor_service.get_logger(__name__)\n-        self._tail = None\n+        self.log = self._sensor_service.get_logger(__name__)\n+        self.tail = None\n+        self.file_ref = {}\n \n     def setup(self):\n-        self._tail = Tail(filenames=[])\n-        self._tail.handler = self._handle_line\n-        self._tail.should_run = True\n+        self.tail = Tail(filenames=[])\n+        self.tail.handler = self._handle_line\n+        self.tail.should_run = True\n \n     def run(self):\n-        self._tail.run()\n+        self.tail.run()\n \n     def cleanup(self):\n-        if self._tail:\n-            self._tail.should_run = False\n+        if self.tail:\n+            self.tail.should_run = False\n \n             try:\n-                self._tail.notifier.stop()\n+                self.tail.notifier.stop()\n             except Exception:\n-                self._logger.exception(\"Unable to stop the tail notifier\")\n+                self.log.exception(\"Unable to stop the tail notifier\")\n \n     def add_trigger(self, trigger):\n         file_path = trigger[\"parameters\"].get(\"file_path\", None)\n \n         if not file_path:\n-            self._logger.error('Received trigger type without \"file_path\" field.')\n+            self.log.error('Received trigger type without \"file_path\" field.')\n             return\n \n-        self._trigger = trigger.get(\"ref\", None)\n+        trigger = trigger.get(\"ref\", None)\n \n-        if not self._trigger:\n-            raise Exception(\"Trigger %s did not contain a ref.\" % trigger)\n+        if not trigger:\n+            raise Exception(f\"Trigger {trigger} did not contain a ref.\")\n \n         # Wait a bit to avoid initialization race in logshipper library\n         eventlet.sleep(1.0)\n \n-        self._tail.add_file(filename=file_path)\n-        self._logger.info('Added file \"%s\"' % (file_path))\n+        self.tail.add_file(filename=file_path)\n+        self.file_ref[file_path] = trigger\n+\n+        self.log.info(f\"Added file '{file_path}' ({trigger}) to watch list.\")\n \n     def update_trigger(self, trigger):\n         pass\n@@ -73,22 +74,28 @@\n         file_path = trigger[\"parameters\"].get(\"file_path\", None)\n \n         if not file_path:\n-            self._logger.error('Received trigger type without \"file_path\" field.')\n+            self.log.error(\"Received trigger type without 'file_path' field.\")\n             return\n \n-        self._tail.remove_file(filename=file_path)\n-        self._trigger = None\n+        self.tail.remove_file(filename=file_path)\n+        self.file_ref.pop(file_path)\n \n-        self._logger.info('Removed file \"%s\"' % (file_path))\n+        self.log.info(f\"Removed file '{file_path}' ({trigger}) from watch list.\")\n \n     def _handle_line(self, file_path, line):\n-        trigger = self._trigger\n+        if file_path not in self.file_ref:\n+            self.log.error(\n+                f\"No reference found for {file_path}, unable to emit trigger!\"\n+            )\n+            return\n+\n+        trigger = self.file_ref[file_path]\n         payload = {\n             \"file_path\": file_path,\n             \"file_name\": os.path.basename(file_path),\n             \"line\": line,\n         }\n-        self._logger.debug(\n-            \"Sending payload %s for trigger %s to sensor_service.\", payload, trigger\n+        self.log.debug(\n+            f\"Sending payload {payload} for trigger {trigger} to sensor_service.\"\n         )\n         self.sensor_service.dispatch(trigger=trigger, payload=payload)\n", "issue": "Fix multiple file support in linux.file_watch.line + black + fstring\nWhen multiple file_watch rules are defined, the last defined file reference is used for all files being watched.  This causes trigger-instances to fail rule enforcement.\r\n\r\nAdding the reference to the logging shows `test1.log` has the reference ending with `8c505`\r\n```\r\n2021-11-30 18:50:40,434 140243179888112 INFO file_watch_sensor [-] Added file \"/var/log/test1.log\" with reference linux.7e55ad75-b10c-44db-b53e-95164a18c505\r\n2021-11-30 18:50:41,459 140243179888112 INFO file_watch_sensor [-] Added file \"/var/log/test2.log\" with reference linux.590de8c1-c578-4125-9082-2cee03b030a9\r\n```\r\n\r\nWhen the file contents are updated a trigger is emitted by the sensor using the reference of `test2.log` ending in `b030a9`\r\n```\r\nroot@u1804:~# st2 trigger-instance get 61a6649f164625c2d94dccb8 -y\r\nid: 61a6649f164625c2d94dccb8\r\noccurrence_time: '2021-11-30T17:51:27.294000Z'\r\npayload:\r\n    file_name: test1.log\r\n    file_path: /var/log/test1.log\r\n    line: Tue Nov 30 18:51:27 CET 2021 dhcp\r\nstatus: processed\r\ntrigger: linux.590de8c1-c578-4125-9082-2cee03b030a9\r\n```\r\n\r\nThis PR consists of adding a dictionary that is used to track the `path_name` and `reference` pair and looks up the reference for the file that was altered when creating the trigger.\r\n\r\nThe code is formatted with black and updated to use fstrings since all instances will be using Python 3.6+\n", "before_files": [{"content": "# Copyright 2020 The StackStorm Authors.\n# Copyright 2019 Extreme Networks, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\n\nimport eventlet\n\nfrom logshipper.tail import Tail\n\nfrom st2reactor.sensor.base import Sensor\n\n\nclass FileWatchSensor(Sensor):\n    def __init__(self, sensor_service, config=None):\n        super(FileWatchSensor, self).__init__(\n            sensor_service=sensor_service, config=config\n        )\n        self._trigger = None\n        self._logger = self._sensor_service.get_logger(__name__)\n        self._tail = None\n\n    def setup(self):\n        self._tail = Tail(filenames=[])\n        self._tail.handler = self._handle_line\n        self._tail.should_run = True\n\n    def run(self):\n        self._tail.run()\n\n    def cleanup(self):\n        if self._tail:\n            self._tail.should_run = False\n\n            try:\n                self._tail.notifier.stop()\n            except Exception:\n                self._logger.exception(\"Unable to stop the tail notifier\")\n\n    def add_trigger(self, trigger):\n        file_path = trigger[\"parameters\"].get(\"file_path\", None)\n\n        if not file_path:\n            self._logger.error('Received trigger type without \"file_path\" field.')\n            return\n\n        self._trigger = trigger.get(\"ref\", None)\n\n        if not self._trigger:\n            raise Exception(\"Trigger %s did not contain a ref.\" % trigger)\n\n        # Wait a bit to avoid initialization race in logshipper library\n        eventlet.sleep(1.0)\n\n        self._tail.add_file(filename=file_path)\n        self._logger.info('Added file \"%s\"' % (file_path))\n\n    def update_trigger(self, trigger):\n        pass\n\n    def remove_trigger(self, trigger):\n        file_path = trigger[\"parameters\"].get(\"file_path\", None)\n\n        if not file_path:\n            self._logger.error('Received trigger type without \"file_path\" field.')\n            return\n\n        self._tail.remove_file(filename=file_path)\n        self._trigger = None\n\n        self._logger.info('Removed file \"%s\"' % (file_path))\n\n    def _handle_line(self, file_path, line):\n        trigger = self._trigger\n        payload = {\n            \"file_path\": file_path,\n            \"file_name\": os.path.basename(file_path),\n            \"line\": line,\n        }\n        self._logger.debug(\n            \"Sending payload %s for trigger %s to sensor_service.\", payload, trigger\n        )\n        self.sensor_service.dispatch(trigger=trigger, payload=payload)\n", "path": "contrib/linux/sensors/file_watch_sensor.py"}], "after_files": [{"content": "# Copyright 2020 The StackStorm Authors.\n# Copyright 2019 Extreme Networks, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nimport eventlet\n\nfrom logshipper.tail import Tail\n\nfrom st2reactor.sensor.base import Sensor\n\n\nclass FileWatchSensor(Sensor):\n    def __init__(self, sensor_service, config=None):\n        super(FileWatchSensor, self).__init__(\n            sensor_service=sensor_service, config=config\n        )\n        self.log = self._sensor_service.get_logger(__name__)\n        self.tail = None\n        self.file_ref = {}\n\n    def setup(self):\n        self.tail = Tail(filenames=[])\n        self.tail.handler = self._handle_line\n        self.tail.should_run = True\n\n    def run(self):\n        self.tail.run()\n\n    def cleanup(self):\n        if self.tail:\n            self.tail.should_run = False\n\n            try:\n                self.tail.notifier.stop()\n            except Exception:\n                self.log.exception(\"Unable to stop the tail notifier\")\n\n    def add_trigger(self, trigger):\n        file_path = trigger[\"parameters\"].get(\"file_path\", None)\n\n        if not file_path:\n            self.log.error('Received trigger type without \"file_path\" field.')\n            return\n\n        trigger = trigger.get(\"ref\", None)\n\n        if not trigger:\n            raise Exception(f\"Trigger {trigger} did not contain a ref.\")\n\n        # Wait a bit to avoid initialization race in logshipper library\n        eventlet.sleep(1.0)\n\n        self.tail.add_file(filename=file_path)\n        self.file_ref[file_path] = trigger\n\n        self.log.info(f\"Added file '{file_path}' ({trigger}) to watch list.\")\n\n    def update_trigger(self, trigger):\n        pass\n\n    def remove_trigger(self, trigger):\n        file_path = trigger[\"parameters\"].get(\"file_path\", None)\n\n        if not file_path:\n            self.log.error(\"Received trigger type without 'file_path' field.\")\n            return\n\n        self.tail.remove_file(filename=file_path)\n        self.file_ref.pop(file_path)\n\n        self.log.info(f\"Removed file '{file_path}' ({trigger}) from watch list.\")\n\n    def _handle_line(self, file_path, line):\n        if file_path not in self.file_ref:\n            self.log.error(\n                f\"No reference found for {file_path}, unable to emit trigger!\"\n            )\n            return\n\n        trigger = self.file_ref[file_path]\n        payload = {\n            \"file_path\": file_path,\n            \"file_name\": os.path.basename(file_path),\n            \"line\": line,\n        }\n        self.log.debug(\n            f\"Sending payload {payload} for trigger {trigger} to sensor_service.\"\n        )\n        self.sensor_service.dispatch(trigger=trigger, payload=payload)\n", "path": "contrib/linux/sensors/file_watch_sensor.py"}]}