アクセスログで、クエリパラメータで集計したいという要件があったのでembulkを使ってみました
そもそもfluentd -> elasticsearchの時点で何とかできるかなと思ったものの
- 既に動いてるものに変更を加えるのが手間だった
- いまいちいいやり方を見つけられなかった
- 単純にembulk使ってみたかった
という事でembulkを使ってみました
fluentdで集められたアクセスログに対して
- embulkで1日一回データを読み込む
- クエリパラメータの値を取得
- elasticsearchに突っ込む
という感じに実装しました
embulkのインストール
公式に従ってインストール
yum install -y java-1.8.0-openjdk-devel.x86_64
- embulk
curl -o /usr/local/bin/embulk -L "http://dl.embulk.org/embulk-latest.jar" chmod +x /usr/local/bin/embulk
プラグインのインストール
embulk gem install embulk-parser-query_string embulk gem install embulk-filter-column embulk gem install embulk-output-elasticsearch
/usr/local/bin以下にインストールしたものの、プラグインなどはユーザーディレクトリの .embulk
以下に配置される+プラグインはそこを探しに行くようになっているので異なるユーザーで同じプラグインを使用する場合でもそれぞれインストールコマンドを打たなくてはならない模様
- embulk-filter-column
A filter plugin for Embulk to filter out columns
パースして取得した結果にカラムを定義したり追加したりできる
今回はクエリパラメータ以外に日付のデータを追加させるために使用
- embulk-parser-query_string
Embulk parser plugin for URL-encoded key value pairs
クエリパラメータをパースしてkey valueの形にしてくれるプラグイン
capture: でクエリパラメータがあるデータ範囲を指定して使用
- embulk-output-elasticsearch
インプットしたデータをElasticsearchに突っ込むためのプラグイン
雛形を作成
Embulk parser plugin for URL-encoded key value pairsの説明に従って生成
- partial-config.yml
in: type: file path_prefix: ./target_file parser: strip_quote: true strip_whitespace: true exec: {} out: {type: stdout}
embulk guess -g query_string partial-config.yml -o guessed.yml
configの設定
作成された雛形から実際に設定を書き込む、結果が以下
- guessed.yml
in: type: file path_prefix: /data/var/log/access_log-20151111.log parser: strip_quote: true strip_whitespace: true capture: '"path":("/path/to/a.*?")' charset: UTF-8 newline: CRLF type: query_string delimiter: "\t" quote: '"' escape: '"' trim_if_not_quoted: false skip_header_lines: 0 allow_extra_columns: time allow_optional_columns: false columns: - {name: media_id, type: long} - {name: app_id, type: long} - {name: system, type: string} - {name: model, type: string} filters: - type: column add_columns: - {name: date, type: timestamp, default: "2015-11-11", format: "%Y-%m-%d" } exec: {} out: type: elasticsearch nodes: - {host: 192.168.30.12, port: 9300} cluster_name: Test index: query_params-2015-11-11 index_type: a
parser-query_string
気をつけるのはcaptureの部分くらいですね
この部分にクエリパラメータがあります、というのを指定してあげる必要があります
今回は下記のようなアクセスログからpath
の部分だけをパースするため
2015-11-11 13:35:42 app.balancer.ip-192-168-30-1.access_log {"host":"1.1.1.1","user":"-","method":"GET","path":"/path/to/a?app_id=123&media_id=1&system=1.1&model=A1429","code":200,"size":5,"referer":"-","agent":"Apache-HttpClient/UNAVAILABLE (java 1.4)","request_time":0.009,"forwarded_for":"1.1.1.1","cookie":"-"}
capture: '"path":("/path/to.*?")'
の記述を入れました、読み込み対象のファイルのクエリパラメータの箇所が既に抜き出されている場合はこの設定はいらないようです
あとはcolumns:
でクエリパラメータの値をname
に指定、typeはそれぞれデータの型を指定してあげればOK
outをstdoutにして確認するとそれなりの結果が見れるはず
$ embulk run guessed.yml .... ,3,1.1,A1532 2015-11-24 13:46:21.188 +0000 [INFO] (transaction): {done:668 / 676, running: 1} 1,2,0.1,A1529 2015-11-24 13:46:21.201 +0000 [INFO] (transaction): {done:670 / 676, running: 1} ....
filter-column
パースするだけならこれだけでも十分ですが、いつのログか分からなくなってしまうので日付の情報がないといけません
elasticsearchに前日のログをパースしたデータを突っ込むという要件なので
- elasticsearchに突っ込んだ時刻をtimestampにすると一日ずれる
- クエリストリングにはtimestampに出来る情報は無い
- パースもとのログのtimestampを流用したい...
- これが出来れば一番良かったが見つけられなかった
という事で設定ファイルから直接カラムを追加してあげます
その時に使うのがこのfilter-columnプラグイン
任意のカラムを追加したり削除したりできます
今回は日付を追加してこの日の集計です、というのを分かるようにしました
余談ですがfilterプラグインは複数指定できるらしいのでやりようによっては複雑な事もできそう
実際に叩くとこうなる
$ embulk run guessed.yml .... ,3,1.1,A1532,2015-11-13 00:00:00.000000 +0000 2015-11-24 13:46:21.188 +0000 [INFO] (transaction): {done:668 / 676, running: 1} 1,2,0.1,A1529,2015-11-13 00:00:00.000000 +0000 2015-11-24 13:46:21.201 +0000 [INFO] (transaction): {done:670 / 676, running: 1} ....
output-elasticsearch
githubなどに従って必要な情報を入力するだけ
- 突っ込んだデータをcuratorで定期的に削除できるようにインデックス名に日付を入れるようにした
- グラフ化したときにpath毎にどうなのかというのも見たかったのでindex_typeをパスごとに設定した
- 設定ファイル分実行する必要がある
config file | path | index_type |
---|---|---|
a.yml | /path/to/a | a |
b.yml | /path/to/b | b |
c.yml | /path/to/c | c |
変数の指定
後は設定に書いたべた書きの日付を何とかします
日次でまわす必要があるのでconfigの日付の部分を変数で渡せるようにします
変数を参照する仕組みはembulkにもあります。
liquidというrubyのテンプレートエンジンをつかっているようです。
- 前日の日付をフォーマットに従って環境変数に格納
export datestr=`date --date "1 day ago" +"%Y%m%d"`
config側では {{ env.datestr }}
という形で参照することができます
ファイル名はyml.liquid
にしてあげる必要があります
embulk run test.yml.liquid
変数を読み込むようにしたバージョンが以下
- guessed.yml.liquid
in: type: file path_prefix: /data/var/log/access_log-{{ env.datestr }}.log parser: strip_quote: true strip_whitespace: true capture: '"path":("/path/to/a.*?")' charset: UTF-8 newline: CRLF type: query_string delimiter: "\t" quote: '"' escape: '"' trim_if_not_quoted: false skip_header_lines: 0 allow_extra_columns: time allow_optional_columns: false columns: - {name: media_id, type: long} - {name: app_id, type: long} - {name: system, type: string} - {name: model, type: string} filters: - type: column add_columns: - {name: date, type: timestamp, default: "{{ env.dateymd }}", format: "%Y-%m-%d" } exec: {} out: type: elasticsearch nodes: - {host: 192.168.30.12, port: 9300} cluster_name: Test index: query_params-{{ env.dateymd }} index_type: a
- ログファイルの特定
- インデックス名
- dateカラムの値
の三箇所で変数を参照させました
後はrunすれば良いだけ
コマンド一発で打つなら下記
export datestr=`date --date "1 day ago" +"%Y%m%d"` && export dateymd=`date --date "1 day ago" +"%Y-%m-%d"` && embulk run guessed.yml.liquid
cronで仕込むなら下記
%
はcrontabだとエスケープしないと動作してくれないのでエスケープしてあげます
0 0 * * * export datestr=`date --date "1 day ago" +"\%Y\%m\%d"` && export dateymd=`date --date "1 day ago" +"\%Y-\%m-\%d"` && /usr/local/bin/embulk run guessed.yml.liquid
環境変数のセットと実行を分けた場合は下記のようにすればいけそうですね
#!/bin/bash export datestr=`date --date "1 day ago" +"%Y%m%d"` export dateymd=`date --date "1 day ago" +"%Y-%m-%d"` exec "$@"
- crontab
ENV_SCRIPT=/var/embulk/query_params/env 0 1 * * * $ENV_SCRIPT /usr/local/bin/embulk run /var/embulk/query_params/guessed.yml.liquid
大分すっきりしました
これで実装は終わり
定期実行に関して、last_pathを使ってみようかと思ったが、下記理由により断念
query_params-YYYY-MM-DD というindexにすることでcuratorで定期的に削除したい
- index名を日付の連番にする必要がある
filterで追加しているカラムも変える必要がある
- パース元のログのタイムスタンプを何とか持ってこれればよかったが、いい方法が見つけられなかったためconfig内で変数を用いる必要が発生した
embulk側で対象ファイルの選定の時点で、日付を含めないと現在収集中のログも対象に入ってしまう
- 正確な集計が出来ない
などなどありこういう実装にしてみました
あんまり綺麗な方法では無い気がするので他にいい方法があればご指摘いただければと思います!
定期削除
最後にcuratorでelasticsearchのデータを定期的に削除するcronをセットして完了!
- crontab(elasticsearch)
0 1 * * * /usr/local/bin/curator --host 192.168.30.12 delete indices --prefix query_params --older-than 60 --time-unit days --timestring \%Y-\%m-\%d
まとめ
スクリプトで書いて集計値を出すより楽
- 設定書くだけ
何をしてるかわかりやすい
- スクリプトだと人によって癖が出たりする
kibana側でグラフを出すので柔軟なグラフが設定できる
- 月ごと、週ごと、日ごとなど
- path毎にどうなっているか
- このクエリとこのクエリの組み合わせは何件あるなど...
embulkに関しては他にもプラグインが出てきているので組み合わせは色々ある!
割と簡単に実装できるし効果もあるので今後取り入れて行きたいと思いました
次はエラーログをよしなに可視化したいですね
最後に今回の実装で出力したグラフをを申し訳程度に貼り付けて終わりにします