Skip to content

Commit a0526d3

Browse files
authored
Merge pull request #2 from skiedude/filewatchsensor
Convert FileWatchSensor to use threading over eventlent and stackstorm/logshipper
2 parents 0d0f44c + 66c8be1 commit a0526d3

9 files changed

Lines changed: 64 additions & 91 deletions

File tree

CHANGELOG.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
Changelog
22
=========
33

4-
in development - unreleased
5-
---------------------------
4+
in development
5+
--------------
66

77
This release is tested with and supports the following Python versions and 3rd party dependencies.
88

@@ -24,11 +24,11 @@ Changed
2424
* Removed mongodb 7.0, rabbitmq 3.13 and redis 8.0
2525
* Replaced deprecated `pkg_resources` module with `importlib-metadata` and `importlib-resources`.
2626
* Replaced abandoned `flex` module by `openapi-spec-validator`
27+
* Replaced Stackstorm/logshipper (stops working with Python 3.12) and eventlet in the `linux.file_watch_sensor` with threading. (by @skiedude)
2728

2829
Added
2930
~~~~~
3031

31-
3232
3.9.0 - October 10, 2025
3333
------------------------
3434

contrib/linux/README.md

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,37 @@
22

33
This pack contains actions for commonly used Linux commands and tools.
44

5-
## Configuration
5+
## Sensors
6+
7+
### FileWatchSensor
68

7-
* ``file_watch_sensor.file_paths`` - A list of paths to the files to monitor.
8-
Note: Those need to be full paths to the files (e.g. ``/var/log/auth.log``)
9-
and not directories (files don't need to exist yet when the sensor is ran
10-
though).
9+
This sensor monitors files that are declared by a rule (one file per rule). Once a new line is
10+
detected, a trigger is emitted.
1111

12-
Example:
12+
### Example Rule:
1313

14-
```yaml
15-
---
16-
file_watch_sensor:
17-
file_paths:
18-
- /opt/data/absolute_path_to_file.log
14+
This example rule will register with the FileWatchSensor to watch the file `/tmp/st2_test`. When a new line is
15+
detected, the trigger will be emitted, and the action `core.local` will echo the trigger data.
1916
```
17+
---
18+
name: sample_rule_file_watch
19+
pack: "examples"
20+
description: Sample rule custom trigger type - add a file to be watched by file_watch_sensor in linux pack.
21+
enabled: true
2022
21-
## Sensors
23+
trigger:
24+
parameters:
25+
file_path: /tmp/st2_test
26+
type: linux.file_watch.line
2227
23-
### FileWatchSensor
28+
criteria: {}
2429
25-
This sensor monitors specified files for new new lines. Once a new line is
26-
detected, a trigger is emitted.
30+
action:
31+
parameters:
32+
cmd: echo "{{trigger}}"
33+
ref: core.local
34+
35+
```
2736

2837
### linux.file_watch.line trigger
2938

contrib/linux/requirements.txt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1 @@
1-
# used by file watcher sensor
2-
pyinotify>=0.9.5,<=0.10 ; platform_system=="Linux"
3-
logshipper@ git+https://github.com/StackStorm/logshipper.git@stackstorm_patched ; platform_system=="Linux"
1+

contrib/linux/sensors/README.md

Lines changed: 0 additions & 29 deletions
This file was deleted.

contrib/linux/sensors/file_watch_sensor.py

Lines changed: 36 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2020 The StackStorm Authors.
1+
# Copyright 2020-2026 The StackStorm Authors.
22
# Copyright 2019 Extreme Networks, Inc.
33
#
44
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,9 +14,8 @@
1414
# limitations under the License.
1515

1616
import os
17-
import eventlet
18-
19-
from logshipper.tail import Tail
17+
import time
18+
import threading
2019

2120
from st2reactor.sensor.base import Sensor
2221

@@ -27,25 +26,19 @@ def __init__(self, sensor_service, config=None):
2726
sensor_service=sensor_service, config=config
2827
)
2928
self.log = self._sensor_service.get_logger(__name__)
30-
self.tail = None
29+
self._watchers = {} # file_path -> (thread, stop_event)
3130
self.file_ref = {}
3231

3332
def setup(self):
34-
self.tail = Tail(filenames=[])
35-
self.tail.handler = self._handle_line
36-
self.tail.should_run = True
33+
pass
3734

3835
def run(self):
39-
self.tail.run()
36+
while True:
37+
time.sleep(1)
4038

4139
def cleanup(self):
42-
if self.tail:
43-
self.tail.should_run = False
44-
45-
try:
46-
self.tail.notifier.stop()
47-
except Exception:
48-
self.log.exception("Unable to stop the tail notifier")
40+
for file_path in list(self._watchers):
41+
self._stop_watcher(file_path)
4942

5043
def add_trigger(self, trigger):
5144
file_path = trigger["parameters"].get("file_path", None)
@@ -54,18 +47,19 @@ def add_trigger(self, trigger):
5447
self.log.error('Received trigger type without "file_path" field.')
5548
return
5649

57-
trigger = trigger.get("ref", None)
50+
ref = trigger.get("ref", None)
5851

59-
if not trigger:
52+
if not ref:
6053
raise Exception(f"Trigger {trigger} did not contain a ref.")
6154

62-
# Wait a bit to avoid initialization race in logshipper library
63-
eventlet.sleep(1.0)
55+
self.file_ref[file_path] = ref
6456

65-
self.tail.add_file(filename=file_path)
66-
self.file_ref[file_path] = trigger
57+
stop_event = threading.Event()
58+
t = threading.Thread(target=self._tail, args=(file_path, stop_event), daemon=True)
59+
self._watchers[file_path] = (t, stop_event)
60+
t.start()
6761

68-
self.log.info(f"Added file '{file_path}' ({trigger}) to watch list.")
62+
self.log.info(f"Added file '{file_path}' ({ref}) to watch list.")
6963

7064
def update_trigger(self, trigger):
7165
pass
@@ -77,10 +71,25 @@ def remove_trigger(self, trigger):
7771
self.log.error("Received trigger type without 'file_path' field.")
7872
return
7973

80-
self.tail.remove_file(filename=file_path)
81-
self.file_ref.pop(file_path)
82-
83-
self.log.info(f"Removed file '{file_path}' ({trigger}) from watch list.")
74+
self._stop_watcher(file_path)
75+
self.file_ref.pop(file_path, None)
76+
77+
self.log.info(f"Removed file '{file_path}' from watch list.")
78+
79+
def _stop_watcher(self, file_path):
80+
if file_path in self._watchers:
81+
_, stop_event = self._watchers.pop(file_path)
82+
stop_event.set()
83+
84+
def _tail(self, file_path, stop_event):
85+
with open(file_path, "r") as f:
86+
f.seek(0, 2) # seek to EOF
87+
while not stop_event.is_set():
88+
line = f.readline()
89+
if line:
90+
self._handle_line(file_path, line.strip())
91+
else:
92+
time.sleep(0.1)
8493

8594
def _handle_line(self, file_path, line):
8695
if file_path not in self.file_ref:

fixed-requirements.txt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ oslo.utils==7.3.0
3939
paramiko==3.5.1
4040
# 202403: bump to 3.0.43 for py3.10 support
4141
prompt-toolkit==3.0.52
42-
pyinotify==0.9.6 ; platform_system=="Linux"
4342
pymongo==4.6.3
4443
pyparsing==3.1.4
4544
zstandard==0.23.0
@@ -76,8 +75,6 @@ tooz==6.3.0
7675
# virtualenv==20.30.0 (<21) has pip==25.0.1 wheel==0.45.1 setuptools==75.3.2
7776
# lockfiles/st2.lock has pip==25.0.1 wheel==0.45.1 setuptools==75.3.2
7877
pip==25.3
79-
# This setuptools version number is in the Makefile, but CircleCI builds are pulling a version
80-
# that is incompatible with our logshipper fork.
8178
setuptools==80.10.2
8279
webob==1.8.9
8380
webtest==3.0.1

st2actions/in-requirements.txt

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,6 @@ python-json-logger
1616
# needed by core "packs" pack
1717
gitpython
1818
lockfile
19-
# needed by core "linux" pack - TODO: create virtualenv for linux pack on postinst
20-
pyinotify
21-
logshipper@ git+https://github.com/StackStorm/logshipper.git ; platform_system=="Linux"
22-
# logshipper has metadata in setup.cfg that is not supported by setuptools 78, so we need
23-
# an explicit dep (from fixed-requirements.txt) to prevent CircleCI from pulling that in.
2419
setuptools
2520
# required by pack_mgmt/setup_virtualenv.py#L135
2621
virtualenv

st2common/tests/fixtures/requirements-used-for-tests.txt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,8 @@ flex==6.14.0
77
# some
88
# more
99
# commments....
10-
git+https://github.com/Kami/logshipper.git@stackstorm_patched#egg=logshipper
1110
git+https://github.com/StackStorm/orquesta.git@224c1a589a6007eb0598a62ee99d674e7836d369#egg=orquesta
1211
git+https://github.com/StackStorm/st2-auth-backend-flat-file.git@master#egg=st2-auth-backend-flat-file
13-
-e git+https://github.com/Kami/logshipper.git@stackstorm_patched#egg=logshipper-editable
1412
git+https://github.com/StackStorm/st2.git#egg=python_runner&subdirectory=contrib/runners/python_runner
1513
hg+https://hg.repo/some_pkg.git#egg=SomePackageHq
1614
svn+svn://svn.repo/some_pkg/trunk/@ma-branch#egg=SomePackageSvn

st2common/tests/unit/test_dist_utils.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,8 @@ def test_fetch_requirements(self):
7474
"amqp==2.5.1",
7575
"argcomplete",
7676
"bcrypt==3.1.6",
77-
"logshipper",
7877
"orquesta",
7978
"st2-auth-backend-flat-file",
80-
"logshipper-editable",
8179
"python_runner",
8280
"SomePackageHq",
8381
"SomePackageSvn",
@@ -89,10 +87,8 @@ def test_fetch_requirements(self):
8987
"zake==0.2.2",
9088
]
9189
expected_links = [
92-
"git+https://github.com/Kami/logshipper.git@stackstorm_patched#egg=logshipper",
9390
"git+https://github.com/StackStorm/orquesta.git@224c1a589a6007eb0598a62ee99d674e7836d369#egg=orquesta", # NOQA
9491
"git+https://github.com/StackStorm/st2-auth-backend-flat-file.git@master#egg=st2-auth-backend-flat-file", # NOQA
95-
"git+https://github.com/Kami/logshipper.git@stackstorm_patched#egg=logshipper-editable",
9692
"git+https://github.com/StackStorm/st2.git#egg=python_runner&subdirectory=contrib/runners/python_runner", # NOQA
9793
"hg+https://hg.repo/some_pkg.git#egg=SomePackageHq",
9894
"svn+svn://svn.repo/some_pkg/trunk/@ma-branch#egg=SomePackageSvn",

0 commit comments

Comments
 (0)