|  | 
|  | 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