DevDevデブ!!

プログラミングのこととか書きます。多分。。。

fsnotifyでなんやかんやするためのフレームワークっぽいの作った

こんにちは、こんにちは。

発売日に購入して丸一ヶ月かけてポケモンクリアしました。チャンピオン戦は四天王的なやつらと連戦させられるのかと思ったら、1戦毎にインターバルを挟む形式で、スタジアムの外にも出られる仕様だったので、拍子抜けでしたね。

ダンデには辛勝だったけど。オノノクスげきりんがやべえ。

今回の記事は、goでfsnotifyを使ったデーモンみたいなのを書くときに使えるフレームワーク的なものを作ったというお話です。(何いってんのか分からんな

fsnotifyって何よ?

github.com

上のgithubのREADMEの説明読んだほうが早いけど、あれです。ファイルの変更(更新、リネーム、削除、作成)を検知するためのgo用のライブラリです。

作ったもの

github.com

senjuです。千手観音からとりました。

何をしてくれるもの?

fsnotifyで検知できるイベントに対してイベントハンドラを設定して、無限ループさせるプログラム作成を補助する的な。そんなやつです。

使い方

READMEのusageをベースに説明します。

package main

import (
    "context"
    "fmt"
    "github.com/wano/senju"
    "gopkg.in/fsnotify/fsnotify.v1"
    "log"
    "os"
    "os/signal"
    "sync"
    "syscall"
)

func main() {
    sut := senju.New() //(1)
    eventHandler := senju.NewEventHandler() //(2)
    eventHandler.SetHandler(func(event fsnotify.Event) func(context.Context) error { //(3)
        return func(ctx context.Context) error {
            fmt.Println(event)
            return nil
        }
    }, senju.Rename) //watch Rename event
    sut.Add("/tmp/senju", eventHandler) //(4)

    sigCh := make(chan os.Signal)
    signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGTERM, syscall.SIGINT, syscall.SIGKILL, syscall.SIGQUIT)

    wg := &sync.WaitGroup{}
    go func(){ //(5)
        defer wg.Done()
        if err := sut.Run(); err != nil {
            log.Println(err)
        }
    }()
    wg.Add(1)
    <-sigCh
    sut.Close() //(6)
    wg.Wait()
}

まず、senju.NewでSenjuのインスタンスを作ります(1)。

次に、監視したいファイル毎にEventHandlerを作ります(2)。

そして、EventHandlerにイベント毎のハンドラーを設定します(3)。上記のサンプルではRenameイベントに対するハンドラを設定しています。

SetHandlerの関数シグネチャは以下のようになってます。

func (e *EventHandler) SetHandler(handler Handler, name EventName)

で、Handler型は以下のようになってます。

type Handler func(fsnotify.Event) func(context.Context) error

「fsnotify.Eventを引数にとって、func(context.Context) errorを返却する関数」です。イベントを検知すると、senjuがこの戻り値の関数を実行してくれます。

EventHandlerの設定が完了したら、Addで監視対象のファイルとヒモ付します(4)。この時点で監視対象のファイルが存在しなくてもエラーにはなりません。senjuの内部で一定間隔で監視対象のファイルが存在しているか見にいって、存在したらfsnotifyの監視対象に含めるAdd関数を実行しています。

あとは、goroutineの中でRun関数を実行して、senjuを起動します(5)。

あとはメイン関数の中で、senjuの終了を待ち受ける感じですね。

なんで作ったん?

RTBによる広告配信で、入札リクエストログのローテーションが発生した場合に、即座にs3にアップロードする処理を書きたくてfsnotifyを調べてたんだけど、イベント毎のハンドラ設定と無限ループでイベントを待ち受ける部分は簡単に汎用化できそうだったので切り出してみたって感じです。

なので、会社のリポジトリ

広告成果に関わるログについてはfluentd->kinesis->kinesis firehose->s3って感じで、ニアリアルタイムでs3に送ってるんだけど、今のうちの状況だと入札リクエストログの収集についてはあまりリアルタイム性を要求されないので、fluentd経由とは別にしたかったんですよね。

感想

これ、すでに誰か同じようなの作ってそうですね。名前だけかぶってないか確認してリポジトリ作ってささっと作っちゃったけど。