Python学习日志(四):实例

Python 实例

引言

在系统管理和运维工作中,监控文件系统的变化是一项常见且重要的任务。无论是追踪配置文件修改、检测关键日志更新,还是发现异常文件写入,都需要及时了解文件系统的动态。手动监控既不高效也不可靠,自动化脚本是解决这类问题的理想方案。本日志将详细介绍一个使用 Python 编写的文件系统监控脚本,它能够持续监视指定目录下的文件创建、修改和删除事件,并将这些事件记录到日志文件中,同时提供一定的异常处理能力。

技术选型与库介绍

为了实现高效的文件系统监控,我们选择使用 Python 的 watchdog 库。watchdog 是一个跨平台的库,它利用操作系统提供的底层文件系统事件通知机制(如 Linux 的 inotify, macOS 的 FSEvents, Windows 的 ReadDirectoryChangesW),能够实时、高效地捕获文件系统事件,而无需轮询目录,大大降低了资源消耗。

  • 安装 watchdog 在开始编写脚本前,需要确保环境中已安装 watchdog 库。可以通过 pip 安装:

    bash 复制代码
    pip install watchdog

脚本功能设计

本脚本的核心功能是监控指定目录及其子目录中的所有文件事件,并将事件详细信息记录到日志文件中。具体功能包括:

  1. 监控事件类型:
    • 文件/目录创建 (FileCreatedEvent)
    • 文件/目录修改 (FileModifiedEvent)
    • 文件/目录移动 (FileMovedEvent)
    • 文件/目录删除 (FileDeletedEvent)
    • 目录创建 (DirCreatedEvent)
    • 目录移动 (DirMovedEvent)
    • 目录删除 (DirDeletedEvent)
  2. 日志记录: 将事件发生的类型、时间、文件路径等信息以清晰易读的格式记录到指定的日志文件中。
  3. 命令行参数: 允许用户通过命令行指定要监控的目录路径和日志文件路径。
  4. 异常处理: 对可能出现的异常(如目录不存在、权限不足、日志写入失败等)进行捕获和处理,提供友好的错误信息。
  5. 安全退出: 支持通过键盘中断 (Ctrl+C) 安全地停止监控。

完整代码实现

python 复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
文件系统监控脚本 (filesystem_monitor.py)

功能:持续监控指定目录下的文件系统事件(创建、修改、删除、移动),并将事件记录到日志文件。
依赖:watchdog 库 (pip install watchdog)

使用示例:
    python filesystem_monitor.py /path/to/watch /path/to/monitor.log
"""

import argparse
import logging
import time
import os
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler, LoggingEventHandler

def setup_logger(log_file):
    """
    配置日志记录器。

    参数:
        log_file (str): 日志文件路径。

    返回:
        logging.Logger: 配置好的日志记录器对象。
    """
    # 创建日志记录器
    logger = logging.getLogger('FileSystemMonitor')
    logger.setLevel(logging.INFO)  # 设置日志级别为 INFO

    # 创建文件处理器,将日志写入文件
    file_handler = logging.FileHandler(log_file)
    file_handler.setLevel(logging.INFO)

    # 创建格式化器
    formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
    file_handler.setFormatter(formatter)

    # 将处理器添加到记录器
    logger.addHandler(file_handler)

    return logger

class CustomEventHandler(FileSystemEventHandler):
    """
    自定义文件系统事件处理器,继承自 watchdog.events.FileSystemEventHandler。
    重写事件处理方法以定制日志内容。
    """

    def __init__(self, logger):
        super().__init__()
        self.logger = logger

    def on_any_event(self, event):
        """
        捕获任何文件系统事件。通常我们不直接使用这个,而是使用更具体的事件方法。
        但这里我们选择记录所有事件类型。
        """
        # 在实际应用中,您可能更倾向于使用下面具体的 on_created, on_modified 等方法
        # 这里为了演示捕获所有事件,使用 on_any_event
        if event.is_directory:
            object_type = "目录"
        else:
            object_type = "文件"

        event_type = event.event_type
        event_path = event.src_path

        # 对于移动事件,额外记录目标路径
        if event_type == 'moved':
            event_path = f"{event.src_path} -> {event.dest_path}"

        log_message = f"事件类型: {event_type}, 对象类型: {object_type}, 路径: {event_path}"
        self.logger.info(log_message)

def main():
    """
    主函数,解析命令行参数,启动监控。
    """
    # 解析命令行参数
    parser = argparse.ArgumentParser(description='监控指定目录的文件系统事件并记录日志。')
    parser.add_argument('watch_dir', type=str, help='要监控的目录路径')
    parser.add_argument('log_file', type=str, help='日志文件路径')
    args = parser.parse_args()

    # 检查监控目录是否存在
    if not os.path.exists(args.watch_dir):
        print(f"错误: 目录 '{args.watch_dir}' 不存在!")
        return
    if not os.path.isdir(args.watch_dir):
        print(f"错误: '{args.watch_dir}' 不是一个目录!")
        return

    # 检查日志文件目录是否存在,不存在则尝试创建
    log_dir = os.path.dirname(args.log_file)
    if log_dir and not os.path.exists(log_dir):
        try:
            os.makedirs(log_dir)
        except OSError as e:
            print(f"无法创建日志目录 '{log_dir}': {e}")
            return

    # 设置日志记录器
    try:
        logger = setup_logger(args.log_file)
    except (IOError, PermissionError) as e:
        print(f"无法初始化日志文件 '{args.log_file}': {e}")
        return

    logger.info(f"开始监控目录: {args.watch_dir}")
    logger.info(f"日志记录到文件: {args.log_file}")

    # 创建事件处理器和观察者
    event_handler = CustomEventHandler(logger)
    observer = Observer()
    observer.schedule(event_handler, args.watch_dir, recursive=True)  # recursive=True 监控子目录

    # 启动观察者
    observer.start()
    logger.info("监控器已启动。按 Ctrl+C 停止。")

    try:
        # 主循环,保持程序运行
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        # 捕获 Ctrl+C 信号
        observer.stop()
        logger.info("监控器已停止。")

    # 等待观察者线程结束
    observer.join()

if __name__ == '__main__':
    main()

代码详解

  1. 导入模块 (import ...):

    • argparse: 用于解析命令行参数。
    • logging: Python 标准日志库,用于记录事件信息。
    • time: 提供 sleep 函数,用于主循环等待。
    • os: 提供路径检查、目录创建等操作系统接口。
    • watchdog.observers.Observer: 观察者对象,负责监控目录和分发事件。
    • watchdog.events.FileSystemEventHandler: 文件系统事件处理器的基类,我们需要继承它来实现自定义处理逻辑。LoggingEventHandler 是 watchdog 自带的一个简单日志处理器,但我们这里选择自定义以实现更灵活的日志格式。
  2. setup_logger(log_file) 函数:

    • 这是配置日志记录器的辅助函数。
    • 创建一个名为 'FileSystemMonitor' 的日志记录器 (logger)。
    • 设置日志级别为 INFO,表示记录所有 INFO 级别及以上的消息(如 WARNING, ERROR, CRITICAL)。
    • 创建一个 FileHandler 将日志写入到指定的 log_file
    • 定义一个格式化器 (Formatter),指定日志条目的格式:时间戳 (%(asctime)s)、日志级别 (%(levelname)s)、消息内容 (%(message)s),并设置时间戳的格式 (datefmt='%Y-%m-%d %H:%M:%S')。
    • 将格式化器应用到文件处理器,并将文件处理器添加到日志记录器。
    • 返回配置好的日志记录器对象。
  3. CustomEventHandler 类:

    • 继承自 FileSystemEventHandler
    • __init__ 中接收一个 logger 对象,以便在事件处理时记录日志。
    • 重写了 on_any_event(self, event) 方法。这个方法会在 任何 文件系统事件发生时被调用。
    • 在方法内部:
      • 判断事件对象是文件 (event.is_directory == False) 还是目录 (event.is_directory == True)。
      • 获取事件类型 (event.event_type),如 'created', 'modified', 'deleted', 'moved')。
      • 获取事件源路径 (event.src_path)。
      • 对于 'moved' 事件,源路径是原位置,目标路径是 event.dest_path,日志中同时记录两者。
      • 组合日志信息字符串 (log_message)。
      • 使用 logger.info(log_message) 记录事件信息到日志文件。
  4. main() 函数:

    • 参数解析: 使用 argparse 定义两个位置参数 watch_dir (要监控的目录) 和 log_file (日志文件路径)。
    • 路径验证:
      • 检查 watch_dir 是否存在 (os.path.exists) 并且是一个目录 (os.path.isdir)。如果不满足则打印错误并退出。
      • 检查 log_file 所在的目录 (log_dir = os.path.dirname(args.log_file)) 是否存在。如果不存在,尝试使用 os.makedirs 创建它。如果创建失败(如权限问题),打印错误并退出。
    • 日志初始化: 调用 setup_logger 创建日志记录器。捕获可能的文件写入异常 (IOError, PermissionError)。
    • 启动日志: 在日志中记录开始监控的目录和日志文件路径。
    • 创建处理器和观察者:
      • 实例化自定义事件处理器 CustomEventHandler,传入配置好的 logger
      • 创建 Observer 对象。
      • 调用 observer.schedule(event_handler, path, recursive=True) 将事件处理器注册到要监控的路径 (args.watch_dir)。recursive=True 表示监控该目录下的所有子目录。
    • 启动监控: 调用 observer.start() 启动观察者线程。记录启动信息。
    • 主循环与退出:
      • 使用 while True: time.sleep(1) 保持主线程运行。
      • 捕获 KeyboardInterrupt 异常(用户按下 Ctrl+C)。
      • 在异常处理中,调用 observer.stop() 停止观察者线程,并在日志中记录停止信息。
      • 调用 observer.join() 等待观察者线程完全结束。
  5. 入口点 (if __name__ == '__main__':):

    • 这是 Python 脚本的标准入口点,当直接运行此脚本时,会调用 main() 函数。

使用说明

  1. 运行脚本:

    bash 复制代码
    python filesystem_monitor.py /path/to/your/directory /path/to/your/logfile.log

    /path/to/your/directory 替换为你想监控的目录路径,将 /path/to/your/logfile.log 替换为你希望保存日志的文件路径。

  2. 观察日志:

    • 脚本启动后,它会持续监控指定目录。

    • 任何在该目录及其子目录下发生的文件创建、修改、删除、移动事件都会被捕获。

    • 事件详情会被记录到指定的日志文件中。日志条目示例:

      复制代码
      2023-10-27 14:30:15 - INFO - 事件类型: created, 对象类型: 文件, 路径: /watched_dir/newfile.txt
      2023-10-27 14:31:22 - INFO - 事件类型: modified, 对象类型: 文件, 路径: /watched_dir/existing.log
      2023-10-27 14:32:50 - INFO - 事件类型: moved, 对象类型: 文件, 路径: /watched_dir/oldname.txt -> /watched_dir/newname.txt
      2023-10-27 14:33:05 - INFO - 事件类型: deleted, 对象类型: 目录, 路径: /watched_dir/empty_folder
  3. 停止脚本:

    • 在命令行终端中按下 Ctrl+C 组合键,脚本会安全停止监控并退出。

潜在问题与改进方向

  1. 性能: 监控大量文件或深度嵌套的目录结构时,可能会对系统性能产生轻微影响。watchdog 本身效率很高,但日志写入频率过高可能成为瓶颈。
  2. 事件风暴: 如果短时间内发生大量文件事件(如编译项目、复制大量文件),日志文件可能会迅速增长。可以考虑:
    • 增加日志级别过滤(例如只记录 WARNING 或更高级别的事件,但这样会丢失信息)。
    • 对日志进行轮转(Rotate),使用 logging.handlers.RotatingFileHandlerTimedRotatingFileHandler
    • 对特定事件类型进行聚合后再记录(实现较复杂)。
  3. 安全性:
    • 脚本运行需要足够的权限访问目标目录和写入日志文件。
    • 监控的日志文件本身也是一个文件,修改它也会触发事件。这可能导致循环记录(记录修改日志文件的事件)。脚本本身不会修改日志文件,但外部修改会记录。这是一个需要注意的点,通常可以接受。
  4. 功能扩展:
    • 邮件/通知: 集成 smtplib 或其他通知库(如 Slack API),当检测到特定关键文件被修改或删除时发送警报。
    • 配置文件: 使用 configparserjson 模块将监控目录、日志路径、忽略规则等配置项放在外部文件中。
    • 忽略规则: 继承 FileSystemEventHandler 并重写 ignore_directoriesignore_patterns 属性,或者更精细地在 on_any_event 方法中加入过滤逻辑,忽略特定文件类型(如 .tmp, .bak)或目录。
    • 事件处理: 不仅仅是记录日志,可以触发其他操作,例如:
      • 检测到新的日志文件时,自动进行解析或发送到日志服务器。
      • 检测到配置文件修改时,自动重启相关服务(需谨慎,避免循环重启)。
      • 检测到可疑文件(如 .php 在非 Web 目录)写入时,进行告警或隔离。
    • 监控多个目录: 修改脚本支持同时监控多个目录。可以通过多次调用 observer.schedule 来实现。
    • 更详细的日志: 记录事件来源进程(Linux 下可通过 /proc 获取,跨平台较复杂)、文件大小变化等。
  5. 文件内容哈希(可选): 对于关键文件,在记录修改事件时,可以计算并记录文件内容的哈希值(如 MD5, SHA1),以便后续验证文件是否被篡改。注意这会影响性能,尤其是大文件频繁修改时。

总结

本次日志介绍了一个实用的 Python 文件系统监控脚本。它利用 watchdog 库实现了对指定目录的高效、实时监控,捕获文件创建、修改、删除和移动事件,并将这些事件详细记录到日志文件中。脚本具备命令行参数解析、路径验证、异常处理和安全的退出机制。这个脚本是学习 Python 应用于实际运维场景的一个良好示例,涵盖了文件操作、事件处理、日志记录、命令行交互等多个方面。通过阅读代码和运行实践,可以加深对 Python 在这些领域应用的理解。脚本本身已经可用,也提供了丰富的扩展思路以满足更复杂的运维监控需求。

相关推荐
sealaugh322 小时前
react native(学习笔记第二课) 英语打卡微应用(1)-开始构建
笔记·学习·react native
夜瞬2 小时前
NLP学习笔记03:文本分类——从 TF-IDF 到 BERT
笔记·学习·自然语言处理
Fanfanaas2 小时前
Linux 系统编程 进程篇 (二)
linux·运维·服务器·c语言·开发语言·学习
克里斯蒂亚诺·罗纳尔达2 小时前
智能体学习22——智能体间通信(A2A)
人工智能·学习·ai
2301_764150562 小时前
Redis如何控制只读从库的安全_配置replica-read-only防止从节点数据被意外篡改
jvm·数据库·python
油丶酸萝卜别吃2 小时前
高效处理数组差异:JS中新增、删除、交集的最优解(Set实现)
开发语言·前端·javascript
HoneyMoose2 小时前
Npmp 安装时候提示警告: error (ERR_INVALID_THIS)
开发语言
DaqunChen2 小时前
SQL如何检测分组内是否存在满足条件的数据_EXISTS结合分组
jvm·数据库·python
gskyi2 小时前
时间格式化神器:智能显示相对时间
开发语言·javascript·ecmascript