notebook

都内でWEB系エンジニアやってます。

fluentd+embulk+elasticsearchでクエリパラメータを可視化する

アクセスログで、クエリパラメータで集計したいという要件があったのでembulkを使ってみました

そもそもfluentd -> elasticsearchの時点で何とかできるかなと思ったものの

  • 既に動いてるものに変更を加えるのが手間だった
  • いまいちいいやり方を見つけられなかった
  • 単純にembulk使ってみたかった

という事でembulkを使ってみました

fluentdで集められたアクセスログに対して

  • embulkで1日一回データを読み込む
  • クエリパラメータの値を取得
  • elasticsearchに突っ込む

という感じに実装しました

f:id:swfz:20151126013443p:plain

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 plugin for URL-encoded key value pairs

クエリパラメータをパースしてkey valueの形にしてくれるプラグイン

capture: でクエリパラメータがあるデータ範囲を指定して使用

  • embulk-output-elasticsearch

enbulk-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にもあります。

Using variables

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に関しては他にもプラグインが出てきているので組み合わせは色々ある!

割と簡単に実装できるし効果もあるので今後取り入れて行きたいと思いました

次はエラーログをよしなに可視化したいですね

最後に今回の実装で出力したグラフをを申し訳程度に貼り付けて終わりにします

f:id:swfz:20151126013501p:plain