|
| 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() |
0 commit comments