地方エンジニアの学習日記

興味ある技術の雑なメモだったりを書いてくブログ。たまに日記とガジェット紹介。

check-logの実装を眺めてみた

github.com

check-logはMackerelの公式プラグイン集のうちのログ監視を行えるツールです。例えばアプリケーションログに特定の文字列、例えばCRITみたいなのが出たらコマンドを失敗させるみたいな使い方ができるツールです。

Usage:
  main [OPTIONS]

Application Options:
  -f, --file=FILE                                Path to log file
  -p, --pattern=PAT                              Pattern to search for. If specified multiple, they will be treated together with the AND operator
      --suppress-pattern                         Suppress pattern display
  -E, --exclude=PAT                              Pattern to exclude from matching. If specified multiple, they will be treated together with the AND operator
  -w, --warning-over=                            Trigger a warning if matched lines is over a number
  -c, --critical-over=                           Trigger a critical if matched lines is over a number
      --warning-level=N                          Warning level if pattern has a group
      --critical-level=N                         Critical level if pattern has a group
  -r, --return                                   Return matched line
      --search-in-directory=DIR                  Specify the directory of files to be detected
  -F, --file-pattern=FILE                        Check a pattern of files, instead of one file
  -i, --icase                                    Run a case insensitive match
  -s, --state-dir=DIR                            Dir to keep state files under
      --no-state                                 Don't use state file and read whole logs
      --encoding=                                Encoding of log file
      --missing=(CRITICAL|WARNING|OK|UNKNOWN)    Exit status when log files missing (default: UNKNOWN)
      --check-first                              Check the log on the first run

Help Options:
  -h, --help                                     Show this help message

読み始める前にどんな実装なんだろうを想像してたのですがinotifyを使って何かしらしているんだろう思ってたのですがどうやら違うようでした。

ざっくり流れ

ざっくり流れは以下のようになっていました。

1 オプションの解析
2 ステートファイルのロード
3 ログファイルの探索、inodeの取得
4 ステートファイルにあるskipバイト数分seek
5 seekした以降のファイルの内容を指定された文字列パターンと一致するかどうかをcheck

ステートファイルにどこまで読んだのかを記録、inodeもそこに書いてあるのでローテートしても追うことが可能というような仕組みになっているようでした。tail -f的なものとは違ったようでした。

ログローテートの対応

fluentdに似ている実装ということが分かり満足してたのですがログローテートの対応はどうなっているんだって思って調べてみました。Mackerelで例えば1分間に1回の監視設定だと12:00では監視上問題がなくても12:01になるまでの時間にエラーが多発してそのタイミングでログローテートされてしまってはエラーを見逃してしまいますがどうなっているのか調べました。結論としてはそのような事象の対応も実装されていました。

func openOldFile(f string, state *state) (*os.File, error) {
    fi, err := os.Stat(f) // ローテート後のファイル
    if err != nil {
        return nil, err
    }
    inode := detectInode(fi) // ローテート後のファイルのinodeを取得
    if state.Inode > 0 && state.Inode != inode { // 引数で渡されたファイルのinodeとステートファイルのinodeが違う場合
        if oldFile, err := findFileByInode(state.Inode, filepath.Dir(f)); err == nil { // 古いログファイルを探す
            oldf, err := os.Open(oldFile)
            if err != nil {
                return nil, err
            }
            oldfi, _ := oldf.Stat() // 古いログファイルの情報を取得する
            if oldfi.Size() > state.SkipBytes { // 古いログファイルのサイズとskipすべきバイト数を比較
                oldf.Seek(state.SkipBytes, io.SeekStart) // ここにはいるのは最後に読み込んだタイミングの後にファイルが追記されていた場合
                return oldf, nil                                              // seekさせた状態のファイルディスクリプたを呼び出し元に戻す
            }
        } else if err != errFileNotFoundByInode {
            return nil, err
        }
                 // 存在しない場合は何もしない(ディレクトリが別の場合も何もしない模様)
    }
    return nil, nil
}

感想

先入観だけでいうとfsイベント監視なんだろうなと思ってたのですが全然違くてとても面白かったです。