CLOVER🍀

That was when it all began.

はじめてのKibanaでグラフ表示

ElasticsearchとKibanaを動かせる環境を作ったので、今度は何かしらグラフ表示してみたいと思います。

Docker Composeで、ElasticsearchとKibanaを試す環境を作る - CLOVER

ネタはどうしようかなぁと思ったのですが…自分のブログ(http://d.hatena.ne.jp/Kazuhira/)の月ごとの投稿回数をお題にしてみました。
※一応、このブログは月1回は投稿しようという目標を自分で設けています

以下の流れでやります。

では、やってみます。

ブログのクロール

まずは、ブログをwgetでクロールします。

ローカルにApacheによるフォワードプロキシを、ポート8080で起動しているとして、以下のコマンドでこのブログのみをクロールします。

$ export http_proxy=http://localhost:8080/
$ wget -r --no-parent http://d.hatena.ne.jp/Kazuhira/

けっこう時間がかかりましたが、こんな感じのアクセスログが取得できました。

$ head -n 100 logs/access.log 
172.17.0.1 - - [16/Feb/2016:14:30:00 +0000] "GET http://d.hatena.ne.jp/Kazuhira/ HTTP/1.1" 200 276760 "-" "Wget/1.15 (linux-gnu)"
172.17.0.1 - - [16/Feb/2016:14:30:00 +0000] "GET http://d.hatena.ne.jp/robots.txt HTTP/1.1" 200 359 "-" "Wget/1.15 (linux-gnu)"
172.17.0.1 - - [16/Feb/2016:14:30:00 +0000] "GET http://d.hatena.ne.jp/Kazuhira/?of=5 HTTP/1.1" 200 403769 "http://d.hatena.ne.jp/Kazuhira/" "Wget/1.15 (linux-gnu)"
172.17.0.1 - - [16/Feb/2016:14:30:01 +0000] "GET http://d.hatena.ne.jp/Kazuhira/rss HTTP/1.1" 200 248281 "http://d.hatena.ne.jp/Kazuhira/" "Wget/1.15 (linux-gnu)"
172.17.0.1 - - [16/Feb/2016:14:30:01 +0000] "GET http://d.hatena.ne.jp/Kazuhira/rss2 HTTP/1.1" 200 244453 "http://d.hatena.ne.jp/Kazuhira/" "Wget/1.15 (linux-gnu)"
172.17.0.1 - - [16/Feb/2016:14:30:02 +0000] "GET http://d.hatena.ne.jp/Kazuhira/foaf HTTP/1.1" 200 1183 "http://d.hatena.ne.jp/Kazuhira/" "Wget/1.15 (linux-gnu)"
172.17.0.1 - - [16/Feb/2016:14:30:02 +0000] "GET http://d.hatena.ne.jp/Kazuhira/opensearch/diary.xml HTTP/1.1" 200 1030 "http://d.hatena.ne.jp/Kazuhira/" "Wget/1.15 (linux-gnu)"
172.17.0.1 - - [16/Feb/2016:14:30:02 +0000] "GET http://d.hatena.ne.jp/Kazuhira/opensearch/archive.xml HTTP/1.1" 200 1026 "http://d.hatena.ne.jp/Kazuhira/" "Wget/1.15 (linux-gnu)"
172.17.0.1 - - [16/Feb/2016:14:30:02 +0000] "GET http://d.hatena.ne.jp/Kazuhira/mobile HTTP/1.1" 200 78822 "http://d.hatena.ne.jp/Kazuhira/" "Wget/1.15 (linux-gnu)"
172.17.0.1 - - [16/Feb/2016:14:30:06 +0000] "GET http://d.hatena.ne.jp/Kazuhira/archive HTTP/1.1" 200 67406 "http://d.hatena.ne.jp/Kazuhira/" "Wget/1.15 (linux-gnu)"
172.17.0.1 - - [16/Feb/2016:14:30:07 +0000] "GET http://d.hatena.ne.jp/Kazuhira/20160214 HTTP/1.1" 200 89232 "http://d.hatena.ne.jp/Kazuhira/" "Wget/1.15 (linux-gnu)"
172.17.0.1 - - [16/Feb/2016:14:30:07 +0000] "GET http://d.hatena.ne.jp/Kazuhira/20160214/1455460595 HTTP/1.1" 200 52945 "http://d.hatena.ne.jp/Kazuhira/" "Wget/1.15 (linux-gnu)"
172.17.0.1 - - [16/Feb/2016:14:30:08 +0000] "GET http://d.hatena.ne.jp/Kazuhira/searchdiary?word=%2A%5BSpring%5D HTTP/1.1" 200 182759 "http://d.hatena.ne.jp/Kazuhira/" "Wget/1.15 (linux-gnu)"

Elasticsearchにインデックステンプレートを登録する

このアクセスログを、Elasticsearchに登録するわけですが、最初にインデックステンプレートを登録しておきます。

今回は、こんなテンプレートにしました。
index.json

{
  "template": "apache-accesslog_*",
  "settings": {
    "index": {
      "number_of_shards": 1,
      "number_of_replicas": 0
    }
  },
  "mappings": {
    "_default_": {
      "_source": { "enabled": false },
      "dynamic_templates": [ {
          "string_template": {
            "mapping": {
              "index": "not_analyzed",
              "type": "string",
              "store": "yes"
            },
            "match_mapping_type": "string",
            "match": "*"
          }
      } ]
    } 
  }
}

「apache-accesslog_*」のパターンで、テンプレートにしておきます。

これを、Elasticsearchに登録。

$ curl -XPUT http://localhost:9200/_template/apache-accesslog -d @index.json 
{"acknowledged":true}

EmbulkでアクセスログをElasticsearchに登録する

取得したアクセスログをElasticsearchに登録するわけですが、何で放り込むのかちょっと迷うところです。

今回は、Embulkにしました。

GitHub - embulk/embulk: Embulk: Pluggable Bulk Data Loader. http://www.embulk.org

インストール。

$ curl --create-dirs -o ~/.embulk/bin/embulk -L "http://dl.embulk.org/embulk-latest.jar"
$ chmod +x ~/.embulk/bin/embulk
$ echo 'export PATH="$HOME/.embulk/bin:$PATH"' >> ~/.bashrc
$ source ~/.bashrc

今回、ApacheのアクセスログをパースしてElasticsearchに放り込むわけですが、その際にアクセスログをフィルタする必要があります。ブログの記事単位のURLは、こういう形式なので。

http://d.hatena.ne.jp/Kazuhira/20160214/1455460595

で、これらを実現するためにプラグインをインストール。

$ embulk gem install embulk-parser-apache-custom-log
$ embulk gem install embulk-output-elasticsearch
$ embulk gem install embulk-filter-ruby_proc
$ embulk gem install embulk-filter-column
$ embulk gem install embulk-filter-row

書いた設定ファイルは、こちら。
config.yml

in:
  type: file
  path_prefix: logs/access.log
  parser:
    type: apache-custom-log
    format: "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\""
filters:
 - type: column
   add_columns:
     - { name: "blog-month", src: "request-line" }
 - type: ruby_proc
   columns:
     - name: blog-month
       proc: |
         ->(request_line) do
             match = request_line.match(/.+ https?:\/\/d\.hatena\.ne\.jp\/Kazuhira\/(\d+)\d\d\/\d+ .+/)

             if match then
               match[1]
             else
               nil
             end
           end
 - type: row
   conditions:
     - { column: blog-month, operator: "IS NOT NULL" }
out:
  type: elasticsearch
  mode: replace
  nodes:
  - {host: localhost, port: 9300}
  index: apache-accesslog
  index_type: log

Filterで、Request Lineをコピーして目的のURLだけ加工し、条件に合わなかったものを除外しました。その定義が、この部分です。

filters:
 - type: column
   add_columns:
     - { name: "blog-month", src: "request-line" }
 - type: ruby_proc
   columns:
     - name: blog-month
       proc: |
         ->(request_line) do
             match = request_line.match(/.+ https?:\/\/d\.hatena\.ne\.jp\/Kazuhira\/(\d+)\d\d\/\d+ .+/)

             if match then
               match[1]
             else
               nil
             end
           end
 - type: row
   conditions:
     - { column: blog-month, operator: "IS NOT NULL" }

これで、このURLの部分が

http://d.hatena.ne.jp/Kazuhira/20160214/1455460595

こうなります。

201602

こちらが、後の集計対象となります。

実行。

$ embulk run config.yml 
2016-02-20 19:32:22.284 +0900: Embulk v0.8.3
2016-02-20 19:32:25.415 +0900 [INFO] (0001:transaction): Loaded plugin embulk-output-elasticsearch (0.2.1)
2016-02-20 19:32:25.574 +0900 [INFO] (0001:transaction): Loaded plugin embulk-filter-column (0.4.0)
2016-02-20 19:32:25.600 +0900 [INFO] (0001:transaction): Loaded plugin embulk-filter-ruby_proc (0.2.0)
2016-02-20 19:32:25.655 +0900 [INFO] (0001:transaction): Loaded plugin embulk-filter-row (0.2.0)
2016-02-20 19:32:25.732 +0900 [INFO] (0001:transaction): Loaded plugin embulk-parser-apache-custom-log (0.4.0)
2016-02-20 19:32:25.808 +0900 [INFO] (0001:transaction): Listing local files at directory 'logs' filtering filename by prefix 'access.log'
2016-02-20 19:32:25.824 +0900 [INFO] (0001:transaction): Loading files [logs/access.log]
2016-02-20 19:32:25.857 +0900 [INFO] (0001:transaction): since format parameter is not given, use DateTimeFormatter.
2016-02-20 19:32:25.956 +0900 [INFO] (0001:transaction): Using local thread executor with max_threads=16 / output tasks 8 = input tasks 1 * 8
2016-02-20 19:32:26.228 +0900 [INFO] (0001:transaction): [Revanche] loaded [], sites []
2016-02-20 19:32:27.715 +0900 [INFO] (0001:transaction): Executing plugin with 'replace' mode.
2016-02-20 19:32:28.004 +0900 [INFO] (0001:transaction): Inserting data into index[apache-accesslog_20160220-193224]
2016-02-20 19:32:28.009 +0900 [INFO] (0001:transaction): {done:  0 / 1, running: 0}
2016-02-20 19:32:28.047 +0900 [INFO] (0038:task-0000): [Bast] loaded [], sites []
2016-02-20 19:32:28.152 +0900 [INFO] (0038:task-0000): [Caregiver] loaded [], sites []
2016-02-20 19:32:28.204 +0900 [INFO] (0038:task-0000): [Sultan] loaded [], sites []
2016-02-20 19:32:28.256 +0900 [INFO] (0038:task-0000): [Karolina Dean] loaded [], sites []
2016-02-20 19:32:28.328 +0900 [INFO] (0038:task-0000): [Zach] loaded [], sites []
2016-02-20 19:32:28.393 +0900 [INFO] (0038:task-0000): [Zeitgeist] loaded [], sites []
2016-02-20 19:32:28.452 +0900 [INFO] (0038:task-0000): [Aleksei Sytsevich] loaded [], sites []
2016-02-20 19:32:28.521 +0900 [INFO] (0038:task-0000): [Man-Brute] loaded [], sites []
2016-02-20 19:32:29.076 +0900 [INFO] (0038:task-0000): since format parameter is not given, use DateTimeFormatter.
2016-02-20 19:32:29.077 +0900 [INFO] (0038:task-0000): since format parameter is not given, use DateTimeFormatter.
2016-02-20 19:32:29.078 +0900 [INFO] (0038:task-0000): LogFormat : %h %l %u %t "%r" %>s %O "%{Referer}i" "%{User-Agent}i"
2016-02-20 19:32:29.078 +0900 [INFO] (0038:task-0000): RegExp    : (.*) ([^\s]*) (.*) \[([^\]]+)\] "(.*)" ([1-9]\d{2}) (-?\d+|-) "(.*)" "(.*)"
2016-02-20 19:32:29.079 +0900 [INFO] (0038:task-0000): replacement : 9
2016-02-20 19:32:31.149 +0900 [INFO] (0038:task-0000): Execute 127 bulk actions
2016-02-20 19:32:31.509 +0900 [INFO] (elasticsearch[Bast][listener][T#1]): 127 bulk actions succeeded
2016-02-20 19:32:31.653 +0900 [INFO] (0038:task-0000): Execute 109 bulk actions
2016-02-20 19:32:31.822 +0900 [INFO] (elasticsearch[Caregiver][listener][T#1]): 109 bulk actions succeeded
2016-02-20 19:32:31.868 +0900 [INFO] (0038:task-0000): Execute 19 bulk actions
2016-02-20 19:32:31.916 +0900 [INFO] (elasticsearch[Sultan][listener][T#1]): 19 bulk actions succeeded
2016-02-20 19:32:31.984 +0900 [INFO] (0038:task-0000): Execute 86 bulk actions
2016-02-20 19:32:32.124 +0900 [INFO] (elasticsearch[Karolina Dean][listener][T#1]): 86 bulk actions succeeded
2016-02-20 19:32:32.177 +0900 [INFO] (0038:task-0000): Execute 100 bulk actions
2016-02-20 19:32:32.243 +0900 [INFO] (elasticsearch[Zach][listener][T#1]): 100 bulk actions succeeded
2016-02-20 19:32:32.280 +0900 [INFO] (0038:task-0000): Execute 110 bulk actions
2016-02-20 19:32:32.486 +0900 [INFO] (elasticsearch[Zeitgeist][listener][T#1]): 110 bulk actions succeeded
2016-02-20 19:32:32.558 +0900 [INFO] (0038:task-0000): Execute 96 bulk actions
2016-02-20 19:32:32.669 +0900 [INFO] (elasticsearch[Aleksei Sytsevich][listener][T#1]): 96 bulk actions succeeded
2016-02-20 19:32:32.697 +0900 [INFO] (0038:task-0000): Execute 98 bulk actions
2016-02-20 19:32:32.776 +0900 [INFO] (elasticsearch[Man-Brute][listener][T#1]): 98 bulk actions succeeded
2016-02-20 19:32:32.795 +0900 [INFO] (0001:transaction): {done:  1 / 1, running: 0}
2016-02-20 19:32:32.823 +0900 [INFO] (0001:transaction): Assigned alias[apache-accesslog] to index[apache-accesslog_20160220-193224]
2016-02-20 19:32:32.941 +0900 [INFO] (main): Committed.
2016-02-20 19:32:32.942 +0900 [INFO] (main): Next config diff: {"in":{"last_path":"logs/access.log"},"out":{}}

無事、取り込まれたみたいです。

Kibanaで可視化する

最後は、Kibanaで可視化です。

インデックス名のパターンを設定。

データを認識することを確認して、今回は「Vertical bar chart」で表示してみました。
※標準は、古い月から並べています

ひと月あたり、20回以上投稿することもあれば、1回しか書いていない月とかありました…。意外と多く書いている月もあるんだなーと、ちょっと驚いた部分も。

まとめ

とりあえず簡単にですが、Elasticsearchに取り込んだデータをKibanaでグラフ表示してみました。今後は、他の用途のデータなどを可視化して、Kibanaに慣れていければなぁと思います。