0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ファイル変更をリアルタイム監視&ログ出力するツールを作ってみた

Posted at

はじめに

初めましての人もそうでない人もこんにちは!
最近コーディング中に「あれ、いつこのファイル変更したっけ?」「どの部分を修正したか忘れちゃった...」なんて経験ありませんか?

そんな悩みを解決するべく、今回はPythonでファイルの変更をリアルタイムで監視して、詳細なログを出力するツールを作成してみました!

ファイルの作成・削除・変更を文字単位で追跡できる超便利なツールになっているので、ぜひ最後までご覧ください!

今回作るもの

  • ファイルの作成・削除・変更をリアルタイムで監視
  • 文字の追加・削除・置換を詳細にログ出力
  • CSVファイルで変更履歴を管理
  • 対応ファイル形式:.py, .txt, .js, .html, .css, .json

ディレクトリ構成

/
│
├── file_monitor.py      # メインのプログラム
├── logs/
│   └── logs.csv        # ログ収集用ファイル(自動生成)
└── files/              # 監視対象フォルダ(自動生成)
    ├── hogehoge.py
    ├── hogehoge.txt
    └── ...

環境構築

必要なライブラリをインストールします:

pip install watchdog pytz

コーディング

メインのプログラムファイル file_monitor.py を作成します:

import csv
import os
from datetime import datetime
import pytz
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler

class FileChangeHandler(FileSystemEventHandler):
    def __init__(self, logger):
        self.logger = logger
        self.file_contents = {}
    
    def on_modified(self, event):
        if not event.is_directory and event.src_path.endswith(('.py', '.txt', '.js', '.html', '.css', '.json')):
            filename = os.path.basename(event.src_path)
            try:
                with open(event.src_path, 'r', encoding='utf-8') as f:
                    new_content = f.read()
                old_content = self.file_contents.get(event.src_path, '')
                if old_content != new_content:
                    self.compare_content(filename, old_content, new_content)
                    self.file_contents[event.src_path] = new_content
            except:
                pass
    
    def compare_content(self, filename, old_content, new_content):
        if len(new_content) > len(old_content):
            added_text = new_content[len(old_content):]
            line_num, col_num = self.get_position(old_content, len(old_content))
            for i, char in enumerate(added_text):
                if char == '\n':
                    self.logger.log(filename, '入力', "追加: '改行'", line_num, col_num + i + 1)
                    line_num += 1
                    col_num = 0
                    i = -1
                else:
                    self.logger.log(filename, '入力', f"追加: '{char}'", line_num, col_num + i + 1)
        elif len(new_content) < len(old_content):
            deleted_text = old_content[len(new_content):]
            line_num, col_num = self.get_position(new_content, len(new_content))
            for char in deleted_text:
                self.logger.log(filename, '削除', f"削除: '{char}'" if char != '\n' else "削除: '改行'", line_num, col_num + 1)
        else:
            for i, (old_char, new_char) in enumerate(zip(old_content, new_content)):
                if old_char != new_char:
                    line_num, col_num = self.get_position(old_content, i)
                    self.logger.log(filename, '置換', f"'{old_char}''{new_char}'", line_num, col_num)
    
    def get_position(self, content, index):
        if index >= len(content):
            lines = content.split('\n')
            return len(lines), len(lines[-1]) + 1 if lines else 1
        text_before = content[:index]
        lines = text_before.split('\n')
        return len(lines), len(lines[-1]) + 1
    
    def on_created(self, event):
        if not event.is_directory:
            self.logger.log(os.path.basename(event.src_path), 'ファイル作成', f"ファイル作成: {os.path.basename(event.src_path)}", 0, 0)
            try:
                with open(event.src_path, 'r', encoding='utf-8') as f:
                    self.file_contents[event.src_path] = f.read()
            except:
                pass
    
    def on_deleted(self, event):
        if not event.is_directory:
            self.logger.log(os.path.basename(event.src_path), 'ファイル削除', f"ファイル削除: {os.path.basename(event.src_path)}", 0, 0)
            self.file_contents.pop(event.src_path, None)

class FileLogger:
    def __init__(self):
        os.makedirs("logs", exist_ok=True)
        os.makedirs("files", exist_ok=True)
        
        with open("logs/logs.csv", 'w', newline='', encoding='utf-8') as f:
            csv.writer(f).writerow(['時刻', 'ファイル名', '操作', '内容', '', ''])
    
    def log(self, filename, action, content, line=0, column=0):
        timestamp = datetime.now(pytz.timezone('Asia/Tokyo')).strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
        with open("logs/logs.csv", 'a', newline='', encoding='utf-8') as f:
            csv.writer(f).writerow([timestamp, filename, action, content, line, column])
    
    def start_monitor(self):
        event_handler = FileChangeHandler(self)
        
        for root, dirs, files in os.walk("files"):
            for file in files:
                if file.endswith(('.py', '.txt', '.js', '.html', '.css', '.json')):
                    filepath = os.path.join(root, file)
                    try:
                        with open(filepath, 'r', encoding='utf-8') as f:
                            event_handler.file_contents[filepath] = f.read()
                    except:
                        pass
        
        observer = Observer()
        observer.schedule(event_handler, "files", recursive=True)
        observer.start()
        
        print("ファイル監視を開始しました。Ctrl+Cで終了します。")
        print("監視対象フォルダ: ./files/")
        print("ログファイル: ./logs/logs.csv")
        
        try:
            import time
            while True:
                time.sleep(1)
        except KeyboardInterrupt:
            observer.stop()
            print("\nファイル監視を終了しました。")
        observer.join()

if __name__ == "__main__":
    FileLogger().start_monitor()

実行してみた

プログラムを実行します:

python3 file_monitor.py

実行すると以下のようなメッセージが表示されます:

ファイル監視を開始しました。Ctrl+Cで終了します。
監視対象フォルダ: ./files/
ログファイル: ./logs/logs.csv

files/ フォルダ内で以下のようなテストファイルを作成・編集してみました:

test.py を作成

print("Hello World")

sample.txt を編集

最初のテキスト
追加のテキスト

ログ出力結果

logs/logs.csv に以下のようなログが出力されました:

時刻 ファイル名 操作 内容
2025-05-30 15:30:01.123 test.py ファイル作成 ファイル作成: test.py 0 0
2025-05-30 15:30:15.456 test.py 入力 追加: 'p' 1 1
2025-05-30 15:30:15.457 test.py 入力 追加: 'r' 1 2
2025-05-30 15:30:15.458 test.py 入力 追加: 'i' 1 3
... ... ... ... ... ...

特徴・機能

リアルタイム監視

  • ファイルの変更を瞬時に検知
  • 複数ファイルの同時監視が可能

詳細なログ記録

  • 文字単位での変更追跡
  • 行番号・列番号の正確な位置情報
  • 時系列管理

多くの操作ログの収集に対応

  • 文字追加: 新しく入力された文字を記録
  • 文字削除: 削除された文字を記録
  • 文字置換: 変更前後の文字を記録
  • ファイル作成/削除: ファイル操作も記録

おわりに

今回は Python と watchdog ライブラリを使って、ファイル変更をリアルタイムで監視するツールを作成しました!
文字単位での詳細な変更追跡ができるので、コーディング中の変更履歴をしっかりと記録できます。
開発効率の向上やデバッグ支援にぜひ活用してみてください!

皆さんも試してみてはいかがでしょうか?
今回の記事はいかがだったでしょうか!
またどこかの記事でお会いしましょう!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?