fsnotify는 파일 시스템 및 디렉터리의 변경 사항을 모니터링하고 변경이 발생할 때 애플리케이션에 알림을 제공할 수 있는 Go로 작성된 파일 시스템 알림 라이브러리입니다. 이는 크로스 플랫폼 운영을 지원하며 Linux, macOS, Windows와 같은 다양한 운영 체제에서 실행될 수 있습니다. fsnotify는 Linux의 inotify, macOS의 FSEvents, 그리고 Windows의 ReadDirectoryChangesW와 같은 다양한 운영 체제의 파일 시스템 알림 메커니즘을 활용합니다.

Go 1.17 이상의 버전을 필요로 하며, 자세한 문서는 https://pkg.go.dev/github.com/fsnotify/fsnotify에서 확인할 수 있습니다.

다양한 운영 체제 지원:

백엔드 운영 체제 상태
inotify Linux 지원됨
kqueue BSD, macOS 지원됨
ReadDirectoryChangesW Windows 지원됨
FEN illumos 지원됨
fanotify Linux 5.9+ 아직 지원되지 않음
AHAFS AIX AIX 브랜치; 유지자와 테스트 환경 부족으로 실험적인 기능
FSEvents macOS x/sys/unix 지원 필요
USN Journals Windows x/sys/windows 지원 필요
Polling 모든 아직 지원되지 않음

아직 Android와 Solaris는 Linux와 illumos를 포함하여 테스트되지 않았습니다.

사용 사례

fsnotify의 사용 사례는 다음과 같은 경우를 포함하지만 이에 국한되지는 않습니다:

  1. 실시간 파일 동기화: fsnotify는 파일 시스템의 변경 사항을 실시간으로 모니터링하여 실시간 파일 동기화를 구현하는 데 적합합니다. 소스 파일에서 변경이 발생하면 해당 변경은 즉시 대상 파일에 동기화될 수 있으며 파일의 일관성을 보장합니다.
  2. 자동 빌드: fsnotify는 프로젝트의 소스 코드 및 의존성 파일의 변경 사항을 모니터링하여 변경이 발생하면 빌드 명령을 트리거하여 자동 빌드를 달성합니다. 이를 통해 수동 빌드의 시간과 노력을 절약하고 개발 효율성을 향상시킬 수 있습니다.
  3. 파일 백업: fsnotify는 백업이 필요한 파일이나 디렉터리의 변경 사항을 모니터링하고 변경이 발생하면 즉시 백업을 시작할 수 있습니다. 이를 통해 데이터의 보안을 보장하고 파일 손실 또는 손상으로 인한 데이터 손실을 방지할 수 있습니다.
  4. 실시간 로그 모니터링: fsnotify는 로그 파일의 생성, 수정, 삭제와 같은 작업을 모니터링하고 변경이 발생하면 로그 모니터링 프로그램을 트리거하여 로그 내용의 실시간 변경을 효과적으로 모니터링할 수 있습니다.
  5. 파일 시스템 보안 모니터링: fsnotify는 파일 시스템에서 파일 액세스, 수정, 삭제와 같은 보안 이벤트를 모니터링할 수 있습니다. 이를 통해 파일 시스템의 보안 이벤트를 신속하게 식별하고 기록할 수 있습니다.

사용 예제

기본 예제:

package main

import (
    "log"

    "github.com/fsnotify/fsnotify"
)

func main() {
    // 새로운 watcher를 생성합니다.
    watcher, err := fsnotify.NewWatcher()
    if err != nil {
        log.Fatal(err)
    }
    defer watcher.Close()

    // 이벤트를 수신하기 시작합니다.
    go func() {
        for {
            select {
            case event, ok := <-watcher.Events:
                if !ok {
                    return
                }
                log.Println("이벤트:", event)
                if event.Has(fsnotify.Write) {
                    log.Println("수정된 파일:", event.Name)
                }
            case err, ok := <-watcher.Errors:
                if !ok {
                    return
                }
                log.Println("에러:", err)
            }
        }
    }()

    // 모니터링이 필요한 경로를 추가합니다.
    err = watcher.Add("/tmp")
    if err != nil {
        log.Fatal(err)
    }

    // 메인 고루틴을 블록합니다.
    <-make(chan struct{})
}

더 많은 예제는 cmd/fsnotify에서 찾을 수 있으며 다음 명령을 사용하여 실행할 수 있습니다:

% go run ./cmd/fsnotify

더 자세한 문서는 godoc에서 확인할 수 있습니다: https://pkg.go.dev/github.com/fsnotify/fsnotify

자주 묻는 질문

파일을 다른 디렉터리로 이동하면 여전히 모니터링되나요?

아니요, 이동된 위치를 모니터링하고 있지 않는 한 모니터링되지 않습니다.

하위 디렉터리를 모니터링합니까?

아니요, 모니터링하려는 각 디렉터리에 대해 모니터링을 추가해야 합니다(재귀적 모니터링이 계획 중입니다: #18).

고루틴에서 오류 및 이벤트 채널을 동시에 모니터링해야합니까?

네. select를 사용하여 동일한 고루틴에서 두 채널에서 읽을 수 있습니다(각 채널에 대해 별도로 고루틴을 시작할 필요가 없습니다. 예를 참고하세요).

NFS, SMB, FUSE, /proc 또는 /sys에서는 왜 알림이 작동하지 않나요?

fsnotify는 기능을 사용하려면 기본 운영 체제의 지원이 필요합니다. 현재 NFS 및 SMB 프로토콜은 파일 알림에 대한 네트워크 수준 지원을 제공하지 않으며, /proc 및 /sys 가상 파일 시스템도 지원하지 않습니다.

이것은 폴링 감시자를 사용하여 수정할 수 있지만 아직 구현되지 않았습니다(#9).

왜 많은 Chmod 이벤트를 받나요?

일부 프로그램은 macOS의 Spotlight, 백신 프로그램, 백업 애플리케이션 및 기타 일부 알려진 애플리케이션 등 많은 속성 변경을 생성할 수 있습니다. 일반적으로 Chmod 이벤트를 무시하는 것이 좋으며 종종 쓸모 없을 뿐만 아니라 문제를 일으킬 수 있습니다.

macOS의 Spotlight 인덱싱은 여러 이벤트를 일으킬 수 있습니다(참조: #15). 일시적인 해결책은 Spotlight 개인 정보 설정에 폴더를 추가하는 것입니다. 네이티브 FSEvents 구현이 이루어질 때까지 (참조: #11) 사용해보세요.

파일을 감시하는 것이 잘 작동하지 않습니다

특별히 에디터와 같은 많은 프로그램이 파일을 디렉터리 대신 개별 파일을 감시하는 것은 권장되지 않습니다. 왜냐하면 파일을 원자적으로 업데이트할 것입니다: 임시 파일에 쓰고 나서 해당 위치로 옮겨 원본 파일(또는 그 변형)을 덮어씁니다. 이제 원래 파일에 대한 모니터링이 손실되었습니다.

따라서 전원 장애 또는 충돌로 인해 절반으로 쓰여진 파일이 발생하지 않습니다.

상위 디렉터리를 감시하고 Event.Name을 사용하여 관심 없는 파일을 걸러내세요. cmd/fsnotify/file.go에 예제가 있습니다.

특정 플랫폼에 대한 참고 사항

Linux

파일이 삭제되면 모든 파일 디스크립터가 닫힐 때까지 REMOVE 이벤트가 발생하지 않으며, 그 때 CHMOD 이벤트가 발생합니다:

fp := os.Open("file")
os.Remove("file")        // CHMOD
fp.Close()               // REMOVE

이것은 inotify에서 전송되는 이벤트이므로 이에 너무 많은 변경이 가해지지 않아야 합니다.

fs.inotify.max_user_watches 시스템 변수는 각 사용자당 최대 관찰 수(각 Watcher는 "인스턴스"이고 추가하는 각 경로는 "감시"입니다)를 지정하고, fs.inotify.max_user_instances는 각 사용자당 inotify 인스턴스의 최대 수를 지정합니다.

이것은 /proc에서도 찾을 수 있으며, /proc/sys/fs/inotify/max_user_watches/proc/sys/fs/inotify/max_user_instances 경로에서 찾을 수 있습니다.

이를 늘리려면 sysctl을 사용하거나 proc 파일에 값을 쓸 수 있습니다:

sysctl fs.inotify.max_user_watches=124983
sysctl fs.inotify.max_user_instances=128

변경 사항을 부팅 후에 적용하려면 /etc/sysctl.conf 또는 /usr/lib/sysctl.d/50-default.conf를 수정하세요(각 Linux 배포판마다 세부 사항은 다를 수 있으므로 해당 배포판 문서를 참조하세요):

fs.inotify.max_user_watches=124983
fs.inotify.max_user_instances=128

제한에 도달하면 "장치에 여유 공간이 없음" 또는 "열린 파일이 너무 많음" 오류가 발생할 수 있습니다.

kqueue (macOS, 모든 BSD 시스템)

kqueue는 감시되는 각 파일에 대해 파일 디스크립터를 열어야 하므로, 파일 다섯 개를 포함하는 디렉터리를 감시하고 있다면 여섯 개의 파일 디스크립터가 필요합니다. 이러한 플랫폼에서는 시스템의 "최대 열린 파일" 제한에 더 빨리 도달할 것입니다.

최대 열린 파일 수를 제어하기 위해 sysctl 변수 kern.maxfileskern.maxfilesperproc을 사용할 수 있습니다.