Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit c5b254c

Browse filesBrowse files
committed
add watchdog tutorial
1 parent 707a12b commit c5b254c
Copy full SHA for c5b254c

File tree

Expand file treeCollapse file tree

5 files changed

+289
-0
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

5 files changed

+289
-0
lines changed
Open diff view settings
Collapse file
+26Lines changed: 26 additions & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# [How to Create a Watchdog in Python](https://www.thepythoncode.com/article/create-a-watchdog-in-python)
2+
To run this:
3+
- `pip3 install -r requirements.txt`
4+
- `python3 controller.py --help`
5+
**Output:**
6+
```
7+
usage: controller.py [-h] [-d WATCH_DELAY] [-r] [-p PATTERN] [--watch-directories] path
8+
9+
Watchdog script for watching for files & directories' changes
10+
11+
positional arguments:
12+
path
13+
14+
optional arguments:
15+
-h, --help show this help message and exit
16+
-d WATCH_DELAY, --watch-delay WATCH_DELAY
17+
Watch delay, default is 1
18+
-r, --recursive Whether to recursively watch for the path's children, default is False
19+
-p PATTERN, --pattern PATTERN
20+
Pattern of files to watch, default is .txt,.trc,.log
21+
--watch-directories Whether to watch directories, default is True
22+
```
23+
- For example, watching the path `E:\watchdog` recursively for log and text files:
24+
```
25+
python controller.py E:\watchdog --recursive -p .txt,.log
26+
```
Collapse file
+31Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import datetime
2+
from pygtail import Pygtail
3+
4+
# Loading the package called re from the RegEx Module in order to work with Regular Expressions
5+
import re
6+
7+
8+
class FileChecker:
9+
def __init__(self, exceptionPattern):
10+
self.exceptionPattern = exceptionPattern
11+
12+
def checkForException(self, event, path):
13+
# Get current date and time according to the specified format.
14+
now = (datetime.datetime.now()).strftime("%Y-%m-%d %H:%M:%S")
15+
# Read the lines of the file (specified in the path) that have not been read yet
16+
# Meaning by that it will start from the point where it was last stopped.
17+
for num, line in enumerate(Pygtail(path), 1):
18+
# Remove leading and trailing whitespaces including newlines.
19+
line = line.strip()
20+
# Return all non-overlapping matches of the values specified in the Exception Pattern.
21+
# The line is scanned from left to right and matches are returned in the oder found.
22+
if line and any(re.findall('|'.join(self.exceptionPattern), line, flags=re.I | re.X)):
23+
# Observation Detected
24+
type = 'observation'
25+
msg = f"{now} -- {event.event_type} -- File: {path} -- Observation: {line}"
26+
yield type, msg
27+
elif line:
28+
# No Observation Detected
29+
type = 'msg'
30+
msg = f"{now} -- {event.event_type} -- File: {path}"
31+
yield type, msg
Collapse file
+22Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Application configuration File
2+
################################
3+
4+
# Directory To Watch, If not specified, the following value will be considered explicitly.
5+
WATCH_DIRECTORY = "C:\\SCRIPTS"
6+
7+
# Delay Between Watch Cycles In Seconds
8+
WATCH_DELAY = 1
9+
10+
# Check The WATCH_DIRECTORY and its children
11+
WATCH_RECURSIVELY = False
12+
13+
# whether to watch for directory events
14+
DO_WATCH_DIRECTORIES = True
15+
16+
# Patterns of the files to watch
17+
WATCH_PATTERN = '.txt,.trc,.log'
18+
19+
LOG_FILES_EXTENSIONS = ('.txt', '.log', '.trc')
20+
21+
# Patterns for observations
22+
EXCEPTION_PATTERN = ['EXCEPTION', 'FATAL', 'ERROR']
Collapse file
+208Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
# The Observer watches for any file change and then dispatches the respective events to an event handler.
2+
from watchdog.observers import Observer
3+
# The event handler will be notified when an event occurs.
4+
from watchdog.events import FileSystemEventHandler
5+
import time
6+
import config
7+
import os
8+
from checker import FileChecker
9+
import datetime
10+
from colorama import Fore, Style, init
11+
12+
init()
13+
14+
GREEN = Fore.GREEN
15+
BLUE = Fore.BLUE
16+
RED = Fore.RED
17+
YELLOW = Fore.YELLOW
18+
19+
event2color = {
20+
"created": GREEN,
21+
"modified": BLUE,
22+
"deleted": RED,
23+
"moved": YELLOW,
24+
}
25+
26+
27+
def print_with_color(s, color=Fore.WHITE, brightness=Style.NORMAL, **kwargs):
28+
"""Utility function wrapping the regular `print()` function
29+
but with colors and brightness"""
30+
print(f"{brightness}{color}{s}{Style.RESET_ALL}", **kwargs)
31+
32+
33+
# Class that inherits from FileSystemEventHandler for handling the events sent by the Observer
34+
class LogHandler(FileSystemEventHandler):
35+
36+
def __init__(self, watchPattern, exceptionPattern, doWatchDirectories):
37+
self.watchPattern = watchPattern
38+
self.exceptionPattern = exceptionPattern
39+
self.doWatchDirectories = doWatchDirectories
40+
# Instantiate the checker
41+
self.fc = FileChecker(self.exceptionPattern)
42+
43+
def on_any_event(self, event):
44+
now = (datetime.datetime.now()).strftime("%Y-%m-%d %H:%M:%S")
45+
# print("event happened:", event)
46+
# To Observe files only not directories
47+
if not event.is_directory:
48+
# To cater for the on_move event
49+
path = event.src_path
50+
if hasattr(event, 'dest_path'):
51+
path = event.dest_path
52+
# Ensure that the file extension is among the pre-defined ones.
53+
if path.endswith(self.watchPattern):
54+
msg = f"{now} -- {event.event_type} -- File: {path}"
55+
if event.event_type in ('modified', 'created', 'moved'):
56+
# check for exceptions in log files
57+
if path.endswith(config.LOG_FILES_EXTENSIONS):
58+
for type, msg in self.fc.checkForException(event=event, path=path):
59+
print_with_color(
60+
msg, color=event2color[event.event_type], brightness=Style.BRIGHT)
61+
else:
62+
print_with_color(
63+
msg, color=event2color[event.event_type])
64+
else:
65+
print_with_color(msg, color=event2color[event.event_type])
66+
elif self.doWatchDirectories:
67+
msg = f"{now} -- {event.event_type} -- Folder: {event.src_path}"
68+
print_with_color(msg, color=event2color[event.event_type])
69+
70+
def on_modified(self, event):
71+
pass
72+
73+
def on_deleted(self, event):
74+
pass
75+
76+
def on_created(self, event):
77+
pass
78+
79+
def on_moved(self, event):
80+
pass
81+
82+
83+
class LogWatcher:
84+
# Initialize the observer
85+
observer = None
86+
# Initialize the stop signal variable
87+
stop_signal = 0
88+
89+
# The observer is the class that watches for any file system change and then dispatches the event to the event handler.
90+
def __init__(self, watchDirectory, watchDelay, watchRecursively, watchPattern, doWatchDirectories, exceptionPattern):
91+
# Initialize variables in relation
92+
self.watchDirectory = watchDirectory
93+
self.watchDelay = watchDelay
94+
self.watchRecursively = watchRecursively
95+
self.watchPattern = watchPattern
96+
self.doWatchDirectories = doWatchDirectories
97+
self.exceptionPattern = exceptionPattern
98+
99+
# Create an instance of watchdog.observer
100+
self.observer = Observer()
101+
# The event handler is an object that will be notified when something happens to the file system.
102+
self.event_handler = LogHandler(
103+
watchPattern, exceptionPattern, self.doWatchDirectories)
104+
105+
def schedule(self):
106+
print("Observer Scheduled:", self.observer.name)
107+
# Call the schedule function via the Observer instance attaching the event
108+
self.observer.schedule(
109+
self.event_handler, self.watchDirectory, recursive=self.watchRecursively)
110+
111+
def start(self):
112+
print("Observer Started:", self.observer.name)
113+
self.schedule()
114+
# Start the observer thread and wait for it to generate events
115+
now = (datetime.datetime.now()).strftime("%Y-%m-%d %H:%M:%S")
116+
msg = f"Observer: {self.observer.name} - Started On: {now}"
117+
print(msg)
118+
119+
msg = (
120+
f"Watching {'Recursively' if self.watchRecursively else 'Non-Recursively'}: {self.watchPattern}"
121+
f" -- Folder: {self.watchDirectory} -- Every: {self.watchDelay}(sec) -- For Patterns: {self.exceptionPattern}"
122+
)
123+
print(msg)
124+
self.observer.start()
125+
126+
def run(self):
127+
print("Observer is running:", self.observer.name)
128+
self.start()
129+
try:
130+
while True:
131+
time.sleep(self.watchDelay)
132+
133+
if self.stop_signal == 1:
134+
print(
135+
f"Observer stopped: {self.observer.name} stop signal:{self.stop_signal}")
136+
self.stop()
137+
break
138+
except:
139+
self.stop()
140+
self.observer.join()
141+
142+
def stop(self):
143+
print("Observer Stopped:", self.observer.name)
144+
145+
now = (datetime.datetime.now()).strftime("%Y-%m-%d %H:%M:%S")
146+
msg = f"Observer: {self.observer.name} - Stopped On: {now}"
147+
print(msg)
148+
self.observer.stop()
149+
self.observer.join()
150+
151+
def info(self):
152+
info = {
153+
'observerName': self.observer.name,
154+
'watchDirectory': self.watchDirectory,
155+
'watchDelay': self.watchDelay,
156+
'watchRecursively': self.watchRecursively,
157+
'watchPattern': self.watchPattern,
158+
}
159+
return info
160+
161+
162+
def is_dir_path(path):
163+
"""Utility function to check whether a path is an actual directory"""
164+
if os.path.isdir(path):
165+
return path
166+
else:
167+
raise NotADirectoryError(path)
168+
169+
170+
if __name__ == "__main__":
171+
import argparse
172+
parser = argparse.ArgumentParser(
173+
description="Watchdog script for watching for files & directories' changes")
174+
parser.add_argument("path",
175+
default=config.WATCH_DIRECTORY,
176+
type=is_dir_path,
177+
)
178+
parser.add_argument("-d", "--watch-delay",
179+
help=f"Watch delay, default is {config.WATCH_DELAY}",
180+
default=config.WATCH_DELAY,
181+
type=int,
182+
)
183+
parser.add_argument("-r", "--recursive",
184+
action="store_true",
185+
help=f"Whether to recursively watch for the path's children, default is {config.WATCH_RECURSIVELY}",
186+
default=config.WATCH_RECURSIVELY,
187+
)
188+
parser.add_argument("-p", "--pattern",
189+
help=f"Pattern of files to watch, default is {config.WATCH_PATTERN}",
190+
default=config.WATCH_PATTERN,
191+
)
192+
parser.add_argument("--watch-directories",
193+
action="store_true",
194+
help=f"Whether to watch directories, default is {config.DO_WATCH_DIRECTORIES}",
195+
default=config.DO_WATCH_DIRECTORIES,
196+
)
197+
# parse the arguments
198+
args = parser.parse_args()
199+
# define & launch the log watcher
200+
log_watcher = LogWatcher(
201+
watchDirectory=args.path,
202+
watchDelay=args.watch_delay,
203+
watchRecursively=args.recursive,
204+
watchPattern=tuple(args.pattern.split(",")),
205+
doWatchDirectories=args.watch_directories,
206+
exceptionPattern=config.EXCEPTION_PATTERN,
207+
)
208+
log_watcher.run()
Collapse file
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Pygtail==0.11.1
2+
watchdog==2.1.1

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.