文件监控
# 文件监控
watchdog 库实时监控文件变化(创建/修改/删除)、触发回调、配置文件热加载、日志采集、Shell inotifywait。
# 监控的两种模式:轮询 vs 事件驱动
操作系统提供了两种文件监控机制:
- 轮询(Polling):定时
os.stat()检查文件元数据。开销大(CPU 空转),有检测延迟(轮询间隔),但跨平台一致。 - 事件驱动(Event-driven):内核通知文件变更。零 CPU 开销、实时响应。Linux 用
inotify(文件级),macOS 用FSEvents(目录级),Windows 用ReadDirectoryChangesW。
watchdog 的设计哲学:自动选择平台最优事件驱动后端——Linux→inotify、macOS→FSEvents、Windows→ReadDirectoryChangesW。只在无原生事件系统时回退到轮询。
FSEvents 的局限性:macOS 的 FSEvents 是目录级别的——它告诉你"某个目录下发生了变更",但不指定具体是哪个文件。watchdog 在 macOS 上需要额外通过
os.scandir来定位变更文件——所以在 macOS 上监控大量文件时会比 Linux 稍慢。
# 一、watchdog——Python 文件监控
pip install watchdog
1
# 1.1 基础监控——文件变更触发回调
Observer 是调度器(内部维护一个线程池),schedule() 注册"监控目录 + 事件处理器"对。每个目录的监控运行在独立线程中,互不阻塞。
#!/usr/bin/env python3
"""监控目录文件变化——Observer 多线程架构,每个 monitored dir 一个线程"""
import time
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
class Watcher(FileSystemEventHandler):
def on_created(self, event):
print(f"📄 新建: {event.src_path}")
def on_modified(self, event):
print(f"✏️ 修改: {event.src_path}")
def on_deleted(self, event):
print(f"🗑️ 删除: {event.src_path}")
def on_moved(self, event):
print(f"📦 移动: {event.src_path} → {event.dest_path}")
if __name__ == '__main__':
observer = Observer()
observer.schedule(Watcher(), '.', recursive=True)
observer.start()
print("👀 监控中 ... Ctrl+C 退出")
try:
while True: time.sleep(1)
except KeyboardInterrupt:
observer.stop()
observer.join() # 等待所有监控线程结束
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 1.2 实战:配置文件热加载
为什么要热加载? 传统方式修改配置 → 重启服务 → 短暂的不可用窗口。热加载消除这个窗口——监控到配置文件变更后,仅重新解析配置并更新服务的运行时状态(如重建连接池、刷新缓存),不中断正在处理的请求。
#!/usr/bin/env python3
import json, time
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
class ConfigReloader(FileSystemEventHandler):
def __init__(self, config_path, callback):
self.config_path = config_path
self.callback = callback
def on_modified(self, event):
# 编辑器保存时可能触发多次 modify(如 vim 的 swap 文件)
# 所以精确判断 event.src_path 是否匹配
if event.src_path.endswith(self.config_path):
print(f"[{time.ctime()}] 配置变更 ...")
with open(self.config_path) as f:
new_config = json.load(f)
self.callback(new_config)
def on_config_change(config):
print(f" DB Host: {config.get('db_host')}, Port: {config.get('port')}")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 1.3 实战:日志目录采集
场景:应用按小时滚动日志(app_20250610_14.log, app_20250610_15.log, ...),采集管道需要自动发现新文件并开始 tail。
#!/usr/bin/env python3
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
import os
class LogTailer(FileSystemEventHandler):
def __init__(self, log_dir):
self.watched = set()
# 启动时采集已有日志文件
for f in os.listdir(log_dir):
if f.endswith('.log'):
self.watched.add(f)
def on_created(self, event):
if not event.is_directory and event.src_path.endswith('.log'):
fname = os.path.basename(event.src_path)
if fname not in self.watched:
self.watched.add(fname)
print(f"📄 新日志: {fname} ——已加入采集")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 二、Shell——inotifywait
Linux 特有的 inotify 子系统让用户空间直接订阅内核文件事件。inotifywait 封装了系统调用,适合在 shell 脚本中使用——管道后接 while read 处理每一个事件。
#!/bin/bash
# apt install inotify-tools
# -m 持续监控(不退出) -r 递归子目录
inotifywait -m -r /watch/dir -e create -e modify -e delete |
while read dir action file; do
echo "[$(date)] $action $dir$file"
done
# 配置文件热加载
inotifywait -m -e modify /etc/app/config.yaml |
while read f; do
systemctl reload myapp
done
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
上次更新: 2026/06/17, 12:47:39